Skip to content

Commit 3ea8474

Browse files
committed
hash_replace branch merged int rel_1_2_beta
2 parents 984f2f4 + 862874b commit 3ea8474

File tree

6 files changed

+291
-7
lines changed

6 files changed

+291
-7
lines changed

expected/pathman_basic.out

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,14 +1565,64 @@ SELECT * FROM test.hash_rel WHERE id = 123;
15651565
123 | 456 | 789
15661566
(1 row)
15671567

1568+
/* Test replacing hash partition */
1569+
CREATE TABLE test.hash_rel_extern (LIKE test.hash_rel INCLUDING ALL);
1570+
SELECT pathman.replace_hash_partition('test.hash_rel_0', 'test.hash_rel_extern');
1571+
replace_hash_partition
1572+
------------------------
1573+
test.hash_rel_extern
1574+
(1 row)
1575+
1576+
\d+ test.hash_rel_0
1577+
Table "test.hash_rel_0"
1578+
Column | Type | Modifiers | Storage | Stats target | Description
1579+
--------+---------+------------------------------------------------------------+---------+--------------+-------------
1580+
id | integer | not null default nextval('test.hash_rel_id_seq'::regclass) | plain | |
1581+
value | integer | not null | plain | |
1582+
abc | integer | | plain | |
1583+
Indexes:
1584+
"hash_rel_0_pkey" PRIMARY KEY, btree (id)
1585+
Triggers:
1586+
hash_rel_upd_trig BEFORE UPDATE ON test.hash_rel_0 FOR EACH ROW EXECUTE PROCEDURE test.hash_rel_upd_trig_func()
1587+
1588+
\d+ test.hash_rel_extern
1589+
Table "test.hash_rel_extern"
1590+
Column | Type | Modifiers | Storage | Stats target | Description
1591+
--------+---------+------------------------------------------------------------+---------+--------------+-------------
1592+
id | integer | not null default nextval('test.hash_rel_id_seq'::regclass) | plain | |
1593+
value | integer | not null | plain | |
1594+
abc | integer | | plain | |
1595+
Indexes:
1596+
"hash_rel_extern_pkey" PRIMARY KEY, btree (id)
1597+
Check constraints:
1598+
"pathman_hash_rel_extern_2_check" CHECK (pathman.get_hash_part_idx(hashint4(value), 3) = 0)
1599+
Inherits: test.hash_rel
1600+
1601+
INSERT INTO test.hash_rel SELECT * FROM test.hash_rel_0;
1602+
DROP TABLE test.hash_rel_0;
1603+
/* Table with which we are replacing partition must have exact same structure */
1604+
CREATE TABLE test.hash_rel_wrong(
1605+
id INTEGER NOT NULL,
1606+
value INTEGER);
1607+
SELECT pathman.replace_hash_partition('test.hash_rel_1', 'test.hash_rel_wrong');
1608+
ERROR: partition must have the exact same structure as parent
1609+
EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel;
1610+
QUERY PLAN
1611+
-----------------------------------
1612+
Append
1613+
-> Seq Scan on hash_rel_extern
1614+
-> Seq Scan on hash_rel_1
1615+
-> Seq Scan on hash_rel_2
1616+
(4 rows)
1617+
15681618
/*
15691619
* Clean up
15701620
*/
15711621
SELECT pathman.drop_partitions('test.hash_rel');
1572-
NOTICE: drop cascades to 3 other objects
1573-
NOTICE: 2 rows copied from test.hash_rel_0
1622+
NOTICE: drop cascades to 2 other objects
15741623
NOTICE: 3 rows copied from test.hash_rel_1
15751624
NOTICE: 2 rows copied from test.hash_rel_2
1625+
NOTICE: 2 rows copied from test.hash_rel_extern
15761626
drop_partitions
15771627
-----------------
15781628
3
@@ -2248,6 +2298,6 @@ EXPLAIN (COSTS OFF) SELECT * FROM test.index_on_childs WHERE c1 > 100 AND c1 < 2
22482298
(12 rows)
22492299

22502300
DROP SCHEMA test CASCADE;
2251-
NOTICE: drop cascades to 50 other objects
2301+
NOTICE: drop cascades to 51 other objects
22522302
DROP EXTENSION pg_pathman CASCADE;
22532303
DROP SCHEMA pathman CASCADE;

