Skip to content

Commit 1795eb2

Browse files
jcolemankeithf4
authored andcommitted
Fix (unique) index inheritance from template (#278)
v4.2.2 Fix (unique) index inheritance from template
1 parent 3e9768f commit 1795eb2

File tree

6 files changed

+242
-8
lines changed

6 files changed

+242
-8
lines changed

CHANGELOG.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
4.2.2
2+
-- Fix bug in template table index inheritance that prevented any additional indexes from being copied after finding an index definition that exists on both the template and parent tables. Github Pull Request #278.
3+
14
4.2.1
25
-- Fix bug in partition_data_time causing never-ending loop while calculating partition range for natively partitioned tables. Github issue #273
36

META.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "pg_partman",
33
"abstract": "Extension to manage partitioned tables by time or ID",
4-
"version": "4.2.1",
4+
"version": "4.2.2",
55
"maintainer": [
66
"Keith Fiske <[email protected]>"
77
],
@@ -20,9 +20,9 @@
2020
},
2121
"provides": {
2222
"pg_partman": {
23-
"file": "sql/pg_partman--4.2.1.sql",
23+
"file": "sql/pg_partman--4.2.2.sql",
2424
"docfile": "doc/pg_partman.md",
25-
"version": "4.2.1",
25+
"version": "4.2.2",
2626
"abstract": "Extension to manage partitioned tables by time or ID"
2727
}
2828
},

pg_partman.control

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
default_version = '4.2.1'
1+
default_version = '4.2.2'
22
comment = 'Extension to manage partitioned tables by time or ID'
33
relocatable = false

sql/functions/inherit_template_properties.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ IF current_setting('server_version_num')::int >= 100000 THEN
9898
AND i.indisvalid
9999
ORDER BY 1
100100
LOOP
101+
v_dupe_found := false;
102+
101103
IF current_setting('server_version_num')::int >= 110000 THEN
102104
FOR v_parent_index_list IN
103105
SELECT

test/test_native/test-id-native.sql

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
BEGIN;
1010
SELECT set_config('search_path','partman, public',false);
1111

12-
SELECT plan(115);
12+
SELECT plan(120);
1313
CREATE SCHEMA partman_test;
1414
CREATE SCHEMA partman_retention_test;
1515
CREATE ROLE partman_basic;
@@ -29,8 +29,6 @@ GRANT SELECT,INSERT,UPDATE ON partman_test.id_taptest_table TO partman_basic, PU
2929
GRANT ALL ON partman_test.id_taptest_table TO partman_revoke;
3030
-- Template table
3131
CREATE UNLOGGED TABLE partman_test.template_id_taptest_table (LIKE partman_test.id_taptest_table);
32-
-- Regular unique indexes do not work on native in PG11 if the partition key isn't included
33-
CREATE UNIQUE INDEX ON partman_test.template_id_taptest_table (col4);
3432

3533
DO $pg11_objects_check$
3634
BEGIN
@@ -43,10 +41,15 @@ ELSE
4341
-- Create on template table
4442
ALTER TABLE partman_test.template_id_taptest_table ADD PRIMARY KEY (col1);
4543
ALTER TABLE partman_test.template_id_taptest_table ADD FOREIGN KEY (col2) REFERENCES partman_test.fk_test_reference(col2);
46-
CREATE INDEX ON partman_test.template_id_taptest_table (col3);
4744
END IF;
4845
END $pg11_objects_check$;
4946

