From f6f331aaa1f5211b37bbf7056030c99967475f4e Mon Sep 17 00:00:00 2001 From: Arpan Agrawal Date: Tue, 10 Sep 2024 17:08:08 +0530 Subject: [PATCH 01/13] [DNR][pg15] feat: support foreign keys that reference partitioned tables Summary: TBD Test Plan: Jenkins: rebase: pg15 Subscribers: yql Differential Revision: https://phorge.dev.yugabyte.com/D37933 --- src/postgres/src/backend/commands/tablecmds.c | 10 ---------- src/postgres/src/backend/utils/adt/ri_triggers.c | 3 ++- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/postgres/src/backend/commands/tablecmds.c b/src/postgres/src/backend/commands/tablecmds.c index c52c57061e70..5bd5df488253 100644 --- a/src/postgres/src/backend/commands/tablecmds.c +++ b/src/postgres/src/backend/commands/tablecmds.c @@ -9720,16 +9720,6 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, * Validity checks (permission checks wait till we have the column * numbers) */ - /* - * YB_TODO(feat): begin: Remove after adding support for foreign keys that reference - * partitioned tables - */ - if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot reference partitioned table \"%s\"", - RelationGetRelationName(pkrel)))); - /* YB_TODO(feat): end */ - if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { if (!recurse) diff --git a/src/postgres/src/backend/utils/adt/ri_triggers.c b/src/postgres/src/backend/utils/adt/ri_triggers.c index 7e53d2bc0d02..d4bac95210f6 100644 --- a/src/postgres/src/backend/utils/adt/ri_triggers.c +++ b/src/postgres/src/backend/utils/adt/ri_triggers.c @@ -425,7 +425,8 @@ RI_FKey_check(TriggerData *trigdata) break; } - if (IsYBRelation(pk_rel)) + // YB_TODO: temp disable fast path if the referenced relation is partitioned + if (IsYBRelation(pk_rel) && pk_rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) { /* * Use fast path for FK check in case ybctid for row in source table can be build from From 5f89c40fbacada20913bc7a5c581f8046000418a Mon Sep 17 00:00:00 2001 From: Arpan Agrawal Date: Tue, 10 Sep 2024 19:41:29 +0530 Subject: [PATCH 02/13] import upstream PG tests --- .../test/isolation/yb_pg_isolation_schedule | 2 + .../regress/expected/yb_pg_foreign_key.out | 390 +++++++++++++++++- .../test/regress/sql/yb_pg_foreign_key.sql | 249 ++++++++++- 3 files changed, 616 insertions(+), 25 deletions(-) diff --git a/src/postgres/src/test/isolation/yb_pg_isolation_schedule b/src/postgres/src/test/isolation/yb_pg_isolation_schedule index bbbbcffcd8ab..0d2d013077ea 100644 --- a/src/postgres/src/test/isolation/yb_pg_isolation_schedule +++ b/src/postgres/src/test/isolation/yb_pg_isolation_schedule @@ -29,6 +29,8 @@ test: update-locked-tuple test: delete-abort-savept test: fk-deadlock test: fk-deadlock2 +test: fk-partitioned-1 +test: fk-partitioned-2 test: insert-conflict-do-nothing test: insert-conflict-do-update-2 test: insert-conflict-do-update diff --git a/src/postgres/src/test/regress/expected/yb_pg_foreign_key.out b/src/postgres/src/test/regress/expected/yb_pg_foreign_key.out index 32b4cdc5ddf2..ee1e23f39a34 100644 --- a/src/postgres/src/test/regress/expected/yb_pg_foreign_key.out +++ b/src/postgres/src/test/regress/expected/yb_pg_foreign_key.out @@ -1499,19 +1499,6 @@ drop table pktable2, fktable2; -- -- Foreign keys and partitioned tables -- --- partitioned table in the referenced side are not allowed -CREATE TABLE fk_partitioned_pk (a int, b int, primary key (a, b)) - PARTITION BY RANGE (a, b); --- verify with create table first ... -CREATE TABLE fk_notpartitioned_fk (a int, b int, - FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk); -ERROR: cannot reference partitioned table "fk_partitioned_pk" --- and then with alter table. -CREATE TABLE fk_notpartitioned_fk_2 (a int, b int); -ALTER TABLE fk_notpartitioned_fk_2 ADD FOREIGN KEY (a, b) - REFERENCES fk_partitioned_pk; -ERROR: cannot reference partitioned table "fk_partitioned_pk" -DROP TABLE fk_partitioned_pk, fk_notpartitioned_fk_2; -- Creation of a partitioned hierarchy with irregular definitions CREATE TABLE fk_notpartitioned_pk (fdrop1 int, a int, fdrop2 int, b int, PRIMARY KEY (a, b)); @@ -1933,3 +1920,380 @@ drop schema fkpart0, fkpart1, fkpart2 cascade; */ -- TODO(jason): remove when issue #1721 is closed or closing. DISCARD TEMP; +-- Test a partitioned table as referenced table. +-- Verify basic functionality with a regular partition creation and a partition +-- with a different column layout, as well as partitions added (created and +-- attached) after creating the foreign key. +CREATE SCHEMA fkpart3; +SET search_path TO fkpart3; +CREATE TABLE pk (a int PRIMARY KEY) PARTITION BY RANGE (a); +CREATE TABLE pk1 PARTITION OF pk FOR VALUES FROM (0) TO (1000); +CREATE TABLE pk2 (b int, a int); +ALTER TABLE pk2 DROP COLUMN b; +ALTER TABLE pk2 ALTER a SET NOT NULL; +ALTER TABLE pk ATTACH PARTITION pk2 FOR VALUES FROM (1000) TO (2000); +NOTICE: table rewrite may lead to inconsistencies +DETAIL: Concurrent DMLs may not be reflected in the new table. +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19860. Set 'ysql_suppress_unsafe_alter_notice' yb-tserver gflag to true to suppress this notice. +CREATE TABLE fk (a int) PARTITION BY RANGE (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES FROM (0) TO (750); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk; +CREATE TABLE fk2 (b int, a int) ; +ALTER TABLE fk2 DROP COLUMN b; +ALTER TABLE fk ATTACH PARTITION fk2 FOR VALUES FROM (750) TO (3500); +CREATE TABLE pk3 PARTITION OF pk FOR VALUES FROM (2000) TO (3000); +CREATE TABLE pk4 (LIKE pk); +ALTER TABLE pk ATTACH PARTITION pk4 FOR VALUES FROM (3000) TO (4000); +NOTICE: table rewrite may lead to inconsistencies +DETAIL: Concurrent DMLs may not be reflected in the new table. +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19860. Set 'ysql_suppress_unsafe_alter_notice' yb-tserver gflag to true to suppress this notice. +CREATE TABLE pk5 (c int, b int, a int NOT NULL) PARTITION BY RANGE (a); +ALTER TABLE pk5 DROP COLUMN b, DROP COLUMN c; +CREATE TABLE pk51 PARTITION OF pk5 FOR VALUES FROM (4000) TO (4500); +CREATE TABLE pk52 PARTITION OF pk5 FOR VALUES FROM (4500) TO (5000); +ALTER TABLE pk ATTACH PARTITION pk5 FOR VALUES FROM (4000) TO (5000); +NOTICE: table rewrite may lead to inconsistencies +DETAIL: Concurrent DMLs may not be reflected in the new table. +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19860. Set 'ysql_suppress_unsafe_alter_notice' yb-tserver gflag to true to suppress this notice. +NOTICE: table rewrite may lead to inconsistencies +DETAIL: Concurrent DMLs may not be reflected in the new table. +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19860. Set 'ysql_suppress_unsafe_alter_notice' yb-tserver gflag to true to suppress this notice. +CREATE TABLE fk3 PARTITION OF fk FOR VALUES FROM (3500) TO (5000); +-- these should fail: referenced value not present +INSERT into fk VALUES (1); +ERROR: insert or update on table "fk1" violates foreign key constraint "fk_a_fkey" +DETAIL: Key (a)=(1) is not present in table "pk". +INSERT into fk VALUES (1000); +ERROR: insert or update on table "fk2" violates foreign key constraint "fk_a_fkey" +DETAIL: Key (a)=(1000) is not present in table "pk". +INSERT into fk VALUES (2000); +ERROR: insert or update on table "fk2" violates foreign key constraint "fk_a_fkey" +DETAIL: Key (a)=(2000) is not present in table "pk". +INSERT into fk VALUES (3000); +ERROR: insert or update on table "fk2" violates foreign key constraint "fk_a_fkey" +DETAIL: Key (a)=(3000) is not present in table "pk". +INSERT into fk VALUES (4000); +ERROR: insert or update on table "fk3" violates foreign key constraint "fk_a_fkey" +DETAIL: Key (a)=(4000) is not present in table "pk". +INSERT into fk VALUES (4500); +ERROR: insert or update on table "fk3" violates foreign key constraint "fk_a_fkey" +DETAIL: Key (a)=(4500) is not present in table "pk". +-- insert into the referenced table, now they should work +INSERT into pk VALUES (1), (1000), (2000), (3000), (4000), (4500); +INSERT into fk VALUES (1), (1000), (2000), (3000), (4000), (4500); +-- should fail: referencing value present +DELETE FROM pk WHERE a = 1; +ERROR: update or delete on table "pk1" violates foreign key constraint "fk_a_fkey1" on table "fk" +DETAIL: Key (a)=(1) is still referenced from table "fk". +DELETE FROM pk WHERE a = 1000; +ERROR: update or delete on table "pk2" violates foreign key constraint "fk_a_fkey2" on table "fk" +DETAIL: Key (a)=(1000) is still referenced from table "fk". +DELETE FROM pk WHERE a = 2000; +ERROR: update or delete on table "pk3" violates foreign key constraint "fk_a_fkey3" on table "fk" +DETAIL: Key (a)=(2000) is still referenced from table "fk". +DELETE FROM pk WHERE a = 3000; +ERROR: update or delete on table "pk4" violates foreign key constraint "fk_a_fkey4" on table "fk" +DETAIL: Key (a)=(3000) is still referenced from table "fk". +DELETE FROM pk WHERE a = 4000; +ERROR: update or delete on table "pk51" violates foreign key constraint "fk_a_fkey6" on table "fk" +DETAIL: Key (a)=(4000) is still referenced from table "fk". +DELETE FROM pk WHERE a = 4500; +ERROR: update or delete on table "pk52" violates foreign key constraint "fk_a_fkey7" on table "fk" +DETAIL: Key (a)=(4500) is still referenced from table "fk". +UPDATE pk SET a = 2 WHERE a = 1; +ERROR: update or delete on table "pk1" violates foreign key constraint "fk_a_fkey1" on table "fk" +DETAIL: Key (a)=(1) is still referenced from table "fk". +UPDATE pk SET a = 1002 WHERE a = 1000; +ERROR: update or delete on table "pk2" violates foreign key constraint "fk_a_fkey2" on table "fk" +DETAIL: Key (a)=(1000) is still referenced from table "fk". +UPDATE pk SET a = 2002 WHERE a = 2000; +ERROR: update or delete on table "pk3" violates foreign key constraint "fk_a_fkey3" on table "fk" +DETAIL: Key (a)=(2000) is still referenced from table "fk". +UPDATE pk SET a = 3002 WHERE a = 3000; +ERROR: update or delete on table "pk4" violates foreign key constraint "fk_a_fkey4" on table "fk" +DETAIL: Key (a)=(3000) is still referenced from table "fk". +UPDATE pk SET a = 4002 WHERE a = 4000; +ERROR: update or delete on table "pk51" violates foreign key constraint "fk_a_fkey6" on table "fk" +DETAIL: Key (a)=(4000) is still referenced from table "fk". +UPDATE pk SET a = 4502 WHERE a = 4500; +ERROR: update or delete on table "pk52" violates foreign key constraint "fk_a_fkey7" on table "fk" +DETAIL: Key (a)=(4500) is still referenced from table "fk". +-- now they should work +DELETE FROM fk; +UPDATE pk SET a = 2 WHERE a = 1; +DELETE FROM pk WHERE a = 2; +UPDATE pk SET a = 1002 WHERE a = 1000; +DELETE FROM pk WHERE a = 1002; +UPDATE pk SET a = 2002 WHERE a = 2000; +DELETE FROM pk WHERE a = 2002; +UPDATE pk SET a = 3002 WHERE a = 3000; +DELETE FROM pk WHERE a = 3002; +UPDATE pk SET a = 4002 WHERE a = 4000; +DELETE FROM pk WHERE a = 4002; +UPDATE pk SET a = 4502 WHERE a = 4500; +DELETE FROM pk WHERE a = 4502; +CREATE SCHEMA fkpart4; +SET search_path TO fkpart4; +-- dropping/detaching PARTITIONs is prevented if that would break +-- a foreign key's existing data +CREATE TABLE droppk (a int PRIMARY KEY) PARTITION BY RANGE (a); +CREATE TABLE droppk1 PARTITION OF droppk FOR VALUES FROM (0) TO (1000); +CREATE TABLE droppk_d PARTITION OF droppk DEFAULT; +CREATE TABLE droppk2 PARTITION OF droppk FOR VALUES FROM (1000) TO (2000) + PARTITION BY RANGE (a); +CREATE TABLE droppk21 PARTITION OF droppk2 FOR VALUES FROM (1000) TO (1400); +CREATE TABLE droppk2_d PARTITION OF droppk2 DEFAULT; +INSERT into droppk VALUES (1), (1000), (1500), (2000); +CREATE TABLE dropfk (a int REFERENCES droppk); +INSERT into dropfk VALUES (1), (1000), (1500), (2000); +-- these should all fail +ALTER TABLE droppk DETACH PARTITION droppk_d; +ERROR: removing partition "droppk_d" violates foreign key constraint "dropfk_a_fkey5" +DETAIL: Key (a)=(2000) is still referenced from table "dropfk". +ALTER TABLE droppk2 DETACH PARTITION droppk2_d; +ERROR: removing partition "droppk2_d" violates foreign key constraint "dropfk_a_fkey4" +DETAIL: Key (a)=(1500) is still referenced from table "dropfk". +ALTER TABLE droppk DETACH PARTITION droppk1; +ERROR: removing partition "droppk1" violates foreign key constraint "dropfk_a_fkey1" +DETAIL: Key (a)=(1) is still referenced from table "dropfk". +ALTER TABLE droppk DETACH PARTITION droppk2; +ERROR: removing partition "droppk2" violates foreign key constraint "dropfk_a_fkey2" +DETAIL: Key (a)=(1000) is still referenced from table "dropfk". +ALTER TABLE droppk2 DETACH PARTITION droppk21; +ERROR: removing partition "droppk21" violates foreign key constraint "dropfk_a_fkey3" +DETAIL: Key (a)=(1000) is still referenced from table "dropfk". +-- dropping partitions is disallowed +DROP TABLE droppk_d; +ERROR: cannot drop table droppk_d because other objects depend on it +DETAIL: constraint dropfk_a_fkey on table dropfk depends on table droppk_d +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP TABLE droppk2_d; +ERROR: cannot drop table droppk2_d because other objects depend on it +DETAIL: constraint dropfk_a_fkey on table dropfk depends on table droppk2_d +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP TABLE droppk1; +ERROR: cannot drop table droppk1 because other objects depend on it +DETAIL: constraint dropfk_a_fkey on table dropfk depends on table droppk1 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP TABLE droppk2; +ERROR: cannot drop table droppk2 because other objects depend on it +DETAIL: constraint dropfk_a_fkey on table dropfk depends on table droppk2 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP TABLE droppk21; +ERROR: cannot drop table droppk21 because other objects depend on it +DETAIL: constraint dropfk_a_fkey on table dropfk depends on table droppk21 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DELETE FROM dropfk; +-- dropping partitions is disallowed, even when no referencing values +DROP TABLE droppk_d; +ERROR: cannot drop table droppk_d because other objects depend on it +DETAIL: constraint dropfk_a_fkey on table dropfk depends on table droppk_d +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP TABLE droppk2_d; +ERROR: cannot drop table droppk2_d because other objects depend on it +DETAIL: constraint dropfk_a_fkey on table dropfk depends on table droppk2_d +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP TABLE droppk1; +ERROR: cannot drop table droppk1 because other objects depend on it +DETAIL: constraint dropfk_a_fkey on table dropfk depends on table droppk1 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- but DETACH is allowed, and DROP afterwards works +ALTER TABLE droppk2 DETACH PARTITION droppk21; +DROP TABLE droppk2; +ERROR: cannot drop table droppk2 because other objects depend on it +DETAIL: constraint dropfk_a_fkey on table dropfk depends on table droppk2 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- Verify that initial constraint creation and cloning behave correctly +CREATE SCHEMA fkpart5; +SET search_path TO fkpart5; +CREATE TABLE pk (a int PRIMARY KEY) PARTITION BY LIST (a); +CREATE TABLE pk1 PARTITION OF pk FOR VALUES IN (1) PARTITION BY LIST (a); +CREATE TABLE pk11 PARTITION OF pk1 FOR VALUES IN (1); +CREATE TABLE fk (a int) PARTITION BY LIST (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES IN (1) PARTITION BY LIST (a); +CREATE TABLE fk11 PARTITION OF fk1 FOR VALUES IN (1); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk; +CREATE TABLE pk2 PARTITION OF pk FOR VALUES IN (2); +CREATE TABLE pk3 (a int NOT NULL) PARTITION BY LIST (a); +CREATE TABLE pk31 PARTITION OF pk3 FOR VALUES IN (31); +CREATE TABLE pk32 (b int, a int NOT NULL); +ALTER TABLE pk32 DROP COLUMN b; +ALTER TABLE pk3 ATTACH PARTITION pk32 FOR VALUES IN (32); +ALTER TABLE pk ATTACH PARTITION pk3 FOR VALUES IN (31, 32); +NOTICE: table rewrite may lead to inconsistencies +DETAIL: Concurrent DMLs may not be reflected in the new table. +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19860. Set 'ysql_suppress_unsafe_alter_notice' yb-tserver gflag to true to suppress this notice. +NOTICE: table rewrite may lead to inconsistencies +DETAIL: Concurrent DMLs may not be reflected in the new table. +HINT: See https://github.com/yugabyte/yugabyte-db/issues/19860. Set 'ysql_suppress_unsafe_alter_notice' yb-tserver gflag to true to suppress this notice. +CREATE TABLE fk2 PARTITION OF fk FOR VALUES IN (2); +CREATE TABLE fk3 (b int, a int); +ALTER TABLE fk3 DROP COLUMN b; +ALTER TABLE fk ATTACH PARTITION fk3 FOR VALUES IN (3); +SELECT pg_describe_object('pg_constraint'::regclass, oid, 0), confrelid::regclass, + CASE WHEN conparentid <> 0 THEN pg_describe_object('pg_constraint'::regclass, conparentid, 0) ELSE 'TOP' END +FROM pg_catalog.pg_constraint +WHERE conrelid IN (SELECT relid FROM pg_partition_tree('fk')) +ORDER BY conrelid::regclass::text, conname; + pg_describe_object | confrelid | case +------------------------------------+-----------+----------------------------------- + constraint fk_a_fkey on table fk | pk | TOP + constraint fk_a_fkey1 on table fk | pk1 | constraint fk_a_fkey on table fk + constraint fk_a_fkey2 on table fk | pk11 | constraint fk_a_fkey1 on table fk + constraint fk_a_fkey3 on table fk | pk2 | constraint fk_a_fkey on table fk + constraint fk_a_fkey4 on table fk | pk3 | constraint fk_a_fkey on table fk + constraint fk_a_fkey5 on table fk | pk31 | constraint fk_a_fkey4 on table fk + constraint fk_a_fkey6 on table fk | pk32 | constraint fk_a_fkey4 on table fk + constraint fk_a_fkey on table fk1 | pk | constraint fk_a_fkey on table fk + constraint fk_a_fkey on table fk11 | pk | constraint fk_a_fkey on table fk1 + constraint fk_a_fkey on table fk2 | pk | constraint fk_a_fkey on table fk + constraint fk_a_fkey on table fk3 | pk | constraint fk_a_fkey on table fk +(11 rows) + +CREATE TABLE fk4 (LIKE fk); +INSERT INTO fk4 VALUES (50); +ALTER TABLE fk ATTACH PARTITION fk4 FOR VALUES IN (50); +ERROR: insert or update on table "fk4" violates foreign key constraint "fk_a_fkey" +DETAIL: Key (a)=(50) is not present in table "pk". +-- Verify constraint deferrability +CREATE SCHEMA fkpart9; +SET search_path TO fkpart9; +CREATE TABLE pk (a int PRIMARY KEY) PARTITION BY LIST (a); +CREATE TABLE pk1 PARTITION OF pk FOR VALUES IN (1, 2) PARTITION BY LIST (a); +CREATE TABLE pk11 PARTITION OF pk1 FOR VALUES IN (1); +CREATE TABLE pk3 PARTITION OF pk FOR VALUES IN (3); +CREATE TABLE fk (a int REFERENCES pk DEFERRABLE INITIALLY IMMEDIATE); +INSERT INTO fk VALUES (1); -- should fail +ERROR: insert or update on table "fk" violates foreign key constraint "fk_a_fkey" +DETAIL: Key (a)=(1) is not present in table "pk". +BEGIN; +SET CONSTRAINTS fk_a_fkey DEFERRED; +INSERT INTO fk VALUES (1); +COMMIT; -- should fail +ERROR: insert or update on table "fk" violates foreign key constraint "fk_a_fkey" +DETAIL: Key (a)=(1) is not present in table "pk". +BEGIN; +SET CONSTRAINTS fk_a_fkey DEFERRED; +INSERT INTO fk VALUES (1); +INSERT INTO pk VALUES (1); +COMMIT; -- OK +BEGIN; +SET CONSTRAINTS fk_a_fkey DEFERRED; +DELETE FROM pk WHERE a = 1; +DELETE FROM fk WHERE a = 1; +COMMIT; -- OK +-- Verify constraint deferrability when changed by ALTER +-- Partitioned table at referencing end +CREATE TABLE pt(f1 int, f2 int, f3 int, PRIMARY KEY(f1,f2)); +CREATE TABLE ref(f1 int, f2 int, f3 int) + PARTITION BY list(f1); +CREATE TABLE ref1 PARTITION OF ref FOR VALUES IN (1); +CREATE TABLE ref2 PARTITION OF ref FOR VALUES in (2); +ALTER TABLE ref ADD FOREIGN KEY(f1,f2) REFERENCES pt; +ALTER TABLE ref ALTER CONSTRAINT ref_f1_f2_fkey + DEFERRABLE INITIALLY DEFERRED; +ERROR: ALTER TABLE ALTER CONSTRAINT not supported yet +LINE 1: ALTER TABLE ref ALTER CONSTRAINT ref_f1_f2_fkey + ^ +HINT: See https://github.com/yugabyte/yugabyte-db/issues/1124. React with thumbs up to raise its priority +-- YB note: Port additional tests once ALTER TABLE .. ALTER CONSTRAINT is supported. +-- Verify ON UPDATE/DELETE behavior +CREATE SCHEMA fkpart6; +SET search_path TO fkpart6; +CREATE TABLE pk (a int PRIMARY KEY) PARTITION BY RANGE (a); +CREATE TABLE pk1 PARTITION OF pk FOR VALUES FROM (1) TO (100) PARTITION BY RANGE (a); +CREATE TABLE pk11 PARTITION OF pk1 FOR VALUES FROM (1) TO (50); +CREATE TABLE pk12 PARTITION OF pk1 FOR VALUES FROM (50) TO (100); +CREATE TABLE fk (a int) PARTITION BY RANGE (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES FROM (1) TO (100) PARTITION BY RANGE (a); +CREATE TABLE fk11 PARTITION OF fk1 FOR VALUES FROM (1) TO (10); +CREATE TABLE fk12 PARTITION OF fk1 FOR VALUES FROM (10) TO (100); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk ON UPDATE CASCADE ON DELETE CASCADE; +CREATE TABLE fk_d PARTITION OF fk DEFAULT; +INSERT INTO pk VALUES (1); +INSERT INTO fk VALUES (1); +UPDATE pk SET a = 20; +SELECT tableoid::regclass, * FROM fk; + tableoid | a +----------+---- + fk12 | 20 +(1 row) + +DELETE FROM pk WHERE a = 20; +SELECT tableoid::regclass, * FROM fk; + tableoid | a +----------+--- +(0 rows) + +DROP TABLE fk; +TRUNCATE TABLE pk; +INSERT INTO pk VALUES (20), (50); +CREATE TABLE fk (a int) PARTITION BY RANGE (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES FROM (1) TO (100) PARTITION BY RANGE (a); +CREATE TABLE fk11 PARTITION OF fk1 FOR VALUES FROM (1) TO (10); +CREATE TABLE fk12 PARTITION OF fk1 FOR VALUES FROM (10) TO (100); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk ON UPDATE SET NULL ON DELETE SET NULL; +CREATE TABLE fk_d PARTITION OF fk DEFAULT; +INSERT INTO fk VALUES (20), (50); +UPDATE pk SET a = 21 WHERE a = 20; +DELETE FROM pk WHERE a = 50; +SELECT tableoid::regclass, * FROM fk; + tableoid | a +----------+--- + fk_d | + fk_d | +(2 rows) + +DROP TABLE fk; +TRUNCATE TABLE pk; +INSERT INTO pk VALUES (20), (30), (50); +CREATE TABLE fk (id int, a int DEFAULT 50) PARTITION BY RANGE (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES FROM (1) TO (100) PARTITION BY RANGE (a); +CREATE TABLE fk11 PARTITION OF fk1 FOR VALUES FROM (1) TO (10); +CREATE TABLE fk12 PARTITION OF fk1 FOR VALUES FROM (10) TO (100); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk ON UPDATE SET DEFAULT ON DELETE SET DEFAULT; +CREATE TABLE fk_d PARTITION OF fk DEFAULT; +INSERT INTO fk VALUES (1, 20), (2, 30); +DELETE FROM pk WHERE a = 20 RETURNING *; + a +---- + 20 +(1 row) + +UPDATE pk SET a = 90 WHERE a = 30 RETURNING *; + a +---- + 90 +(1 row) + +SELECT tableoid::regclass, * FROM fk; + tableoid | id | a +----------+----+---- + fk12 | 1 | 50 + fk12 | 2 | 50 +(2 rows) + +DROP TABLE fk; +TRUNCATE TABLE pk; +INSERT INTO pk VALUES (20), (30); +CREATE TABLE fk (a int DEFAULT 50) PARTITION BY RANGE (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES FROM (1) TO (100) PARTITION BY RANGE (a); +CREATE TABLE fk11 PARTITION OF fk1 FOR VALUES FROM (1) TO (10); +CREATE TABLE fk12 PARTITION OF fk1 FOR VALUES FROM (10) TO (100); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk ON UPDATE RESTRICT ON DELETE RESTRICT; +CREATE TABLE fk_d PARTITION OF fk DEFAULT; +INSERT INTO fk VALUES (20), (30); +DELETE FROM pk WHERE a = 20; +ERROR: update or delete on table "pk11" violates foreign key constraint "fk_a_fkey2" on table "fk" +DETAIL: Key (a)=(20) is still referenced from table "fk". +UPDATE pk SET a = 90 WHERE a = 30; +ERROR: update or delete on table "pk" violates foreign key constraint "fk_a_fkey" on table "fk" +DETAIL: Key (a)=(30) is still referenced from table "fk". +SELECT tableoid::regclass, * FROM fk ORDER BY a; -- YB note: ordering + tableoid | a +----------+---- + fk12 | 20 + fk12 | 30 +(2 rows) + +DROP TABLE fk; \ No newline at end of file diff --git a/src/postgres/src/test/regress/sql/yb_pg_foreign_key.sql b/src/postgres/src/test/regress/sql/yb_pg_foreign_key.sql index e59481e16137..cda12ac20e46 100644 --- a/src/postgres/src/test/regress/sql/yb_pg_foreign_key.sql +++ b/src/postgres/src/test/regress/sql/yb_pg_foreign_key.sql @@ -1140,18 +1140,6 @@ drop table pktable2, fktable2; -- Foreign keys and partitioned tables -- --- partitioned table in the referenced side are not allowed -CREATE TABLE fk_partitioned_pk (a int, b int, primary key (a, b)) - PARTITION BY RANGE (a, b); --- verify with create table first ... -CREATE TABLE fk_notpartitioned_fk (a int, b int, - FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk); --- and then with alter table. -CREATE TABLE fk_notpartitioned_fk_2 (a int, b int); -ALTER TABLE fk_notpartitioned_fk_2 ADD FOREIGN KEY (a, b) - REFERENCES fk_partitioned_pk; -DROP TABLE fk_partitioned_pk, fk_notpartitioned_fk_2; - -- Creation of a partitioned hierarchy with irregular definitions CREATE TABLE fk_notpartitioned_pk (fdrop1 int, a int, fdrop2 int, b int, PRIMARY KEY (a, b)); @@ -1434,3 +1422,240 @@ drop schema fkpart0, fkpart1, fkpart2 cascade; */ -- TODO(jason): remove when issue #1721 is closed or closing. DISCARD TEMP; + +-- Test a partitioned table as referenced table. + +-- Verify basic functionality with a regular partition creation and a partition +-- with a different column layout, as well as partitions added (created and +-- attached) after creating the foreign key. +CREATE SCHEMA fkpart3; +SET search_path TO fkpart3; + +CREATE TABLE pk (a int PRIMARY KEY) PARTITION BY RANGE (a); +CREATE TABLE pk1 PARTITION OF pk FOR VALUES FROM (0) TO (1000); +CREATE TABLE pk2 (b int, a int); +ALTER TABLE pk2 DROP COLUMN b; +ALTER TABLE pk2 ALTER a SET NOT NULL; +ALTER TABLE pk ATTACH PARTITION pk2 FOR VALUES FROM (1000) TO (2000); + +CREATE TABLE fk (a int) PARTITION BY RANGE (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES FROM (0) TO (750); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk; +CREATE TABLE fk2 (b int, a int) ; +ALTER TABLE fk2 DROP COLUMN b; +ALTER TABLE fk ATTACH PARTITION fk2 FOR VALUES FROM (750) TO (3500); + +CREATE TABLE pk3 PARTITION OF pk FOR VALUES FROM (2000) TO (3000); +CREATE TABLE pk4 (LIKE pk); +ALTER TABLE pk ATTACH PARTITION pk4 FOR VALUES FROM (3000) TO (4000); + +CREATE TABLE pk5 (c int, b int, a int NOT NULL) PARTITION BY RANGE (a); +ALTER TABLE pk5 DROP COLUMN b, DROP COLUMN c; +CREATE TABLE pk51 PARTITION OF pk5 FOR VALUES FROM (4000) TO (4500); +CREATE TABLE pk52 PARTITION OF pk5 FOR VALUES FROM (4500) TO (5000); +ALTER TABLE pk ATTACH PARTITION pk5 FOR VALUES FROM (4000) TO (5000); + +CREATE TABLE fk3 PARTITION OF fk FOR VALUES FROM (3500) TO (5000); + +-- these should fail: referenced value not present +INSERT into fk VALUES (1); +INSERT into fk VALUES (1000); +INSERT into fk VALUES (2000); +INSERT into fk VALUES (3000); +INSERT into fk VALUES (4000); +INSERT into fk VALUES (4500); +-- insert into the referenced table, now they should work +INSERT into pk VALUES (1), (1000), (2000), (3000), (4000), (4500); +INSERT into fk VALUES (1), (1000), (2000), (3000), (4000), (4500); + +-- should fail: referencing value present +DELETE FROM pk WHERE a = 1; +DELETE FROM pk WHERE a = 1000; +DELETE FROM pk WHERE a = 2000; +DELETE FROM pk WHERE a = 3000; +DELETE FROM pk WHERE a = 4000; +DELETE FROM pk WHERE a = 4500; +UPDATE pk SET a = 2 WHERE a = 1; +UPDATE pk SET a = 1002 WHERE a = 1000; +UPDATE pk SET a = 2002 WHERE a = 2000; +UPDATE pk SET a = 3002 WHERE a = 3000; +UPDATE pk SET a = 4002 WHERE a = 4000; +UPDATE pk SET a = 4502 WHERE a = 4500; +-- now they should work +DELETE FROM fk; +UPDATE pk SET a = 2 WHERE a = 1; +DELETE FROM pk WHERE a = 2; +UPDATE pk SET a = 1002 WHERE a = 1000; +DELETE FROM pk WHERE a = 1002; +UPDATE pk SET a = 2002 WHERE a = 2000; +DELETE FROM pk WHERE a = 2002; +UPDATE pk SET a = 3002 WHERE a = 3000; +DELETE FROM pk WHERE a = 3002; +UPDATE pk SET a = 4002 WHERE a = 4000; +DELETE FROM pk WHERE a = 4002; +UPDATE pk SET a = 4502 WHERE a = 4500; +DELETE FROM pk WHERE a = 4502; + +CREATE SCHEMA fkpart4; +SET search_path TO fkpart4; +-- dropping/detaching PARTITIONs is prevented if that would break +-- a foreign key's existing data +CREATE TABLE droppk (a int PRIMARY KEY) PARTITION BY RANGE (a); +CREATE TABLE droppk1 PARTITION OF droppk FOR VALUES FROM (0) TO (1000); +CREATE TABLE droppk_d PARTITION OF droppk DEFAULT; +CREATE TABLE droppk2 PARTITION OF droppk FOR VALUES FROM (1000) TO (2000) + PARTITION BY RANGE (a); +CREATE TABLE droppk21 PARTITION OF droppk2 FOR VALUES FROM (1000) TO (1400); +CREATE TABLE droppk2_d PARTITION OF droppk2 DEFAULT; +INSERT into droppk VALUES (1), (1000), (1500), (2000); +CREATE TABLE dropfk (a int REFERENCES droppk); +INSERT into dropfk VALUES (1), (1000), (1500), (2000); +-- these should all fail +ALTER TABLE droppk DETACH PARTITION droppk_d; +ALTER TABLE droppk2 DETACH PARTITION droppk2_d; +ALTER TABLE droppk DETACH PARTITION droppk1; +ALTER TABLE droppk DETACH PARTITION droppk2; +ALTER TABLE droppk2 DETACH PARTITION droppk21; +-- dropping partitions is disallowed +DROP TABLE droppk_d; +DROP TABLE droppk2_d; +DROP TABLE droppk1; +DROP TABLE droppk2; +DROP TABLE droppk21; +DELETE FROM dropfk; +-- dropping partitions is disallowed, even when no referencing values +DROP TABLE droppk_d; +DROP TABLE droppk2_d; +DROP TABLE droppk1; +-- but DETACH is allowed, and DROP afterwards works +ALTER TABLE droppk2 DETACH PARTITION droppk21; +DROP TABLE droppk2; + +-- Verify that initial constraint creation and cloning behave correctly +CREATE SCHEMA fkpart5; +SET search_path TO fkpart5; +CREATE TABLE pk (a int PRIMARY KEY) PARTITION BY LIST (a); +CREATE TABLE pk1 PARTITION OF pk FOR VALUES IN (1) PARTITION BY LIST (a); +CREATE TABLE pk11 PARTITION OF pk1 FOR VALUES IN (1); +CREATE TABLE fk (a int) PARTITION BY LIST (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES IN (1) PARTITION BY LIST (a); +CREATE TABLE fk11 PARTITION OF fk1 FOR VALUES IN (1); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk; +CREATE TABLE pk2 PARTITION OF pk FOR VALUES IN (2); +CREATE TABLE pk3 (a int NOT NULL) PARTITION BY LIST (a); +CREATE TABLE pk31 PARTITION OF pk3 FOR VALUES IN (31); +CREATE TABLE pk32 (b int, a int NOT NULL); +ALTER TABLE pk32 DROP COLUMN b; +ALTER TABLE pk3 ATTACH PARTITION pk32 FOR VALUES IN (32); +ALTER TABLE pk ATTACH PARTITION pk3 FOR VALUES IN (31, 32); +CREATE TABLE fk2 PARTITION OF fk FOR VALUES IN (2); +CREATE TABLE fk3 (b int, a int); +ALTER TABLE fk3 DROP COLUMN b; +ALTER TABLE fk ATTACH PARTITION fk3 FOR VALUES IN (3); +SELECT pg_describe_object('pg_constraint'::regclass, oid, 0), confrelid::regclass, + CASE WHEN conparentid <> 0 THEN pg_describe_object('pg_constraint'::regclass, conparentid, 0) ELSE 'TOP' END +FROM pg_catalog.pg_constraint +WHERE conrelid IN (SELECT relid FROM pg_partition_tree('fk')) +ORDER BY conrelid::regclass::text, conname; +CREATE TABLE fk4 (LIKE fk); +INSERT INTO fk4 VALUES (50); +ALTER TABLE fk ATTACH PARTITION fk4 FOR VALUES IN (50); + +-- Verify constraint deferrability +CREATE SCHEMA fkpart9; +SET search_path TO fkpart9; +CREATE TABLE pk (a int PRIMARY KEY) PARTITION BY LIST (a); +CREATE TABLE pk1 PARTITION OF pk FOR VALUES IN (1, 2) PARTITION BY LIST (a); +CREATE TABLE pk11 PARTITION OF pk1 FOR VALUES IN (1); +CREATE TABLE pk3 PARTITION OF pk FOR VALUES IN (3); +CREATE TABLE fk (a int REFERENCES pk DEFERRABLE INITIALLY IMMEDIATE); +INSERT INTO fk VALUES (1); -- should fail +BEGIN; +SET CONSTRAINTS fk_a_fkey DEFERRED; +INSERT INTO fk VALUES (1); +COMMIT; -- should fail +BEGIN; +SET CONSTRAINTS fk_a_fkey DEFERRED; +INSERT INTO fk VALUES (1); +INSERT INTO pk VALUES (1); +COMMIT; -- OK +BEGIN; +SET CONSTRAINTS fk_a_fkey DEFERRED; +DELETE FROM pk WHERE a = 1; +DELETE FROM fk WHERE a = 1; +COMMIT; -- OK + +-- Verify constraint deferrability when changed by ALTER +-- Partitioned table at referencing end +CREATE TABLE pt(f1 int, f2 int, f3 int, PRIMARY KEY(f1,f2)); +CREATE TABLE ref(f1 int, f2 int, f3 int) + PARTITION BY list(f1); +CREATE TABLE ref1 PARTITION OF ref FOR VALUES IN (1); +CREATE TABLE ref2 PARTITION OF ref FOR VALUES in (2); +ALTER TABLE ref ADD FOREIGN KEY(f1,f2) REFERENCES pt; +ALTER TABLE ref ALTER CONSTRAINT ref_f1_f2_fkey + DEFERRABLE INITIALLY DEFERRED; +-- YB note: Port additional tests once ALTER TABLE .. ALTER CONSTRAINT is supported. + +-- Verify ON UPDATE/DELETE behavior +CREATE SCHEMA fkpart6; +SET search_path TO fkpart6; +CREATE TABLE pk (a int PRIMARY KEY) PARTITION BY RANGE (a); +CREATE TABLE pk1 PARTITION OF pk FOR VALUES FROM (1) TO (100) PARTITION BY RANGE (a); +CREATE TABLE pk11 PARTITION OF pk1 FOR VALUES FROM (1) TO (50); +CREATE TABLE pk12 PARTITION OF pk1 FOR VALUES FROM (50) TO (100); +CREATE TABLE fk (a int) PARTITION BY RANGE (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES FROM (1) TO (100) PARTITION BY RANGE (a); +CREATE TABLE fk11 PARTITION OF fk1 FOR VALUES FROM (1) TO (10); +CREATE TABLE fk12 PARTITION OF fk1 FOR VALUES FROM (10) TO (100); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk ON UPDATE CASCADE ON DELETE CASCADE; +CREATE TABLE fk_d PARTITION OF fk DEFAULT; +INSERT INTO pk VALUES (1); +INSERT INTO fk VALUES (1); +UPDATE pk SET a = 20; +SELECT tableoid::regclass, * FROM fk; +DELETE FROM pk WHERE a = 20; +SELECT tableoid::regclass, * FROM fk; +DROP TABLE fk; + +TRUNCATE TABLE pk; +INSERT INTO pk VALUES (20), (50); +CREATE TABLE fk (a int) PARTITION BY RANGE (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES FROM (1) TO (100) PARTITION BY RANGE (a); +CREATE TABLE fk11 PARTITION OF fk1 FOR VALUES FROM (1) TO (10); +CREATE TABLE fk12 PARTITION OF fk1 FOR VALUES FROM (10) TO (100); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk ON UPDATE SET NULL ON DELETE SET NULL; +CREATE TABLE fk_d PARTITION OF fk DEFAULT; +INSERT INTO fk VALUES (20), (50); +UPDATE pk SET a = 21 WHERE a = 20; +DELETE FROM pk WHERE a = 50; +SELECT tableoid::regclass, * FROM fk; +DROP TABLE fk; + +TRUNCATE TABLE pk; +INSERT INTO pk VALUES (20), (30), (50); +CREATE TABLE fk (id int, a int DEFAULT 50) PARTITION BY RANGE (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES FROM (1) TO (100) PARTITION BY RANGE (a); +CREATE TABLE fk11 PARTITION OF fk1 FOR VALUES FROM (1) TO (10); +CREATE TABLE fk12 PARTITION OF fk1 FOR VALUES FROM (10) TO (100); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk ON UPDATE SET DEFAULT ON DELETE SET DEFAULT; +CREATE TABLE fk_d PARTITION OF fk DEFAULT; +INSERT INTO fk VALUES (1, 20), (2, 30); +DELETE FROM pk WHERE a = 20 RETURNING *; +UPDATE pk SET a = 90 WHERE a = 30 RETURNING *; +SELECT tableoid::regclass, * FROM fk; +DROP TABLE fk; + +TRUNCATE TABLE pk; +INSERT INTO pk VALUES (20), (30); +CREATE TABLE fk (a int DEFAULT 50) PARTITION BY RANGE (a); +CREATE TABLE fk1 PARTITION OF fk FOR VALUES FROM (1) TO (100) PARTITION BY RANGE (a); +CREATE TABLE fk11 PARTITION OF fk1 FOR VALUES FROM (1) TO (10); +CREATE TABLE fk12 PARTITION OF fk1 FOR VALUES FROM (10) TO (100); +ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk ON UPDATE RESTRICT ON DELETE RESTRICT; +CREATE TABLE fk_d PARTITION OF fk DEFAULT; +INSERT INTO fk VALUES (20), (30); +DELETE FROM pk WHERE a = 20; +UPDATE pk SET a = 90 WHERE a = 30; +SELECT tableoid::regclass, * FROM fk ORDER BY a; -- YB note: ordering +DROP TABLE fk; \ No newline at end of file From c6f12b8a69b25c3bcf6a100c46c24714b995ac01 Mon Sep 17 00:00:00 2001 From: Arpan Agrawal Date: Tue, 10 Sep 2024 19:43:04 +0530 Subject: [PATCH 03/13] newline --- src/postgres/src/test/regress/expected/yb_pg_foreign_key.out | 2 +- src/postgres/src/test/regress/sql/yb_pg_foreign_key.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/postgres/src/test/regress/expected/yb_pg_foreign_key.out b/src/postgres/src/test/regress/expected/yb_pg_foreign_key.out index ee1e23f39a34..93696dd88cb1 100644 --- a/src/postgres/src/test/regress/expected/yb_pg_foreign_key.out +++ b/src/postgres/src/test/regress/expected/yb_pg_foreign_key.out @@ -2296,4 +2296,4 @@ SELECT tableoid::regclass, * FROM fk ORDER BY a; -- YB note: ordering fk12 | 30 (2 rows) -DROP TABLE fk; \ No newline at end of file +DROP TABLE fk; diff --git a/src/postgres/src/test/regress/sql/yb_pg_foreign_key.sql b/src/postgres/src/test/regress/sql/yb_pg_foreign_key.sql index cda12ac20e46..6107b344967b 100644 --- a/src/postgres/src/test/regress/sql/yb_pg_foreign_key.sql +++ b/src/postgres/src/test/regress/sql/yb_pg_foreign_key.sql @@ -1658,4 +1658,4 @@ INSERT INTO fk VALUES (20), (30); DELETE FROM pk WHERE a = 20; UPDATE pk SET a = 90 WHERE a = 30; SELECT tableoid::regclass, * FROM fk ORDER BY a; -- YB note: ordering -DROP TABLE fk; \ No newline at end of file +DROP TABLE fk; From 86a483903ef4272062693b37fd94d229a16a5ca2 Mon Sep 17 00:00:00 2001 From: Arpan Agrawal Date: Wed, 18 Sep 2024 19:20:16 +0530 Subject: [PATCH 04/13] Use BasePgRegressTestPorted --- .../src/test/java/org/yb/pgsql/TestPgRegressForeignKey.java | 2 +- src/postgres/src/test/regress/expected/yb_pg_foreign_key.out | 4 ++-- src/postgres/src/test/regress/sql/yb_pg_foreign_key.sql | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgRegressForeignKey.java b/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgRegressForeignKey.java index 26685612acfc..68f45ddfbd3b 100644 --- a/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgRegressForeignKey.java +++ b/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgRegressForeignKey.java @@ -18,7 +18,7 @@ import org.yb.YBTestRunner; @RunWith(value=YBTestRunner.class) -public class TestPgRegressForeignKey extends BasePgRegressTest { +public class TestPgRegressForeignKey extends BasePgRegressTestPorted { @Override protected Map getTServerFlags() { Map flagMap = super.getTServerFlags(); diff --git a/src/postgres/src/test/regress/expected/yb_pg_foreign_key.out b/src/postgres/src/test/regress/expected/yb_pg_foreign_key.out index 93696dd88cb1..869ef97b2ca8 100644 --- a/src/postgres/src/test/regress/expected/yb_pg_foreign_key.out +++ b/src/postgres/src/test/regress/expected/yb_pg_foreign_key.out @@ -1589,7 +1589,7 @@ DETAIL: Key (a, b)=(2500, 2502) is still referenced from table "fk_partitioned_ a | integer | | not null | b | integer | | not null | Indexes: - "fk_notpartitioned_pk_pkey" PRIMARY KEY, lsm (a HASH, b ASC) + "fk_notpartitioned_pk_pkey" PRIMARY KEY, lsm (a ASC, b ASC) Referenced by: TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) @@ -2289,7 +2289,7 @@ DETAIL: Key (a)=(20) is still referenced from table "fk". UPDATE pk SET a = 90 WHERE a = 30; ERROR: update or delete on table "pk" violates foreign key constraint "fk_a_fkey" on table "fk" DETAIL: Key (a)=(30) is still referenced from table "fk". -SELECT tableoid::regclass, * FROM fk ORDER BY a; -- YB note: ordering +SELECT tableoid::regclass, * FROM fk; tableoid | a ----------+---- fk12 | 20 diff --git a/src/postgres/src/test/regress/sql/yb_pg_foreign_key.sql b/src/postgres/src/test/regress/sql/yb_pg_foreign_key.sql index 6107b344967b..39430b54ac9d 100644 --- a/src/postgres/src/test/regress/sql/yb_pg_foreign_key.sql +++ b/src/postgres/src/test/regress/sql/yb_pg_foreign_key.sql @@ -1657,5 +1657,5 @@ CREATE TABLE fk_d PARTITION OF fk DEFAULT; INSERT INTO fk VALUES (20), (30); DELETE FROM pk WHERE a = 20; UPDATE pk SET a = 90 WHERE a = 30; -SELECT tableoid::regclass, * FROM fk ORDER BY a; -- YB note: ordering +SELECT tableoid::regclass, * FROM fk; DROP TABLE fk; From e910393e4e7fb03ec1194d57aa87790bc043a27a Mon Sep 17 00:00:00 2001 From: Arpan Agrawal Date: Thu, 26 Sep 2024 16:15:46 +0530 Subject: [PATCH 05/13] POC --- src/postgres/src/backend/commands/tablecmds.c | 7 +- src/postgres/src/backend/commands/trigger.c | 22 +++- src/postgres/src/backend/executor/execMain.c | 1 + .../src/backend/executor/execPartition.c | 122 ++++++++++++++++++ src/postgres/src/backend/executor/execUtils.c | 2 +- .../src/backend/utils/adt/ri_triggers.c | 85 ++++++++++-- src/postgres/src/include/commands/trigger.h | 3 +- .../src/include/executor/execPartition.h | 4 + 8 files changed, 234 insertions(+), 12 deletions(-) diff --git a/src/postgres/src/backend/commands/tablecmds.c b/src/postgres/src/backend/commands/tablecmds.c index 13b9ee0f37b8..9c2039c11e5a 100644 --- a/src/postgres/src/backend/commands/tablecmds.c +++ b/src/postgres/src/backend/commands/tablecmds.c @@ -12225,6 +12225,7 @@ typedef struct YbFKTriggerScanDescData int buffered_tuples_size; int current_tuple_idx; bool all_tuples_processed; + EState* estate; TupleTableSlot* buffered_tuples[]; } YbFKTriggerScanDescData; @@ -12271,7 +12272,7 @@ YbGetNext(YbFKTriggerScanDesc desc, TupleTableSlot *slot) ExecDropSingleTupleTableSlot(new_slot); break; } - YbAddTriggerFKReferenceIntent(desc->trigger, desc->fk_rel, new_slot); + YbAddTriggerFKReferenceIntent(desc->trigger, desc->fk_rel, new_slot, desc->estate); desc->buffered_tuples[desc->buffered_tuples_size++] = new_slot; } } @@ -12310,6 +12311,8 @@ YbFKTriggerScanBegin(TableScanDesc scan, &YbFKTriggerScanVTableIsYugaByteEnabled : &YbFKTriggerScanVTableNotYugaByteEnabled; descr->per_batch_cxt = per_batch_cxt; + descr->estate = CreateExecutorState(); + elog(INFO, "descr->estate %p", descr->estate); return descr; } @@ -12400,6 +12403,7 @@ validateForeignKeyConstraint(char *conname, { LOCAL_FCINFO(fcinfo, 0); TriggerData trigdata = {0}; + // elog(INFO, "TriggerData in validateForeignKeyConstraint"); CHECK_FOR_INTERRUPTS(); @@ -12433,6 +12437,7 @@ validateForeignKeyConstraint(char *conname, MemoryContextSwitchTo(oldcxt); MemoryContextDelete(perTupCxt); table_endscan(scan); + FreeExecutorState(fk_scan->estate); pfree(fk_scan); UnregisterSnapshot(snapshot); if (!IsYBRelation(rel)) diff --git a/src/postgres/src/backend/commands/trigger.c b/src/postgres/src/backend/commands/trigger.c index 6611ba1932fa..390ddcaa07e4 100644 --- a/src/postgres/src/backend/commands/trigger.c +++ b/src/postgres/src/backend/commands/trigger.c @@ -2453,6 +2453,7 @@ ExecCallTriggerFunc(TriggerData *trigdata, void ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo) { + // elog(INFO, "ExecBSInsertTriggers"); TriggerDesc *trigdesc; int i; TriggerData LocTriggerData = {0}; @@ -2473,6 +2474,7 @@ ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo) LocTriggerData.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_BEFORE; LocTriggerData.tg_relation = relinfo->ri_RelationDesc; + LocTriggerData.estate = estate; for (i = 0; i < trigdesc->numtriggers; i++) { Trigger *trigger = &trigdesc->triggers[i]; @@ -2518,6 +2520,7 @@ bool ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo, TupleTableSlot *slot) { + //elog(INFO, "ExecBRInsertTriggers"); TriggerDesc *trigdesc = relinfo->ri_TrigDesc; HeapTuple newtuple = NULL; bool should_free; @@ -2529,6 +2532,7 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo, TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE; LocTriggerData.tg_relation = relinfo->ri_RelationDesc; + LocTriggerData.estate = estate; for (i = 0; i < trigdesc->numtriggers; i++) { Trigger *trigger = &trigdesc->triggers[i]; @@ -2611,6 +2615,7 @@ bool ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo, TupleTableSlot *slot) { + //elog(INFO, "ExecIRInsertTriggers"); TriggerDesc *trigdesc = relinfo->ri_TrigDesc; HeapTuple newtuple = NULL; bool should_free; @@ -2622,6 +2627,7 @@ ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo, TRIGGER_EVENT_ROW | TRIGGER_EVENT_INSTEAD; LocTriggerData.tg_relation = relinfo->ri_RelationDesc; + LocTriggerData.estate = estate; for (i = 0; i < trigdesc->numtriggers; i++) { Trigger *trigger = &trigdesc->triggers[i]; @@ -2691,6 +2697,7 @@ ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo) LocTriggerData.tg_event = TRIGGER_EVENT_DELETE | TRIGGER_EVENT_BEFORE; LocTriggerData.tg_relation = relinfo->ri_RelationDesc; + LocTriggerData.estate = estate; for (i = 0; i < trigdesc->numtriggers; i++) { Trigger *trigger = &trigdesc->triggers[i]; @@ -2788,6 +2795,7 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate, TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE; LocTriggerData.tg_relation = relinfo->ri_RelationDesc; + LocTriggerData.estate = estate; for (i = 0; i < trigdesc->numtriggers; i++) { HeapTuple newtuple; @@ -2878,6 +2886,7 @@ ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo, TRIGGER_EVENT_ROW | TRIGGER_EVENT_INSTEAD; LocTriggerData.tg_relation = relinfo->ri_RelationDesc; + LocTriggerData.estate = estate; ExecForceStoreHeapTuple(trigtuple, slot, false); @@ -2940,6 +2949,7 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo) LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE | TRIGGER_EVENT_BEFORE; LocTriggerData.tg_relation = relinfo->ri_RelationDesc; + LocTriggerData.estate = estate; LocTriggerData.tg_updatedcols = updatedCols; for (i = 0; i < trigdesc->numtriggers; i++) { @@ -3057,6 +3067,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE; LocTriggerData.tg_relation = relinfo->ri_RelationDesc; + LocTriggerData.estate = estate; updatedCols = ExecGetAllUpdatedCols(relinfo, estate); LocTriggerData.tg_updatedcols = updatedCols; for (i = 0; i < trigdesc->numtriggers; i++) @@ -3205,6 +3216,7 @@ ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo, TRIGGER_EVENT_ROW | TRIGGER_EVENT_INSTEAD; LocTriggerData.tg_relation = relinfo->ri_RelationDesc; + LocTriggerData.estate = estate; ExecForceStoreHeapTuple(trigtuple, oldslot, false); @@ -3273,6 +3285,7 @@ ExecBSTruncateTriggers(EState *estate, ResultRelInfo *relinfo) LocTriggerData.tg_event = TRIGGER_EVENT_TRUNCATE | TRIGGER_EVENT_BEFORE; LocTriggerData.tg_relation = relinfo->ri_RelationDesc; + LocTriggerData.estate = estate; for (i = 0; i < trigdesc->numtriggers; i++) { @@ -4510,6 +4523,8 @@ AfterTriggerExecute(EState *estate, LocTriggerData.tg_event = evtshared->ats_event & (TRIGGER_EVENT_OPMASK | TRIGGER_EVENT_ROW); LocTriggerData.tg_relation = rel; + LocTriggerData.estate = estate; + if (TRIGGER_FOR_UPDATE(LocTriggerData.tg_trigger->tgtype)) LocTriggerData.tg_updatedcols = evtshared->ats_modifiedcols; @@ -6126,6 +6141,11 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, bool is_crosspart_update) { Relation rel = relinfo->ri_RelationDesc; + // Relation srcrel = NULL; + // if (src_partinfo) + // srcrel = src_partinfo->ri_RelationDesc; + // elog(INFO, "AfterTriggerSaveEvent rel: %s partrel: %s, src_partinfo: %p", RelationGetRelationName(rel), srcrel ? RelationGetRelationName(rel) : "NULL", src_partinfo); + TriggerDesc *trigdesc = relinfo->ri_TrigDesc; AfterTriggerEventData new_event; AfterTriggerSharedData new_shared; @@ -6547,7 +6567,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, } if (IsYBBackedRelation(rel) && RI_FKey_trigger_type(trigger->tgfoid) == RI_TRIGGER_FK) - YbAddTriggerFKReferenceIntent(trigger, rel, newslot); + YbAddTriggerFKReferenceIntent(trigger, rel, newslot, estate); afterTriggerAddEvent(&afterTriggers.query_stack[afterTriggers.query_depth].events, &new_event, &new_shared); diff --git a/src/postgres/src/backend/executor/execMain.c b/src/postgres/src/backend/executor/execMain.c index ae64a3226fdd..034efd9ae800 100644 --- a/src/postgres/src/backend/executor/execMain.c +++ b/src/postgres/src/backend/executor/execMain.c @@ -1832,6 +1832,7 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, List *qual = RelationGetPartitionQual(resultRelInfo->ri_RelationDesc); resultRelInfo->ri_PartitionCheckExpr = ExecPrepareCheck(qual, estate); + // elog(INFO, "resultRelInfo->ri_PartitionCheckExpr was null, now updated to %p", resultRelInfo->ri_PartitionCheckExpr); MemoryContextSwitchTo(oldcxt); } diff --git a/src/postgres/src/backend/executor/execPartition.c b/src/postgres/src/backend/executor/execPartition.c index cb3cd8dc726e..5699cc0e280f 100644 --- a/src/postgres/src/backend/executor/execPartition.c +++ b/src/postgres/src/backend/executor/execPartition.c @@ -489,6 +489,120 @@ ExecFindPartition(ModifyTableState *mtstate, return rri; } +Oid +FindLeafPartitionOid(ResultRelInfo *rootResultRelInfo, + PartitionTupleRouting *proute, TupleTableSlot *slot, + EState *estate) +{ + PartitionDispatch *pd = proute->partition_dispatch_info; + Datum values[PARTITION_MAX_KEYS]; + bool isnull[PARTITION_MAX_KEYS]; + // Relation rel; + PartitionDispatch dispatch; + PartitionDesc partdesc; + TupleTableSlot *myslot = NULL; + MemoryContext oldcxt; + + /* use per-tuple context here to avoid leaking memory */ + oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); + + /* + * First check the root table's partition constraint, if any. No point in + * routing the tuple if it doesn't belong in the root table itself. + */ + if (rootResultRelInfo->ri_RelationDesc->rd_rel->relispartition) + ExecPartitionCheck(rootResultRelInfo, slot, estate, true); + + /* start with the root partitioned table */ + dispatch = pd[0]; + while (dispatch != NULL) + { + int partidx = -1; + + CHECK_FOR_INTERRUPTS(); + + // rel = dispatch->reldesc; + partdesc = dispatch->partdesc; + + /* + * Extract partition key from tuple. Expression evaluation machinery + * that FormPartitionKeyDatum() invokes expects ecxt_scantuple to + * point to the correct tuple slot. The slot might have changed from + * what was used for the parent table if the table of the current + * partitioning level has different tuple descriptor from the parent. + * So update ecxt_scantuple accordingly. + */ + FormPartitionKeyDatum(dispatch, slot, estate, values, isnull); + + /* + * If this partitioned table has no partitions or no partition for + * these values, error out. + */ + if (partdesc->nparts == 0 || + (partidx = get_partition_for_tuple(dispatch, values, isnull)) < 0) + return InvalidOid; + + if (partdesc->is_leaf[partidx]) + return partdesc->oids[partidx]; + else + { + /* + * Partition is a sub-partitioned table; get the PartitionDispatch + */ + if (likely(dispatch->indexes[partidx] >= 0)) + { + /* Already built. */ + Assert(dispatch->indexes[partidx] < proute->num_dispatch); + /* + * Move down to the next partition level and search again + * until we find a leaf partition that matches this tuple + */ + dispatch = pd[dispatch->indexes[partidx]]; + } + else + { + /* Not yet built. Do that now. */ + PartitionDispatch subdispatch; + + /* + * Create the new PartitionDispatch. We pass the current one + * in as the parent PartitionDispatch + */ + subdispatch = ExecInitPartitionDispatchInfo( + estate, proute, partdesc->oids[partidx], dispatch, partidx, + NULL); + Assert(dispatch->indexes[partidx] >= 0 && + dispatch->indexes[partidx] < proute->num_dispatch); + + dispatch = subdispatch; + } + + /* + * Convert the tuple to the new parent's layout, if different from + * the previous parent. + */ + if (dispatch->tupslot) + { + AttrMap *map = dispatch->tupmap; + TupleTableSlot *tempslot = myslot; + + myslot = dispatch->tupslot; + slot = execute_attr_map_slot(map, slot, myslot); + + if (tempslot != NULL) + ExecClearTuple(tempslot); + } + } + } + + /* Release the tuple in the lowest parent's dedicated slot. */ + if (myslot != NULL) + ExecClearTuple(myslot); + MemoryContextSwitchTo(oldcxt); + // ExecPartitionCheckEmitError(rootResultRelInfo, slot, estate); + return InvalidOid; +} + /* * ExecInitPartitionInfo * Lock the partition and initialize ResultRelInfo. Also setup other @@ -1312,6 +1426,8 @@ FormPartitionKeyDatum(PartitionDispatch pd, ListCell *partexpr_item; int i; + // bool is_virtual = TTS_IS_VIRTUAL(slot); + // elog(INFO, "FormPartitionKeyDatum is_virtual %d", is_virtual); if (pd->key->partexprs != NIL && pd->keystate == NIL) { /* Check caller has set up context correctly */ @@ -1332,6 +1448,12 @@ FormPartitionKeyDatum(PartitionDispatch pd, if (keycol != 0) { /* Plain column; get the value directly from the heap tuple */ + // if (is_virtual) + // { + // datum = slot->tts_values[keycol-1]; + // isNull = slot->tts_isnull[keycol-1]; + // } + // else datum = slot_getattr(slot, keycol, &isNull); } else diff --git a/src/postgres/src/backend/executor/execUtils.c b/src/postgres/src/backend/executor/execUtils.c index 946829366782..b0bc34ce2e1d 100644 --- a/src/postgres/src/backend/executor/execUtils.c +++ b/src/postgres/src/backend/executor/execUtils.c @@ -488,7 +488,7 @@ ReScanExprContext(ExprContext *econtext) * not directly. */ ExprContext * -MakePerTupleExprContext(EState *estate) + MakePerTupleExprContext(EState *estate) { if (estate->es_per_tuple_exprcontext == NULL) estate->es_per_tuple_exprcontext = CreateExprContext(estate); diff --git a/src/postgres/src/backend/utils/adt/ri_triggers.c b/src/postgres/src/backend/utils/adt/ri_triggers.c index d4bac95210f6..7b5b56a884d7 100644 --- a/src/postgres/src/backend/utils/adt/ri_triggers.c +++ b/src/postgres/src/backend/utils/adt/ri_triggers.c @@ -239,6 +239,29 @@ static void ri_ReportViolation(const RI_ConstraintInfo *riinfo, TupleTableSlot *violatorslot, TupleDesc tupdesc, int queryno, bool partgone) pg_attribute_noreturn(); +TupleTableSlot * +helper(const RI_ConstraintInfo *riinfo, TupleTableSlot *slot, TupleDesc pkdesc) +{ + // elog(INFO, "starting helper"); + TupleTableSlot *pkslot = MakeTupleTableSlot(pkdesc, &TTSOpsVirtual); + // int pkatts = pkdesc->natts; + // // Datum values[pkatts]; + // // bool isnull[pkatts]; + for (int i = 0; i < riinfo->nkeys; i++) + { + const int fk_attnum = riinfo->fk_attnums[i]; + const int pk_attnum = riinfo->pk_attnums[i]; + pkslot->tts_values[pk_attnum-1] = + slot_getattr(slot, fk_attnum, &pkslot->tts_isnull[pk_attnum-1]); + } + pkslot->tts_flags &= ~TTS_FLAG_EMPTY; + pkslot->tts_nvalid = pkdesc->natts; + // elog(INFO, "ending helper"); + // HeapTuple tuple = heap_form_tuple(pkdesc, values, isnull); + // table_slot_create(Relation rel, List **reglist) + return pkslot; +} + /* ---------- * YBCBuildYBTupleIdDescriptor - * @@ -246,10 +269,21 @@ static void ri_ReportViolation(const RI_ConstraintInfo *riinfo, * Returns NULL in case at least one attribute type in source and referenced table doesn't match. * ---------- */ -static YBCPgYBTupleIdDescriptor* -YBCBuildYBTupleIdDescriptor(const RI_ConstraintInfo *riinfo, TupleTableSlot *slot) +static YBCPgYBTupleIdDescriptor * +YBCBuildYBTupleIdDescriptor(const RI_ConstraintInfo *riinfo, + TupleTableSlot *slot, EState *estate) { + // elog(INFO, "YBCBuildYBTupleIdDescriptor estate %p", estate); bool using_index = false; + // bool local_estate = false; + + // if (estate == NULL) + // { + // elog(INFO, "estate == NULL, setting local_estate to true"); + // estate = CreateExecutorState(); + // local_estate = true; + // } + Relation idx_rel = RelationIdGetRelation(riinfo->conindid); Relation source_rel = idx_rel; if (idx_rel->rd_index != NULL && !idx_rel->rd_index->indisprimary) @@ -260,6 +294,35 @@ YBCBuildYBTupleIdDescriptor(const RI_ConstraintInfo *riinfo, TupleTableSlot *slo { RelationClose(idx_rel); source_rel = RelationIdGetRelation(riinfo->pk_relid); + if (source_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + TupleTableSlot *pkslot = + helper(riinfo, slot, RelationGetDescr(source_rel)); + PartitionTupleRouting *proute = + ExecSetupPartitionTupleRouting(estate, source_rel); + ResultRelInfo *pkrelinfo = makeNode(ResultRelInfo); + pkrelinfo->ri_RelationDesc = source_rel; + Oid partoid = FindLeafPartitionOid(pkrelinfo, proute, pkslot, estate); + pfree(pkrelinfo); + ExecDropSingleTupleTableSlot(pkslot); + if (partoid == InvalidOid) + { + RI_QueryKey qkey; + ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK); + ri_ReportViolation(riinfo, + RelationIdGetRelation(riinfo->pk_relid), + RelationIdGetRelation(riinfo->fk_relid), + slot, + NULL, + qkey.constr_queryno, + false /* partgone */); + } + ExecCleanupTupleRouting(NULL, proute); + // pfree(pkslot); + RelationClose(source_rel); + source_rel = RelationIdGetRelation(partoid); + // elog(INFO, "Found partition %s", RelationGetRelationName(source_rel)); + } } Oid source_rel_relfilenode_oid = YbGetRelfileNodeId(source_rel); Oid source_dboid = YBCGetDatabaseOid(source_rel); @@ -308,6 +371,13 @@ YBCBuildYBTupleIdDescriptor(const RI_ConstraintInfo *riinfo, TupleTableSlot *slo RelationClose(source_rel); if (using_index && result) YBCFillUniqueIndexNullAttribute(result); + + // if (local_estate) + // { + // ExecCloseResultRelations(estate); + // ExecResetTupleTable(estate->es_tupleTable, false); + // FreeExecutorState(estate); + // } return result; } @@ -425,14 +495,13 @@ RI_FKey_check(TriggerData *trigdata) break; } - // YB_TODO: temp disable fast path if the referenced relation is partitioned - if (IsYBRelation(pk_rel) && pk_rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + if (IsYBRelation(pk_rel)) { /* * Use fast path for FK check in case ybctid for row in source table can be build from * referenced table tuple. */ - YBCPgYBTupleIdDescriptor *descr = YBCBuildYBTupleIdDescriptor(riinfo, newslot); + YBCPgYBTupleIdDescriptor *descr = YBCBuildYBTupleIdDescriptor(riinfo, newslot, trigdata->estate); if (descr) { bool found = false; @@ -1741,7 +1810,6 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) errdetail("MATCH FULL does not allow mixing of null and nonnull key values."), errtableconstraint(fk_rel, NameStr(fake_riinfo.conname)))); - /* * We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK * query, which isn't true, but will cause it to use @@ -3157,10 +3225,11 @@ RI_FKey_trigger_type(Oid tgfoid) } void -YbAddTriggerFKReferenceIntent(Trigger *trigger, Relation fk_rel, TupleTableSlot *new_slot) +YbAddTriggerFKReferenceIntent(Trigger *trigger, Relation fk_rel, TupleTableSlot *new_slot, EState* estate) { + // elog(INFO, "YbAddTriggerFKReferenceIntent"); YBCPgYBTupleIdDescriptor *descr = YBCBuildYBTupleIdDescriptor( - ri_FetchConstraintInfo(trigger, fk_rel, false /* rel_is_pk */), new_slot); + ri_FetchConstraintInfo(trigger, fk_rel, false /* rel_is_pk */), new_slot, estate); /* * Check that ybctid for row in source table can be build from referenced table tuple * (i.e. no type casting is required) diff --git a/src/postgres/src/include/commands/trigger.h b/src/postgres/src/include/commands/trigger.h index f5b2d2fbe9b4..b383a0a0e8bc 100644 --- a/src/postgres/src/include/commands/trigger.h +++ b/src/postgres/src/include/commands/trigger.h @@ -41,6 +41,7 @@ typedef struct TriggerData Tuplestorestate *tg_oldtable; Tuplestorestate *tg_newtable; const Bitmapset *tg_updatedcols; + EState* estate; } TriggerData; /* @@ -278,7 +279,7 @@ extern bool RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel); extern void RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel); -extern void YbAddTriggerFKReferenceIntent(Trigger *trigger, Relation fk_rel, TupleTableSlot *new_slot); +extern void YbAddTriggerFKReferenceIntent(Trigger *trigger, Relation fk_rel, TupleTableSlot *new_slot, EState* estate); /* result values for RI_FKey_trigger_type: */ #define RI_TRIGGER_PK 1 /* is a trigger on the PK relation */ diff --git a/src/postgres/src/include/executor/execPartition.h b/src/postgres/src/include/executor/execPartition.h index 708435e95285..a4f744c98344 100644 --- a/src/postgres/src/include/executor/execPartition.h +++ b/src/postgres/src/include/executor/execPartition.h @@ -32,6 +32,10 @@ extern ResultRelInfo *ExecFindPartition(ModifyTableState *mtstate, extern void ExecCleanupTupleRouting(ModifyTableState *mtstate, PartitionTupleRouting *proute); +extern Oid +FindLeafPartitionOid(ResultRelInfo *rootResultRelInfo, + PartitionTupleRouting *proute, TupleTableSlot *slot, + EState *estate); /* * PartitionedRelPruningData - Per-partitioned-table data for run-time pruning From 638ca25fdce48fc820f87df050836f094adaa85c Mon Sep 17 00:00:00 2001 From: Arpan Agrawal Date: Fri, 27 Sep 2024 15:16:36 +0530 Subject: [PATCH 06/13] minor fix --- src/postgres/src/backend/executor/execPartition.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/postgres/src/backend/executor/execPartition.c b/src/postgres/src/backend/executor/execPartition.c index 5699cc0e280f..cdd6fd717332 100644 --- a/src/postgres/src/backend/executor/execPartition.c +++ b/src/postgres/src/backend/executor/execPartition.c @@ -498,6 +498,8 @@ FindLeafPartitionOid(ResultRelInfo *rootResultRelInfo, Datum values[PARTITION_MAX_KEYS]; bool isnull[PARTITION_MAX_KEYS]; // Relation rel; + ExprContext *ecxt = GetPerTupleExprContext(estate); + TupleTableSlot *ecxt_scantuple_saved = ecxt->ecxt_scantuple; PartitionDispatch dispatch; PartitionDesc partdesc; TupleTableSlot *myslot = NULL; @@ -532,6 +534,7 @@ FindLeafPartitionOid(ResultRelInfo *rootResultRelInfo, * partitioning level has different tuple descriptor from the parent. * So update ecxt_scantuple accordingly. */ + ecxt->ecxt_scantuple = slot; FormPartitionKeyDatum(dispatch, slot, estate, values, isnull); /* @@ -598,6 +601,8 @@ FindLeafPartitionOid(ResultRelInfo *rootResultRelInfo, /* Release the tuple in the lowest parent's dedicated slot. */ if (myslot != NULL) ExecClearTuple(myslot); + /* and restore ecxt's scantuple */ + ecxt->ecxt_scantuple = ecxt_scantuple_saved; MemoryContextSwitchTo(oldcxt); // ExecPartitionCheckEmitError(rootResultRelInfo, slot, estate); return InvalidOid; From b7adc55b25bf52aa178f969eb7d99f5ff1ac02d7 Mon Sep 17 00:00:00 2001 From: Arpan Agrawal Date: Mon, 30 Sep 2024 18:35:53 +0530 Subject: [PATCH 07/13] debugging helper function --- src/postgres/src/backend/executor/execUtils.c | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/postgres/src/backend/executor/execUtils.c b/src/postgres/src/backend/executor/execUtils.c index b0bc34ce2e1d..1eba8482a707 100644 --- a/src/postgres/src/backend/executor/execUtils.c +++ b/src/postgres/src/backend/executor/execUtils.c @@ -261,6 +261,29 @@ FreeExecutorState(EState *estate) MemoryContextDelete(estate->es_query_cxt); } +void ArpanPrint(EState* estate, char* identifier) +{ + ListCell *l, *lc; + foreach (l, estate->es_opened_result_relations) + { + ResultRelInfo *resultRelInfo = lfirst(l); + elog(INFO, + "%s rel: %s " + "ri_ancestorResultRels: %p", + identifier, + RelationGetRelationName( + resultRelInfo->ri_RelationDesc), + resultRelInfo->ri_ancestorResultRels); + foreach (lc, resultRelInfo->ri_ancestorResultRels) + { + ResultRelInfo *rInfo = lfirst(lc); + elog(INFO, "Individual elements %p, relname %s", + rInfo, + RelationGetRelationName( + rInfo->ri_RelationDesc)); + } + } +} /* * Internal implementation for CreateExprContext() and CreateWorkExprContext() * that allows control over the AllocSet parameters. From 3b228787629b4ad7a6794067f6506839277c3839 Mon Sep 17 00:00:00 2001 From: Arpan Agrawal Date: Mon, 30 Sep 2024 18:40:57 +0530 Subject: [PATCH 08/13] minor refactoring --- src/postgres/src/backend/utils/adt/ri_triggers.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/postgres/src/backend/utils/adt/ri_triggers.c b/src/postgres/src/backend/utils/adt/ri_triggers.c index 7b5b56a884d7..8ca9b051e79f 100644 --- a/src/postgres/src/backend/utils/adt/ri_triggers.c +++ b/src/postgres/src/backend/utils/adt/ri_triggers.c @@ -254,8 +254,7 @@ helper(const RI_ConstraintInfo *riinfo, TupleTableSlot *slot, TupleDesc pkdesc) pkslot->tts_values[pk_attnum-1] = slot_getattr(slot, fk_attnum, &pkslot->tts_isnull[pk_attnum-1]); } - pkslot->tts_flags &= ~TTS_FLAG_EMPTY; - pkslot->tts_nvalid = pkdesc->natts; + ExecStoreVirtualTuple(pkslot); // elog(INFO, "ending helper"); // HeapTuple tuple = heap_form_tuple(pkdesc, values, isnull); // table_slot_create(Relation rel, List **reglist) @@ -318,10 +317,8 @@ YBCBuildYBTupleIdDescriptor(const RI_ConstraintInfo *riinfo, false /* partgone */); } ExecCleanupTupleRouting(NULL, proute); - // pfree(pkslot); RelationClose(source_rel); source_rel = RelationIdGetRelation(partoid); - // elog(INFO, "Found partition %s", RelationGetRelationName(source_rel)); } } Oid source_rel_relfilenode_oid = YbGetRelfileNodeId(source_rel); @@ -3227,7 +3224,6 @@ RI_FKey_trigger_type(Oid tgfoid) void YbAddTriggerFKReferenceIntent(Trigger *trigger, Relation fk_rel, TupleTableSlot *new_slot, EState* estate) { - // elog(INFO, "YbAddTriggerFKReferenceIntent"); YBCPgYBTupleIdDescriptor *descr = YBCBuildYBTupleIdDescriptor( ri_FetchConstraintInfo(trigger, fk_rel, false /* rel_is_pk */), new_slot, estate); /* From 07a841dfacc63390a0eaf521d303be9b99e1261c Mon Sep 17 00:00:00 2001 From: Arpan Agrawal Date: Mon, 30 Sep 2024 18:41:17 +0530 Subject: [PATCH 09/13] debugging helper function --- src/postgres/src/include/executor/executor.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/postgres/src/include/executor/executor.h b/src/postgres/src/include/executor/executor.h index 6c5a4298226d..67530833ae06 100644 --- a/src/postgres/src/include/executor/executor.h +++ b/src/postgres/src/include/executor/executor.h @@ -548,6 +548,7 @@ extern void end_tup_output(TupOutputState *tstate); */ extern EState *CreateExecutorState(void); extern void FreeExecutorState(EState *estate); +extern void ArpanPrint(EState* estate, char* identifier); extern ExprContext *CreateExprContext(EState *estate); extern ExprContext *CreateWorkExprContext(EState *estate); extern ExprContext *CreateStandaloneExprContext(void); From d541c613308ca7dcbcc64eb419000cd0846d3080 Mon Sep 17 00:00:00 2001 From: Arpan Agrawal Date: Mon, 30 Sep 2024 21:12:52 +0530 Subject: [PATCH 10/13] seg fault fix --- src/postgres/src/backend/executor/execPartition.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/postgres/src/backend/executor/execPartition.c b/src/postgres/src/backend/executor/execPartition.c index cdd6fd717332..64cfb1dbe156 100644 --- a/src/postgres/src/backend/executor/execPartition.c +++ b/src/postgres/src/backend/executor/execPartition.c @@ -504,6 +504,7 @@ FindLeafPartitionOid(ResultRelInfo *rootResultRelInfo, PartitionDesc partdesc; TupleTableSlot *myslot = NULL; MemoryContext oldcxt; + Oid resultOid = InvalidOid; /* use per-tuple context here to avoid leaking memory */ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); @@ -543,10 +544,13 @@ FindLeafPartitionOid(ResultRelInfo *rootResultRelInfo, */ if (partdesc->nparts == 0 || (partidx = get_partition_for_tuple(dispatch, values, isnull)) < 0) - return InvalidOid; + break; if (partdesc->is_leaf[partidx]) - return partdesc->oids[partidx]; + { + resultOid = partdesc->oids[partidx]; + break; + } else { /* @@ -605,7 +609,7 @@ FindLeafPartitionOid(ResultRelInfo *rootResultRelInfo, ecxt->ecxt_scantuple = ecxt_scantuple_saved; MemoryContextSwitchTo(oldcxt); // ExecPartitionCheckEmitError(rootResultRelInfo, slot, estate); - return InvalidOid; + return resultOid; } /* From 515b6a681f96cbd20e6d09235c7155607acd8746 Mon Sep 17 00:00:00 2001 From: Arpan Agrawal Date: Wed, 2 Oct 2024 19:35:01 +0530 Subject: [PATCH 11/13] do not error on adding FK entry with nulls --- src/postgres/src/backend/commands/tablecmds.c | 3 ++- src/postgres/src/backend/commands/trigger.c | 7 +++++++ .../src/backend/executor/nodeModifyTable.c | 1 + .../src/backend/utils/adt/ri_triggers.c | 20 ++++++++++++------- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/postgres/src/backend/commands/tablecmds.c b/src/postgres/src/backend/commands/tablecmds.c index 9c2039c11e5a..7a12bac35c9d 100644 --- a/src/postgres/src/backend/commands/tablecmds.c +++ b/src/postgres/src/backend/commands/tablecmds.c @@ -12312,7 +12312,7 @@ YbFKTriggerScanBegin(TableScanDesc scan, &YbFKTriggerScanVTableNotYugaByteEnabled; descr->per_batch_cxt = per_batch_cxt; descr->estate = CreateExecutorState(); - elog(INFO, "descr->estate %p", descr->estate); + // elog(INFO, "descr->estate %p", descr->estate); return descr; } @@ -12423,6 +12423,7 @@ validateForeignKeyConstraint(char *conname, trigdata.tg_trigtuple = ExecFetchSlotHeapTuple(ybSlot, false, NULL); trigdata.tg_trigslot = ybSlot; trigdata.tg_trigger = &trig; + trigdata.estate = fk_scan->estate; fcinfo->context = (Node *) &trigdata; diff --git a/src/postgres/src/backend/commands/trigger.c b/src/postgres/src/backend/commands/trigger.c index 390ddcaa07e4..d9e069198317 100644 --- a/src/postgres/src/backend/commands/trigger.c +++ b/src/postgres/src/backend/commands/trigger.c @@ -6141,6 +6141,8 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, bool is_crosspart_update) { Relation rel = relinfo->ri_RelationDesc; + // elog(INFO, "AfterTriggerSaveEvent relation: %s, event %d", RelationGetRelationName(rel), event); + // Relation srcrel = NULL; // if (src_partinfo) // srcrel = src_partinfo->ri_RelationDesc; @@ -6439,13 +6441,17 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, if (is_crosspart_update && TRIGGER_FIRED_BY_DELETE(event) && trigger->tgisclone) + { + // elog(INFO, "Skipping this RI_TRIGGER_PK event (delete, cross partition)"); continue; + } /* Update or delete on trigger's PK table */ if (!RI_FKey_pk_upd_check_required(trigger, rel, oldslot, newslot, &estate->yb_skip_entities)) { + // elog(INFO, "Skipping this RI_TRIGGER_PK event"); /* skip queuing this event */ continue; } @@ -6469,6 +6475,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, oldslot, newslot, &estate->yb_skip_entities)) { + // elog(INFO, "Skipping this RI_TRIGGER_FK event"); /* skip queuing this event */ continue; } diff --git a/src/postgres/src/backend/executor/nodeModifyTable.c b/src/postgres/src/backend/executor/nodeModifyTable.c index a66c0d542172..0f54285ffcea 100644 --- a/src/postgres/src/backend/executor/nodeModifyTable.c +++ b/src/postgres/src/backend/executor/nodeModifyTable.c @@ -2610,6 +2610,7 @@ ExecCrossPartitionUpdateForeignKey(ModifyTableContext *context, RelationGetRelationName(rootRelInfo->ri_RelationDesc)))); } + // elog(INFO, "ExecCrossPartitionUpdateForeignKey adding update trigger for root %s", RelationGetRelationName(rootRelInfo->ri_RelationDesc)); /* Perform the root table's triggers. */ ExecARUpdateTriggers(context->estate, rootRelInfo, sourcePartInfo, destPartInfo, diff --git a/src/postgres/src/backend/utils/adt/ri_triggers.c b/src/postgres/src/backend/utils/adt/ri_triggers.c index 8ca9b051e79f..8f2b6d0232d3 100644 --- a/src/postgres/src/backend/utils/adt/ri_triggers.c +++ b/src/postgres/src/backend/utils/adt/ri_triggers.c @@ -270,7 +270,7 @@ helper(const RI_ConstraintInfo *riinfo, TupleTableSlot *slot, TupleDesc pkdesc) */ static YBCPgYBTupleIdDescriptor * YBCBuildYBTupleIdDescriptor(const RI_ConstraintInfo *riinfo, - TupleTableSlot *slot, EState *estate) + TupleTableSlot *slot, EState *estate, bool error) { // elog(INFO, "YBCBuildYBTupleIdDescriptor estate %p", estate); bool using_index = false; @@ -304,7 +304,8 @@ YBCBuildYBTupleIdDescriptor(const RI_ConstraintInfo *riinfo, Oid partoid = FindLeafPartitionOid(pkrelinfo, proute, pkslot, estate); pfree(pkrelinfo); ExecDropSingleTupleTableSlot(pkslot); - if (partoid == InvalidOid) + ExecCleanupTupleRouting(NULL, proute); + if (partoid == InvalidOid && error) { RI_QueryKey qkey; ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK); @@ -316,9 +317,11 @@ YBCBuildYBTupleIdDescriptor(const RI_ConstraintInfo *riinfo, qkey.constr_queryno, false /* partgone */); } - ExecCleanupTupleRouting(NULL, proute); - RelationClose(source_rel); - source_rel = RelationIdGetRelation(partoid); + if (partoid != InvalidOid) + { + RelationClose(source_rel); + source_rel = RelationIdGetRelation(partoid); + } } } Oid source_rel_relfilenode_oid = YbGetRelfileNodeId(source_rel); @@ -498,7 +501,8 @@ RI_FKey_check(TriggerData *trigdata) * Use fast path for FK check in case ybctid for row in source table can be build from * referenced table tuple. */ - YBCPgYBTupleIdDescriptor *descr = YBCBuildYBTupleIdDescriptor(riinfo, newslot, trigdata->estate); + YBCPgYBTupleIdDescriptor *descr = YBCBuildYBTupleIdDescriptor(riinfo, newslot, trigdata->estate, true); + if (descr) { bool found = false; @@ -599,6 +603,7 @@ RI_FKey_check(TriggerData *trigdata) Datum RI_FKey_check_ins(PG_FUNCTION_ARGS) { + // elog(INFO, "RI_FKey_check_ins"); /* Check that this is a valid trigger call on the right time and event. */ ri_CheckTrigger(fcinfo, "RI_FKey_check_ins", RI_TRIGTYPE_INSERT); @@ -615,6 +620,7 @@ RI_FKey_check_ins(PG_FUNCTION_ARGS) Datum RI_FKey_check_upd(PG_FUNCTION_ARGS) { + // elog(INFO, "RI_FKey_check_upd"); /* Check that this is a valid trigger call on the right time and event. */ ri_CheckTrigger(fcinfo, "RI_FKey_check_upd", RI_TRIGTYPE_UPDATE); @@ -3225,7 +3231,7 @@ void YbAddTriggerFKReferenceIntent(Trigger *trigger, Relation fk_rel, TupleTableSlot *new_slot, EState* estate) { YBCPgYBTupleIdDescriptor *descr = YBCBuildYBTupleIdDescriptor( - ri_FetchConstraintInfo(trigger, fk_rel, false /* rel_is_pk */), new_slot, estate); + ri_FetchConstraintInfo(trigger, fk_rel, false /* rel_is_pk */), new_slot, estate, false); /* * Check that ybctid for row in source table can be build from referenced table tuple * (i.e. no type casting is required) From c8072c0e113ff723c1c674abbd9633232a69cd1a Mon Sep 17 00:00:00 2001 From: Arpan Agrawal Date: Wed, 2 Oct 2024 20:05:56 +0530 Subject: [PATCH 12/13] Do not throw error at ll --- .../src/backend/utils/adt/ri_triggers.c | 33 ++----------------- 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/src/postgres/src/backend/utils/adt/ri_triggers.c b/src/postgres/src/backend/utils/adt/ri_triggers.c index 8f2b6d0232d3..b3388dd4bd17 100644 --- a/src/postgres/src/backend/utils/adt/ri_triggers.c +++ b/src/postgres/src/backend/utils/adt/ri_triggers.c @@ -270,18 +270,9 @@ helper(const RI_ConstraintInfo *riinfo, TupleTableSlot *slot, TupleDesc pkdesc) */ static YBCPgYBTupleIdDescriptor * YBCBuildYBTupleIdDescriptor(const RI_ConstraintInfo *riinfo, - TupleTableSlot *slot, EState *estate, bool error) + TupleTableSlot *slot, EState *estate) { - // elog(INFO, "YBCBuildYBTupleIdDescriptor estate %p", estate); bool using_index = false; - // bool local_estate = false; - - // if (estate == NULL) - // { - // elog(INFO, "estate == NULL, setting local_estate to true"); - // estate = CreateExecutorState(); - // local_estate = true; - // } Relation idx_rel = RelationIdGetRelation(riinfo->conindid); Relation source_rel = idx_rel; @@ -305,18 +296,6 @@ YBCBuildYBTupleIdDescriptor(const RI_ConstraintInfo *riinfo, pfree(pkrelinfo); ExecDropSingleTupleTableSlot(pkslot); ExecCleanupTupleRouting(NULL, proute); - if (partoid == InvalidOid && error) - { - RI_QueryKey qkey; - ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK); - ri_ReportViolation(riinfo, - RelationIdGetRelation(riinfo->pk_relid), - RelationIdGetRelation(riinfo->fk_relid), - slot, - NULL, - qkey.constr_queryno, - false /* partgone */); - } if (partoid != InvalidOid) { RelationClose(source_rel); @@ -372,12 +351,6 @@ YBCBuildYBTupleIdDescriptor(const RI_ConstraintInfo *riinfo, if (using_index && result) YBCFillUniqueIndexNullAttribute(result); - // if (local_estate) - // { - // ExecCloseResultRelations(estate); - // ExecResetTupleTable(estate->es_tupleTable, false); - // FreeExecutorState(estate); - // } return result; } @@ -501,7 +474,7 @@ RI_FKey_check(TriggerData *trigdata) * Use fast path for FK check in case ybctid for row in source table can be build from * referenced table tuple. */ - YBCPgYBTupleIdDescriptor *descr = YBCBuildYBTupleIdDescriptor(riinfo, newslot, trigdata->estate, true); + YBCPgYBTupleIdDescriptor *descr = YBCBuildYBTupleIdDescriptor(riinfo, newslot, trigdata->estate); if (descr) { @@ -3231,7 +3204,7 @@ void YbAddTriggerFKReferenceIntent(Trigger *trigger, Relation fk_rel, TupleTableSlot *new_slot, EState* estate) { YBCPgYBTupleIdDescriptor *descr = YBCBuildYBTupleIdDescriptor( - ri_FetchConstraintInfo(trigger, fk_rel, false /* rel_is_pk */), new_slot, estate, false); + ri_FetchConstraintInfo(trigger, fk_rel, false /* rel_is_pk */), new_slot, estate); /* * Check that ybctid for row in source table can be build from referenced table tuple * (i.e. no type casting is required) From b2c4ba1004040bf9c3f7c80cea964a0b00d36f8a Mon Sep 17 00:00:00 2001 From: Arpan Agrawal Date: Wed, 2 Oct 2024 20:08:15 +0530 Subject: [PATCH 13/13] cleanup --- src/postgres/src/backend/executor/execUtils.c | 25 +------------------ src/postgres/src/include/executor/executor.h | 1 - 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/src/postgres/src/backend/executor/execUtils.c b/src/postgres/src/backend/executor/execUtils.c index 1eba8482a707..946829366782 100644 --- a/src/postgres/src/backend/executor/execUtils.c +++ b/src/postgres/src/backend/executor/execUtils.c @@ -261,29 +261,6 @@ FreeExecutorState(EState *estate) MemoryContextDelete(estate->es_query_cxt); } -void ArpanPrint(EState* estate, char* identifier) -{ - ListCell *l, *lc; - foreach (l, estate->es_opened_result_relations) - { - ResultRelInfo *resultRelInfo = lfirst(l); - elog(INFO, - "%s rel: %s " - "ri_ancestorResultRels: %p", - identifier, - RelationGetRelationName( - resultRelInfo->ri_RelationDesc), - resultRelInfo->ri_ancestorResultRels); - foreach (lc, resultRelInfo->ri_ancestorResultRels) - { - ResultRelInfo *rInfo = lfirst(lc); - elog(INFO, "Individual elements %p, relname %s", - rInfo, - RelationGetRelationName( - rInfo->ri_RelationDesc)); - } - } -} /* * Internal implementation for CreateExprContext() and CreateWorkExprContext() * that allows control over the AllocSet parameters. @@ -511,7 +488,7 @@ ReScanExprContext(ExprContext *econtext) * not directly. */ ExprContext * - MakePerTupleExprContext(EState *estate) +MakePerTupleExprContext(EState *estate) { if (estate->es_per_tuple_exprcontext == NULL) estate->es_per_tuple_exprcontext = CreateExprContext(estate); diff --git a/src/postgres/src/include/executor/executor.h b/src/postgres/src/include/executor/executor.h index 67530833ae06..6c5a4298226d 100644 --- a/src/postgres/src/include/executor/executor.h +++ b/src/postgres/src/include/executor/executor.h @@ -548,7 +548,6 @@ extern void end_tup_output(TupOutputState *tstate); */ extern EState *CreateExecutorState(void); extern void FreeExecutorState(EState *estate); -extern void ArpanPrint(EState* estate, char* identifier); extern ExprContext *CreateExprContext(EState *estate); extern ExprContext *CreateWorkExprContext(EState *estate); extern ExprContext *CreateStandaloneExprContext(void);