hash.sql

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ DECLARE
2222
v_child_relname TEXT;
2323
v_plain_schema TEXT;
2424
v_plain_relname TEXT;
25-
v_atttype REGTYPE;
26-
v_hashfunc REGPROC;
25+
-- v_atttype REGTYPE;
26+
-- v_hashfunc REGPROC;
2727
v_init_callback REGPROCEDURE;
2828

2929
BEGIN
@@ -41,8 +41,8 @@ BEGIN
4141
PERFORM @extschema@.common_relation_checks(parent_relid, attribute);
4242

4343
/* Fetch atttype and its hash function */
44-
v_atttype := @extschema@.get_attribute_type(parent_relid, attribute);
45-
v_hashfunc := @extschema@.get_type_hash_func(v_atttype);
44+
-- v_atttype := @extschema@.get_attribute_type(parent_relid, attribute);
45+
-- v_hashfunc := @extschema@.get_type_hash_func(v_atttype);
4646

4747
SELECT * INTO v_plain_schema, v_plain_relname
4848
FROM @extschema@.get_plain_schema_and_relname(parent_relid);
@@ -72,6 +72,94 @@ END
7272
$$ LANGUAGE plpgsql
7373
SET client_min_messages = WARNING;
7474

75+
/*
76+
* Replace hash partition with another one. It could be useful in case when
77+
* someone wants to attach foreign table as a partition
78+
*/
79+
CREATE OR REPLACE FUNCTION @extschema@.replace_hash_partition(
80+
old_partition REGCLASS,
81+
new_partition REGCLASS)
82+
RETURNS REGCLASS AS
83+
$$
84+
DECLARE
85+
v_attname TEXT;
86+
rel_persistence CHAR;
87+
v_init_callback REGPROCEDURE;
88+
v_parent_relid REGCLASS;
89+
v_part_count INT;
90+
v_part_num INT;
91+
BEGIN
92+
PERFORM @extschema@.validate_relname(old_partition);
93+
PERFORM @extschema@.validate_relname(new_partition);
94+
95+
/* Parent relation */
96+
v_parent_relid := @extschema@.get_parent_of_partition(old_partition);
97+
98+
/* Acquire lock on parent */
99+
PERFORM @extschema@.lock_partitioned_relation(v_parent_relid);
100+
101+
/* Ignore temporary tables */
102+
SELECT relpersistence FROM pg_catalog.pg_class
103+
WHERE oid = new_partition INTO rel_persistence;
104+
105+
IF rel_persistence = 't'::CHAR THEN
106+
RAISE EXCEPTION 'temporary table "%" cannot be used as a partition',
107+
new_partition::TEXT;
108+
END IF;
109+
110+
/* Check that new partition has an equal structure as parent does */
111+
IF NOT @extschema@.validate_relations_equality(v_parent_relid, new_partition) THEN
112+
RAISE EXCEPTION 'partition must have the exact same structure as parent';
113+
END IF;
114+
115+
/* Get partitioning key */
116+
v_attname := attname FROM @extschema@.pathman_config WHERE partrel = v_parent_relid;
117+
IF v_attname IS NULL THEN
118+
RAISE EXCEPTION 'table "%" is not partitioned', v_parent_relid::TEXT;
119+
END IF;
120+
121+
/* Calculate partitions count and old partition's number */
122+
v_part_count := count(*) FROM @extschema@.pathman_partition_list WHERE parent = v_parent_relid;
123+
v_part_num := @extschema@.get_partition_hash(v_parent_relid, old_partition);
124+
125+
/* Detach old partition */
126+
EXECUTE format('ALTER TABLE %s NO INHERIT %s', old_partition, v_parent_relid);
127+
EXECUTE format('ALTER TABLE %s DROP CONSTRAINT IF EXISTS %s',
128+
old_partition,
129+
@extschema@.build_check_constraint_name(old_partition::REGCLASS,
130+
v_attname));
131+
132+
/* Attach new one */
133+
EXECUTE format('ALTER TABLE %s INHERIT %s', new_partition, v_parent_relid);
134+
EXECUTE format('ALTER TABLE %s ADD CONSTRAINT %s CHECK (%s)',
135+
new_partition,
136+
@extschema@.build_check_constraint_name(new_partition::regclass,
137+
v_attname),
138+
@extschema@.build_hash_condition(new_partition::regclass,
139+
v_attname,
140+
v_part_count,
141+
v_part_num));
142+
143+
/* Fetch init_callback from 'params' table */
144+
WITH stub_callback(stub) as (values (0))
145+
SELECT coalesce(init_callback, 0::REGPROCEDURE)
146+
FROM stub_callback
147+
LEFT JOIN @extschema@.pathman_config_params AS params
148+
ON params.partrel = v_parent_relid
149+
INTO v_init_callback;
150+
151+
PERFORM @extschema@.invoke_on_partition_created_callback(v_parent_relid,
152+
new_partition,
153+
v_init_callback);
154+
155+
/* Invalidate cache */
156+
PERFORM @extschema@.on_update_partitions(v_parent_relid);
157+
158+
RETURN new_partition;
159+
END
160+
$$
161+
LANGUAGE plpgsql;
162+
75163
/*
76164
* Creates an update trigger
77165
*/