47+
-- Always create the index on teh template also so that we can test excluding duplicates.
48+
CREATE INDEX ON partman_test.template_id_taptest_table (col3);
49+
50+
-- Regular unique indexes do not work on native in PG11 if the partition key isn't included
51+
CREATE UNIQUE INDEX ON partman_test.template_id_taptest_table (col4);
52+
5053
SELECT create_parent('partman_test.id_taptest_table', 'col1', 'native', '10', p_jobmon := false, p_start_partition := '3000000000', p_template_table := 'partman_test.template_id_taptest_table');
5154
UPDATE part_config SET inherit_privileges = TRUE;
5255
SELECT reapply_privileges('partman_test.id_taptest_table');
@@ -69,6 +72,11 @@ SELECT col_is_fk('partman_test', 'id_taptest_table_p3000000010', 'col2', 'Check
6972
SELECT col_is_fk('partman_test', 'id_taptest_table_p3000000020', 'col2', 'Check that foreign key was inherited to id_taptest_table_p3000000020');
7073
SELECT col_is_fk('partman_test', 'id_taptest_table_p3000000030', 'col2', 'Check that foreign key was inherited to id_taptest_table_p3000000030');
7174
SELECT col_is_fk('partman_test', 'id_taptest_table_p3000000040', 'col2', 'Check that foreign key was inherited to id_taptest_table_p3000000040');
75+
SELECT is_indexed('partman_test', 'id_taptest_table_p3000000000', 'col4', 'Check that unique index was inherited to id_taptest_table_p3000000000');
76+
SELECT is_indexed('partman_test', 'id_taptest_table_p3000000010', 'col4', 'Check that unique index was inherited to id_taptest_table_p3000000010');
77+
SELECT is_indexed('partman_test', 'id_taptest_table_p3000000020', 'col4', 'Check that unique index was inherited to id_taptest_table_p3000000020');
78+
SELECT is_indexed('partman_test', 'id_taptest_table_p3000000030', 'col4', 'Check that unique index was inherited to id_taptest_table_p3000000030');
79+
SELECT is_indexed('partman_test', 'id_taptest_table_p3000000040', 'col4', 'Check that unique index was inherited to id_taptest_table_p3000000040');
7280
SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000000', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000000');
7381
SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000010', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000010');
7482
SELECT table_privs_are('partman_test', 'id_taptest_table_p3000000020', 'partman_basic', ARRAY['SELECT','INSERT','UPDATE'], 'Check partman_basic privileges of id_taptest_table_p3000000020');
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
CREATE OR REPLACE FUNCTION @[email protected]_template_properties (p_parent_table text, p_child_schema text, p_child_tablename text) RETURNS boolean
2+
LANGUAGE plpgsql
3+
AS $$
4+
DECLARE
5+
6+
v_child_relkind char;
7+
v_child_schema text;
8+
v_child_tablename text;
9+
v_child_unlogged char;
10+
v_dupe_found boolean := false;
11+
v_fk_list record;
12+
v_index_list record;
13+
v_inherit_fk boolean;
14+
v_parent_index_list record;
15+
v_parent_oid oid;
16+
v_parent_table text;
17+
v_sql text;
18+
v_template_oid oid;
19+
v_template_schemaname text;
20+
v_template_table text;
21+
v_template_tablename name;
22+
v_template_tablespace name;
23+
v_template_unlogged char;
24+
25+
BEGIN
26+
/*
27+
* Function to inherit the properties of the template table to newly created child tables.
28+
* Currently used for PostgreSQL 10 to inherit indexes and FKs since that is not natively available
29+
* For PG11, used to inherit non-partition-key unique indexes & primary keys
30+
*/
31+
32+
SELECT parent_table, template_table, inherit_fk
33+
INTO v_parent_table, v_template_table, v_inherit_fk
34+
FROM @[email protected]_config
35+
WHERE parent_table = p_parent_table;
36+
IF v_parent_table IS NULL THEN
37+
RAISE EXCEPTION 'Given parent table has no configuration in pg_partman: %', p_parent_table;
38+
ELSIF v_template_table IS NULL THEN
39+
RAISE EXCEPTION 'No template table set in configuration for given parent table: %', p_parent_table;
40+
END IF;
41+
42+
SELECT c.oid INTO v_parent_oid
43+
FROM pg_catalog.pg_class c
44+
JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
45+
WHERE n.nspname = split_part(p_parent_table, '.', 1)::name
46+
AND c.relname = split_part(p_parent_table, '.', 2)::name;
47+
IF v_parent_oid IS NULL THEN
48+
RAISE EXCEPTION 'Unable to find given parent table in system catalogs: %', p_parent_table;
49+
END IF;
50+
51+
SELECT n.nspname, c.relname, c.relkind INTO v_child_schema, v_child_tablename, v_child_relkind
52+
FROM pg_catalog.pg_class c
53+
JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
54+
WHERE n.nspname = p_child_schema::name
55+
AND c.relname = p_child_tablename::name;
56+
IF v_child_tablename IS NULL THEN
57+
RAISE EXCEPTION 'Unable to find given child table in system catalogs: %.%', v_child_schema, v_child_tablename;
58+
END IF;
59+
60+
IF v_child_relkind = 'p' THEN
61+
-- Subpartitioned parent, do not apply properties
62+
RAISE DEBUG 'inherit_template_properties: found given child is subpartition parent, so properties not inherited';
63+
RETURN false;
64+
END IF;
65+
66+
v_template_schemaname := split_part(v_template_table, '.', 1)::name;
67+
v_template_tablename := split_part(v_template_table, '.', 2)::name;
68+
69+
SELECT c.oid, ts.spcname INTO v_template_oid, v_template_tablespace
70+
FROM pg_catalog.pg_class c
71+
JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
72+
LEFT OUTER JOIN pg_catalog.pg_tablespace ts ON c.reltablespace = ts.oid
73+
WHERE n.nspname = v_template_schemaname
74+
AND c.relname = v_template_tablename;
75+
IF v_template_oid IS NULL THEN
76+
RAISE EXCEPTION 'Unable to find configured template table in system catalogs: %', v_template_table;
77+
END IF;
78+
79+
-- Index creation (Required for all indexes in PG10. Only for non-unique, non-partition key indexes in PG11)
80+
IF current_setting('server_version_num')::int >= 100000 THEN
81+
FOR v_index_list IN
82+
SELECT
83+
array_to_string(regexp_matches(pg_get_indexdef(indexrelid), ' USING .*'),',') AS statement
84+
, i.indisprimary
85+
, i.indisunique
86+
, ( SELECT array_agg( a.attname ORDER by x.r )
87+
FROM pg_catalog.pg_attribute a
88+
JOIN ( SELECT k, row_number() over () as r
89+
FROM unnest(i.indkey) k ) as x
90+
ON a.attnum = x.k AND a.attrelid = i.indrelid
91+
) AS indkey_names
92+
, c.relname AS index_name
93+
, ts.spcname AS tablespace_name
94+
FROM pg_catalog.pg_index i
95+
JOIN pg_catalog.pg_class c ON i.indexrelid = c.oid
96+
LEFT OUTER JOIN pg_catalog.pg_tablespace ts ON c.reltablespace = ts.oid
97+
WHERE i.indrelid = v_template_oid
98+
AND i.indisvalid
99+
ORDER BY 1
100+
LOOP
101+
v_dupe_found := false;
102+
103+
IF current_setting('server_version_num')::int >= 110000 THEN
104+
FOR v_parent_index_list IN
105+
SELECT
106+
array_to_string(regexp_matches(pg_get_indexdef(indexrelid), ' USING .*'),',') AS statement
107+
, i.indisprimary
108+
, ( SELECT array_agg( a.attname ORDER by x.r )
109+
FROM pg_catalog.pg_attribute a
110+
JOIN ( SELECT k, row_number() over () as r
111+
FROM unnest(i.indkey) k ) as x
112+
ON a.attnum = x.k AND a.attrelid = i.indrelid
113+
) AS indkey_names
114+
FROM pg_catalog.pg_index i
115+
WHERE i.indrelid = v_parent_oid
116+
AND i.indisvalid
117+
ORDER BY 1
118+
LOOP
119+
120+
IF v_parent_index_list.indisprimary AND v_index_list.indisprimary THEN
121+
IF v_parent_index_list.indkey_names = v_index_list.indkey_names THEN
122+
RAISE DEBUG 'Ignoring duplicate primary key on template table: % ', v_index_list.indkey_names;
123+
v_dupe_found := true;
124+
CONTINUE; -- only continue within this nested loop
125+
END IF;
126+
END IF;
127+
128+
IF v_parent_index_list.statement = v_index_list.statement THEN
129+
RAISE DEBUG 'Ignoring duplicate index on template table: %', v_index_list.statement;
130+
v_dupe_found := true;
131+
CONTINUE; -- only continue within this nested loop
132+
END IF;
133+
134+
END LOOP; -- end parent index loop
135+
END IF; -- End PG11 check
136+
137+
IF v_dupe_found = true THEN
138+
-- Only used in PG11 and should skip trying to create indexes that already existed on the parent
139+
CONTINUE;
140+
END IF;
141+
142+
IF v_index_list.indisprimary THEN
143+
v_sql := format('ALTER TABLE %I.%I ADD PRIMARY KEY (%s)'
144+
, v_child_schema
145+
, v_child_tablename
146+
, '"' || array_to_string(v_index_list.indkey_names, '","') || '"');
147+
IF v_index_list.tablespace_name IS NOT NULL THEN
148+
v_sql := v_sql || format(' USING INDEX TABLESPACE %I', v_index_list.tablespace_name);
149+
END IF;
150+
RAISE DEBUG 'Create pk: %', v_sql;
151+
EXECUTE v_sql;
152+
ELSE
153+
-- statement column should be just the portion of the index definition that defines what it actually is
154+
v_sql := format('CREATE %s INDEX ON %I.%I %s', CASE WHEN v_index_list.indisunique = TRUE THEN 'UNIQUE' ELSE '' END, v_child_schema, v_child_tablename, v_index_list.statement);
155+
IF v_index_list.tablespace_name IS NOT NULL THEN
156+
v_sql := v_sql || format(' TABLESPACE %I', v_index_list.tablespace_name);
157+
END IF;
158+
159+
RAISE DEBUG 'Create index: %', v_sql;
160+
EXECUTE v_sql;
161+
162+
END IF;
163+
164+
END LOOP;
165+
END IF;
166+
-- End index creation
167+
168+
-- Foreign key creation (PG10 only)
169+
IF current_setting('server_version_num')::int >= 100000 AND current_setting('server_version_num')::int < 110000 THEN
170+
IF v_inherit_fk THEN
171+
FOR v_fk_list IN
172+
SELECT pg_get_constraintdef(con.oid) AS constraint_def
173+
FROM pg_catalog.pg_constraint con
174+
JOIN pg_catalog.pg_class c ON con.conrelid = c.oid
175+
WHERE c.oid = v_template_oid
176+
AND contype = 'f'
177+
LOOP
178+
v_sql := format('ALTER TABLE %I.%I ADD %s', v_child_schema, v_child_tablename, v_fk_list.constraint_def);
179+
RAISE DEBUG 'Create FK: %', v_sql;
180+
EXECUTE v_sql;
181+
END LOOP;
182+
END IF;
183+
END IF;
184+
-- End foreign key creation
185+
186+
-- Tablespace inheritance
187+
IF v_template_tablespace IS NOT NULL THEN
188+
v_sql := format('ALTER TABLE %I.%I SET TABLESPACE %I', v_child_schema, v_child_tablename, v_template_tablespace);
189+
RAISE DEBUG 'Alter tablespace: %', v_sql;
190+
EXECUTE v_sql;
191+
END IF;
192+
193+
-- UNLOGGED status. Currently waiting on final stance of how native will handle this property being changed for its children.
194+
-- See release notes for v4.2.0
195+
SELECT relpersistence INTO v_template_unlogged
196+
FROM pg_catalog.pg_class c
197+
JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
198+
WHERE n.nspname = v_template_schemaname
199+
AND c.relname = v_template_tablename;
200+
201+
SELECT relpersistence INTO v_child_unlogged
202+
FROM pg_catalog.pg_class c
203+
JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
204+
WHERE n.nspname = v_child_schema::name
205+
AND c.relname = v_child_tablename::name;
206+
207+
IF v_template_unlogged = 'u' AND v_child_unlogged = 'p' THEN
208+
v_sql := format ('ALTER TABLE %I.%I SET UNLOGGED', v_child_schema, v_child_tablename);
209+
RAISE DEBUG 'Alter UNLOGGED: %', v_sql;
210+
EXECUTE v_sql;
211+
ELSIF v_template_unlogged = 'p' AND v_child_unlogged = 'u' THEN
212+
v_sql := format ('ALTER TABLE %I.%I SET LOGGED', v_child_schema, v_child_tablename);
213+
RAISE DEBUG 'Alter UNLOGGED: %', v_sql;
214+
EXECUTE v_sql;
215+
END IF;
216+
217+
RETURN true;
218+
219+
END
220+
$$;
221+

0 commit comments

Comments
 (0)