init.sql

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,3 +783,23 @@ CREATE OR REPLACE FUNCTION @extschema@.invoke_on_partition_created_callback(
783783
init_callback REGPROCEDURE)
784784
RETURNS VOID AS 'pg_pathman', 'invoke_on_partition_created_callback'
785785
LANGUAGE C;
786+
787+
/*
788+
* Build hash condition for a CHECK CONSTRAINT
789+
*/
790+
CREATE OR REPLACE FUNCTION @extschema@.build_hash_condition(
791+
parent_relid REGCLASS,
792+
attname TEXT,
793+
partitions_count INT,
794+
partition_number INT)
795+
RETURNS TEXT AS 'pg_pathman', 'build_hash_condition'
796+
LANGUAGE C;
797+
798+
/*
799+
* Returns hash value for specified partition (0..N)
800+
*/
801+
CREATE OR REPLACE FUNCTION @extschema@.get_partition_hash(
802+
parent_relid REGCLASS,
803+
partition REGCLASS)
804+
RETURNS INT AS 'pg_pathman', 'get_partition_hash'
805+
LANGUAGE C;

sql/pathman_basic.sql

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,20 @@ ALTER TABLE test.hash_rel ADD COLUMN abc int;
448448
INSERT INTO test.hash_rel (id, value, abc) VALUES (123, 456, 789);
449449
SELECT * FROM test.hash_rel WHERE id = 123;
450450

451+
/* Test replacing hash partition */
452+
CREATE TABLE test.hash_rel_extern (LIKE test.hash_rel INCLUDING ALL);
453+
SELECT pathman.replace_hash_partition('test.hash_rel_0', 'test.hash_rel_extern');
454+
\d+ test.hash_rel_0
455+
\d+ test.hash_rel_extern
456+
INSERT INTO test.hash_rel SELECT * FROM test.hash_rel_0;
457+
DROP TABLE test.hash_rel_0;
458+
/* Table with which we are replacing partition must have exact same structure */
459+
CREATE TABLE test.hash_rel_wrong(
460+
id INTEGER NOT NULL,
461+
value INTEGER);
462+
SELECT pathman.replace_hash_partition('test.hash_rel_1', 'test.hash_rel_wrong');
463+
EXPLAIN (COSTS OFF) SELECT * FROM test.hash_rel;
464+
451465
/*
452466
* Clean up
453467
*/

src/pl_hash_funcs.c

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@
1515

1616
#include "utils/builtins.h"
1717
#include "utils/typcache.h"
18+
#include "utils/lsyscache.h"
19+
#include "utils/builtins.h"
1820

1921

2022
/* Function declarations */
2123

2224
PG_FUNCTION_INFO_V1( create_hash_partitions_internal );
2325
PG_FUNCTION_INFO_V1( get_type_hash_func );
2426
PG_FUNCTION_INFO_V1( get_hash_part_idx );
27+
PG_FUNCTION_INFO_V1( build_hash_condition );
28+
PG_FUNCTION_INFO_V1( get_partition_hash );
2529

2630

2731
/*
@@ -79,3 +83,75 @@ get_hash_part_idx(PG_FUNCTION_ARGS)
7983

8084
PG_RETURN_UINT32(hash_to_part_index(value, part_count));
8185
}
86+
87+
/*
88+
* Build hash condition for a CHECK CONSTRAINT
89+
*/
90+
Datum
91+
build_hash_condition(PG_FUNCTION_ARGS)
92+
{
93+
TypeCacheEntry *tce;
94+
95+
Oid parent = PG_GETARG_OID(0);
96+
text *attname = PG_GETARG_TEXT_P(1);
97+
uint32 partitions_count = PG_GETARG_UINT32(2);
98+
uint32 partition_number = PG_GETARG_UINT32(3);
99+
Oid attyp;
100+
char *result;
101+
102+
if (partition_number >= partitions_count)
103+
elog(ERROR,
104+
"Partition number cannot exceed partitions count");
105+
106+
/* Get attribute type and its hash function oid */
107+
attyp = get_attribute_type(parent, text_to_cstring(attname), false);
108+
if (attyp == InvalidOid)
109+
elog(ERROR,
110+
"Relation '%s' has no attribute '%s'",
111+
get_rel_name(parent),
112+
text_to_cstring(attname));
113+
114+
tce = lookup_type_cache(attyp, TYPECACHE_HASH_PROC);
115+
116+
/* Create hash condition CSTRING */
117+
result = psprintf("%s.get_hash_part_idx(%s(%s), %u) = %u",
118+
get_namespace_name(get_pathman_schema()),
119+
get_func_name(tce->hash_proc),
120+
text_to_cstring(attname),
121+
partitions_count,
122+
partition_number);
123+
124+
PG_RETURN_TEXT_P(cstring_to_text(result));
125+
}
126+
127+
/*
128+
* Returns hash value for specified partition (0..N)
129+
*/
130+
Datum
131+
get_partition_hash(PG_FUNCTION_ARGS)
132+
{
133+
const PartRelationInfo *prel;
134+
Oid parent = PG_GETARG_OID(0);
135+
Oid partition = PG_GETARG_OID(1);
136+
Oid *children;
137+
int i;
138+
139+
/* Validate partition type */
140+
prel = get_pathman_relation_info(parent);
141+
if (!prel || prel->parttype != PT_HASH)
142+
elog(ERROR,
143+
"Relation '%s' isn't partitioned by hash",
144+
get_rel_name(parent));
145+
146+
/* Searching for partition */
147+
children = PrelGetChildrenArray(prel);
148+
for (i=0; i<prel->children_count; i++)
149+
if (children[i] == partition)
150+
PG_RETURN_UINT32(i);
151+
152+
/* If we get here then there is no such partition for specified parent */
153+
elog(ERROR,
154+
"Relation '%s' isn't a part of partitioned table '%s'",
155+
get_rel_name(parent),
156+
get_rel_name(partition));
157+
}

tests/python/partitioning_test.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,14 @@ def test_foreign_table(self):
353353
master.start()
354354
master.psql('postgres', 'create extension pg_pathman')
355355
master.psql('postgres', 'create extension postgres_fdw')
356+
357+
# RANGE partitioning test with FDW:
358+
# - create range partitioned table in master
359+
# - create foreign server
360+
# - create foreign table and insert some data into it
361+
# - attach foreign table to partitioned one
362+
# - try inserting data into foreign partition via parent
363+
# - drop partitions
356364
master.psql(
357365
'postgres',
358366
'''create table abc(id serial, name text);
@@ -405,6 +413,34 @@ def test_foreign_table(self):
405413
# Testing drop partitions (including foreign partitions)
406414
master.safe_psql('postgres', 'select drop_partitions(\'abc\')')
407415

416+
# HASH partitioning with FDW:
417+
# - create hash partitioned table in master
418+
# - create foreign table
419+
# - replace local partition with foreign one
420+
# - insert data
421+
# - drop partitions
422+
master.psql(
423+
'postgres',
424+
'''create table hash_test(id serial, name text);
425+
select create_hash_partitions('hash_test', 'id', 2)''')
426+
fserv.safe_psql('postgres', 'create table f_hash_test(id serial, name text)')
427+
428+
master.safe_psql(
429+
'postgres',
430+
'''import foreign schema public limit to (f_hash_test)
431+
from server fserv into public'''
432+
)
433+
master.safe_psql(
434+
'postgres',
435+
'select replace_hash_partition(\'hash_test_1\', \'f_hash_test\')')
436+
master.safe_psql('postgres', 'insert into hash_test select generate_series(1,10)')
437+
438+
self.assertEqual(
439+
master.safe_psql('postgres', 'select * from hash_test'),
440+
'1|\n2|\n5|\n6|\n8|\n9|\n3|\n4|\n7|\n10|\n'
441+
)
442+
master.safe_psql('postgres', 'select drop_partitions(\'hash_test\')')
443+
408444
def test_parallel_nodes(self):
409445
"""Test parallel queries under partitions"""
410446

0 commit comments

Comments
 (0)