-- NOT ENFORCED -- -- First test, check and cascade -- CREATETABLE PKTABLE ( ptest1 intPRIMARYKEY, ptest2 text ); CREATETABLE FKTABLE ( ftest1 intCONSTRAINT fktable_ftest1_fkey REFERENCES PKTABLE MATCH FULL ONDELETECASCADEONUPDATECASCADENOT ENFORCED,
ftest2 int );
-- Inserting into the foreign key table will not result in an error, even if -- there is no matching key in the referenced table. INSERTINTO FKTABLE VALUES (1, 2); INSERTINTO FKTABLE VALUES (2, 3);
-- Check FKTABLE SELECT * FROM FKTABLE;
-- Reverting it back to ENFORCED will result in failure because constraint validation will be triggered, -- as it was previously in a valid state. ALTERTABLE FKTABLE ALTERCONSTRAINT fktable_ftest1_fkey ENFORCED;
-- Insert referenced data that satisfies the constraint, then attempt to -- change it. INSERTINTO PKTABLE VALUES (1, 'Test1'); INSERTINTO PKTABLE VALUES (2, 'Test2'); ALTERTABLE FKTABLE ALTERCONSTRAINT fktable_ftest1_fkey ENFORCED;
-- Any further inserts will fail due to the enforcement. INSERTINTO FKTABLE VALUES (3, 4);
-- -- MATCH FULL -- -- First test, check and cascade -- -- Insert test data into PKTABLE INSERTINTO PKTABLE VALUES (3, 'Test3'); INSERTINTO PKTABLE VALUES (4, 'Test4'); INSERTINTO PKTABLE VALUES (5, 'Test5');
-- Insert successful rows into FK TABLE INSERTINTO FKTABLE VALUES (3, 4); INSERTINTO FKTABLE VALUES (NULL, 1);
-- Insert a failed row into FK TABLE INSERTINTO FKTABLE VALUES (100, 2);
-- Check FKTABLE SELECT * FROM FKTABLE;
-- Delete a row from PK TABLE DELETEFROM PKTABLE WHERE ptest1=1;
-- Check FKTABLE for removal of matched row SELECT * FROM FKTABLE;
-- Update a row from PK TABLE UPDATE PKTABLE SET ptest1=1WHERE ptest1=2;
-- Check FKTABLE for update of matched row SELECT * FROM FKTABLE;
DROPTABLE FKTABLE; DROPTABLE PKTABLE;
-- -- check set NULL and table constraint on multiple columns -- CREATETABLE PKTABLE ( ptest1 int, ptest2 int, ptest3 text, PRIMARYKEY(ptest1, ptest2) ); CREATETABLE FKTABLE ( ftest1 int, ftest2 int, ftest3 int, CONSTRAINT constrname FOREIGNKEY(ftest1, ftest2) REFERENCES PKTABLE MATCH FULL ONDELETESETNULLONUPDATESETNULL);
-- Test comments
COMMENT ONCONSTRAINT constrname_wrong ON FKTABLE IS'fk constraint comment';
COMMENT ONCONSTRAINT constrname ON FKTABLE IS'fk constraint comment';
COMMENT ONCONSTRAINT constrname ON FKTABLE ISNULL;
-- Delete a row from PK TABLE DELETEFROM PKTABLE WHERE ptest1=1and ptest2=2;
-- Check FKTABLE for removal of matched row SELECT * FROM FKTABLE;
-- Delete another row from PK TABLE DELETEFROM PKTABLE WHERE ptest1=5and ptest2=10;
-- Check FKTABLE (should be no change) SELECT * FROM FKTABLE;
-- Update a row from PK TABLE UPDATE PKTABLE SET ptest1=1WHERE ptest1=2;
-- Check FKTABLE for update of matched row SELECT * FROM FKTABLE;
-- Check update with part of key null UPDATE FKTABLE SET ftest1 = NULLWHERE ftest1 = 1;
-- Check update with old and new key values equal UPDATE FKTABLE SET ftest1 = 1WHERE ftest1 = 1;
-- Try altering the column type where foreign keys are involved ALTERTABLE PKTABLE ALTERCOLUMN ptest1 TYPE bigint; ALTERTABLE FKTABLE ALTERCOLUMN ftest1 TYPE bigint; SELECT * FROM PKTABLE; SELECT * FROM FKTABLE;
DROPTABLE PKTABLE CASCADE; DROPTABLE FKTABLE;
-- -- check set default and table constraint on multiple columns -- CREATETABLE PKTABLE ( ptest1 int, ptest2 int, ptest3 text, PRIMARYKEY(ptest1, ptest2) ); CREATETABLE FKTABLE ( ftest1 intDEFAULT -1, ftest2 intDEFAULT -2, ftest3 int, CONSTRAINT constrname2 FOREIGNKEY(ftest1, ftest2) REFERENCES PKTABLE MATCH FULL ONDELETESETDEFAULTONUPDATESETDEFAULT);
-- Insert a value in PKTABLE for default INSERTINTO PKTABLE VALUES (-1, -2, 'The Default!');
-- Delete a row from PK TABLE DELETEFROM PKTABLE WHERE ptest1=1and ptest2=2;
-- Check FKTABLE to check for removal SELECT * FROM FKTABLE;
-- Delete another row from PK TABLE DELETEFROM PKTABLE WHERE ptest1=5and ptest2=10;
-- Check FKTABLE (should be no change) SELECT * FROM FKTABLE;
-- Update a row from PK TABLE UPDATE PKTABLE SET ptest1=1WHERE ptest1=2;
-- Check FKTABLE for update of matched row SELECT * FROM FKTABLE;
-- this should fail for lack of CASCADE DROPTABLE PKTABLE; DROPTABLE PKTABLE CASCADE; DROPTABLE FKTABLE;
-- -- First test, check with no on delete or on update -- CREATETABLE PKTABLE ( ptest1 intPRIMARYKEY, ptest2 text ); CREATETABLE FKTABLE ( ftest1 intREFERENCES PKTABLE MATCH FULL, ftest2 int );
ALTERTABLE FKTABLE ADDFOREIGNKEY(ftest1, ftest2) REFERENCES PKTABLE MATCH FULL;
-- Modifying other attributes of a constraint should not affect its enforceability, and vice versa ALTERTABLE FKTABLE ADDCONSTRAINT fk_con FOREIGNKEY(ftest1, ftest2) REFERENCES PKTABLE NOT VALID NOT ENFORCED; ALTERTABLE FKTABLE ALTERCONSTRAINT fk_con DEFERRABLE INITIALLY DEFERRED; SELECT condeferrable, condeferred, conenforced, convalidated FROM pg_constraint WHERE conname = 'fk_con';
ALTERTABLE FKTABLE ALTERCONSTRAINT fk_con NOT ENFORCED; SELECT condeferrable, condeferred, conenforced, convalidated FROM pg_constraint WHERE conname = 'fk_con';
-- Enforceability also changes the validate state, as data validation will be -- performed during this transformation. ALTERTABLE FKTABLE ALTERCONSTRAINT fk_con ENFORCED; SELECT condeferrable, condeferred, conenforced, convalidated FROM pg_constraint WHERE conname = 'fk_con';
-- Can change enforceability and deferrability together ALTERTABLE FKTABLE ALTERCONSTRAINT fk_con NOT ENFORCED NOT DEFERRABLE; SELECT condeferrable, condeferred, conenforced, convalidated FROM pg_constraint WHERE conname = 'fk_con';
-- Try to update something that will fail UPDATE PKTABLE set ptest2=5where ptest2=2;
-- Try to update something that will set default UPDATE PKTABLE set ptest1=0, ptest2=-1, ptest3=-2where ptest2=2; UPDATE PKTABLE set ptest2=10where ptest2=4;
-- Try to update something that should not set default UPDATE PKTABLE set ptest2=2WHERE ptest2=3and ptest1=1;
-- Show PKTABLE and FKTABLE SELECT * from PKTABLE; SELECT * from FKTABLE;
-- Try to delete something that should set null DELETEFROM PKTABLE where ptest1=2and ptest2=3and ptest3=4;
-- Show PKTABLE and FKTABLE SELECT * from PKTABLE; SELECT * from FKTABLE;
-- Try to delete something that should not set null DELETEFROM PKTABLE where ptest2=-1and ptest3=5;
-- Show PKTABLE and FKTABLE SELECT * from PKTABLE; SELECT * from FKTABLE;
DROPTABLE FKTABLE; DROPTABLE PKTABLE;
-- Test for ON DELETE SET NULL/DEFAULT (column_list); CREATETABLE PKTABLE (tid int, id int, PRIMARYKEY (tid, id)); CREATETABLE FKTABLE (tid int, id int, foo int, FOREIGNKEY (tid, id) REFERENCES PKTABLE ONDELETESETNULL (bar)); CREATETABLE FKTABLE (tid int, id int, foo int, FOREIGNKEY (tid, id) REFERENCES PKTABLE ONDELETESETNULL (foo)); CREATETABLE FKTABLE (tid int, id int, foo int, FOREIGNKEY (tid, foo) REFERENCES PKTABLE ONUPDATESETNULL (foo)); CREATETABLE FKTABLE (
tid int, id int,
fk_id_del_set_null int,
fk_id_del_set_default intDEFAULT0, FOREIGNKEY (tid, fk_id_del_set_null) REFERENCES PKTABLE ONDELETESETNULL (fk_id_del_set_null), -- this tests handling of duplicate entries in SET DEFAULT column list FOREIGNKEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ONDELETESETDEFAULT (fk_id_del_set_default, fk_id_del_set_default)
);
SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass::oid ORDERBY oid;
-- Test for referencing column number smaller than referenced constraint CREATETABLE PKTABLE (ptest1 int, ptest2 int, UNIQUE(ptest1, ptest2)); CREATETABLE FKTABLE_FAIL1 (ftest1 intREFERENCES pktable(ptest1));
DROPTABLE FKTABLE_FAIL1; DROPTABLE PKTABLE;
-- -- Tests for mismatched types -- -- Basic one column, two table setup CREATETABLE PKTABLE (ptest1 intPRIMARYKEY); INSERTINTO PKTABLE VALUES(42); -- This next should fail, because int=inet does not exist CREATETABLE FKTABLE (ftest1 inet REFERENCES pktable); -- This should also fail for the same reason, but here we -- give the column name CREATETABLE FKTABLE (ftest1 inet REFERENCES pktable(ptest1)); -- This should succeed, even though they are different types, -- because int=int8 exists and is a member of the integer opfamily CREATETABLE FKTABLE (ftest1 int8REFERENCES pktable); -- Check it actually works INSERTINTO FKTABLE VALUES(42); -- should succeed INSERTINTO FKTABLE VALUES(43); -- should fail UPDATE FKTABLE SET ftest1 = ftest1; -- should succeed UPDATE FKTABLE SET ftest1 = ftest1 + 1; -- should fail DROPTABLE FKTABLE; -- This should fail, because we'd have to cast numeric to int which is -- not an implicit coercion (or use numeric=numeric, but that's not part -- of the integer opfamily) CREATETABLE FKTABLE (ftest1 numericREFERENCES pktable); DROPTABLE PKTABLE; -- On the other hand, this should work because int implicitly promotes to -- numeric, and we allow promotion on the FK side CREATETABLE PKTABLE (ptest1 numericPRIMARYKEY); INSERTINTO PKTABLE VALUES(42); CREATETABLE FKTABLE (ftest1 intREFERENCES pktable); -- Check it actually works INSERTINTO FKTABLE VALUES(42); -- should succeed INSERTINTO FKTABLE VALUES(43); -- should fail UPDATE FKTABLE SET ftest1 = ftest1; -- should succeed UPDATE FKTABLE SET ftest1 = ftest1 + 1; -- should fail DROPTABLE FKTABLE; DROPTABLE PKTABLE;
-- Two columns, two tables CREATETABLE PKTABLE (ptest1 int, ptest2 inet, PRIMARYKEY(ptest1, ptest2)); -- This should fail, because we just chose really odd types CREATETABLE FKTABLE (ftest1 cidr, ftest2 timestamp, FOREIGNKEY(ftest1, ftest2) REFERENCES pktable); -- Again, so should this... CREATETABLE FKTABLE (ftest1 cidr, ftest2 timestamp, FOREIGNKEY(ftest1, ftest2) REFERENCES pktable(ptest1, ptest2)); -- This fails because we mixed up the column ordering CREATETABLE FKTABLE (ftest1 int, ftest2 inet, FOREIGNKEY(ftest2, ftest1) REFERENCES pktable); -- As does this... CREATETABLE FKTABLE (ftest1 int, ftest2 inet, FOREIGNKEY(ftest2, ftest1) REFERENCES pktable(ptest1, ptest2)); -- And again.. CREATETABLE FKTABLE (ftest1 int, ftest2 inet, FOREIGNKEY(ftest1, ftest2) REFERENCES pktable(ptest2, ptest1)); -- This works... CREATETABLE FKTABLE (ftest1 int, ftest2 inet, FOREIGNKEY(ftest2, ftest1) REFERENCES pktable(ptest2, ptest1)); DROPTABLE FKTABLE; -- As does this CREATETABLE FKTABLE (ftest1 int, ftest2 inet, FOREIGNKEY(ftest1, ftest2) REFERENCES pktable(ptest1, ptest2)); DROPTABLE FKTABLE; DROPTABLE PKTABLE;
-- Two columns, same table -- Make sure this still works... CREATETABLE PKTABLE (ptest1 int, ptest2 inet, ptest3 int, ptest4 inet, PRIMARYKEY(ptest1, ptest2), FOREIGNKEY(ptest3,
ptest4) REFERENCES pktable(ptest1, ptest2)); DROPTABLE PKTABLE; -- And this, CREATETABLE PKTABLE (ptest1 int, ptest2 inet, ptest3 int, ptest4 inet, PRIMARYKEY(ptest1, ptest2), FOREIGNKEY(ptest3,
ptest4) REFERENCES pktable); DROPTABLE PKTABLE; -- This shouldn't (mixed up columns) CREATETABLE PKTABLE (ptest1 int, ptest2 inet, ptest3 int, ptest4 inet, PRIMARYKEY(ptest1, ptest2), FOREIGNKEY(ptest3,
ptest4) REFERENCES pktable(ptest2, ptest1)); -- Nor should this... (same reason, we have 4,3 referencing 1,2 which mismatches types CREATETABLE PKTABLE (ptest1 int, ptest2 inet, ptest3 int, ptest4 inet, PRIMARYKEY(ptest1, ptest2), FOREIGNKEY(ptest4,
ptest3) REFERENCES pktable(ptest1, ptest2)); -- Not this one either... Same as the last one except we didn't defined the columns being referenced. CREATETABLE PKTABLE (ptest1 int, ptest2 inet, ptest3 int, ptest4 inet, PRIMARYKEY(ptest1, ptest2), FOREIGNKEY(ptest4,
ptest3) REFERENCES pktable);
-- -- Now some cases with inheritance -- Basic 2 table case: 1 column of matching types. createtable pktable_base (base1 intnotnull); createtable pktable (ptest1 int, primarykey(base1), unique(base1, ptest1)) inherits (pktable_base); createtable fktable (ftest1 intreferences pktable(base1)); -- now some ins, upd, del insertinto pktable(base1) values (1); insertinto pktable(base1) values (2); -- let's insert a non-existent fktable value insertinto fktable(ftest1) values (3); -- let's make a valid row for that insertinto pktable(base1) values (3); insertinto fktable(ftest1) values (3); -- let's try removing a row that should fail from pktable deletefrom pktable where base1>2; -- okay, let's try updating all of the base1 values to *4 -- which should fail. update pktable set base1=base1*4; -- okay, let's try an update that should work. update pktable set base1=base1*4where base1<3; -- and a delete that should work deletefrom pktable where base1>3; -- cleanup droptable fktable; deletefrom pktable;
-- Now 2 columns 2 tables, matching types createtable fktable (ftest1 int, ftest2 int, foreignkey(ftest1, ftest2) references pktable(base1, ptest1)); -- now some ins, upd, del insertinto pktable(base1, ptest1) values (1, 1); insertinto pktable(base1, ptest1) values (2, 2); -- let's insert a non-existent fktable value insertinto fktable(ftest1, ftest2) values (3, 1); -- let's make a valid row for that insertinto pktable(base1,ptest1) values (3, 1); insertinto fktable(ftest1, ftest2) values (3, 1); -- let's try removing a row that should fail from pktable deletefrom pktable where base1>2; -- okay, let's try updating all of the base1 values to *4 -- which should fail. update pktable set base1=base1*4; -- okay, let's try an update that should work. update pktable set base1=base1*4where base1<3; -- and a delete that should work deletefrom pktable where base1>3; -- cleanup droptable fktable; droptable pktable; droptable pktable_base;
-- Now we'll do one all in 1 table with 2 columns of matching types createtable pktable_base(base1 intnotnull, base2 int); createtable pktable(ptest1 int, ptest2 int, primarykey(base1, ptest1), foreignkey(base2, ptest2) references
pktable(base1, ptest1)) inherits (pktable_base); insertinto pktable (base1, ptest1, base2, ptest2) values (1, 1, 1, 1); insertinto pktable (base1, ptest1, base2, ptest2) values (2, 1, 1, 1); insertinto pktable (base1, ptest1, base2, ptest2) values (2, 2, 2, 1); insertinto pktable (base1, ptest1, base2, ptest2) values (1, 3, 2, 2); -- fails (3,2) isn't in base1, ptest1 insertinto pktable (base1, ptest1, base2, ptest2) values (2, 3, 3, 2); -- fails (2,2) is being referenced deletefrom pktable where base1=2; -- fails (1,1) is being referenced (twice) update pktable set base1=3where base1=1; -- this sequence of two deletes will work, since after the first there will be no (2,*) references deletefrom pktable where base2=2; deletefrom pktable where base1=2; droptable pktable; droptable pktable_base;
-- 2 columns (2 tables), mismatched types createtable pktable_base(base1 intnotnull); createtable pktable(ptest1 inet, primarykey(base1, ptest1)) inherits (pktable_base); -- just generally bad types (with and without column references on the referenced table) createtable fktable(ftest1 cidr, ftest2 int[], foreignkey (ftest1, ftest2) references pktable); createtable fktable(ftest1 cidr, ftest2 int[], foreignkey (ftest1, ftest2) references pktable(base1, ptest1)); -- let's mix up which columns reference which createtable fktable(ftest1 int, ftest2 inet, foreignkey(ftest2, ftest1) references pktable); createtable fktable(ftest1 int, ftest2 inet, foreignkey(ftest2, ftest1) references pktable(base1, ptest1)); createtable fktable(ftest1 int, ftest2 inet, foreignkey(ftest1, ftest2) references pktable(ptest1, base1)); droptable pktable; droptable pktable_base;
-- deferrable, explicitly deferred CREATETABLE pktable (
id INT4PRIMARYKEY,
other INT4
);
CREATETABLE fktable (
id INT4PRIMARYKEY,
fk INT4REFERENCES pktable DEFERRABLE
);
-- default to immediate: should fail INSERTINTO fktable VALUES (5, 10);
-- explicitly defer the constraint
BEGIN;
SET CONSTRAINTS ALL DEFERRED;
INSERTINTO fktable VALUES (10, 15); INSERTINTO pktable VALUES (15, 0); -- make the FK insert valid
COMMIT;
DROPTABLE fktable, pktable;
-- deferrable, initially deferred CREATETABLE pktable (
id INT4PRIMARYKEY,
other INT4
);
CREATETABLE fktable (
id INT4PRIMARYKEY,
fk INT4REFERENCES pktable DEFERRABLE INITIALLY DEFERRED
);
-- default to deferred, should succeed
BEGIN;
INSERTINTO fktable VALUES (100, 200); INSERTINTO pktable VALUES (200, 500); -- make the FK insert valid
COMMIT;
-- default to deferred, explicitly make immediate
BEGIN;
SET CONSTRAINTS ALL IMMEDIATE;
-- should fail INSERTINTO fktable VALUES (500, 1000);
COMMIT;
-- Check that the existing FK trigger is both deferrable and initially deferred SELECT conname, tgrelid::regclass as tgrel,
regexp_replace(tgname, '[0-9]+', 'N') as tgname, tgtype,
tgdeferrable, tginitdeferred FROM pg_trigger t JOIN pg_constraint c ON (t.tgconstraint = c.oid) WHERE conrelid = 'fktable'::regclass AND conname = 'fktable_fk_fkey' ORDERBY tgrelid, tgtype;
-- Changing the constraint to NOT ENFORCED drops the associated FK triggers ALTERTABLE FKTABLE ALTERCONSTRAINT fktable_fk_fkey NOT ENFORCED; SELECT conname, tgrelid::regclass as tgrel,
regexp_replace(tgname, '[0-9]+', 'N') as tgname, tgtype,
tgdeferrable, tginitdeferred FROM pg_trigger t JOIN pg_constraint c ON (t.tgconstraint = c.oid) WHERE conrelid = 'fktable'::regclass AND conname = 'fktable_fk_fkey' ORDERBY tgrelid, tgtype;
-- Changing it back to ENFORCED will recreate the necessary FK triggers -- that are deferrable and initially deferred ALTERTABLE FKTABLE ALTERCONSTRAINT fktable_fk_fkey ENFORCED; SELECT conname, tgrelid::regclass as tgrel,
regexp_replace(tgname, '[0-9]+', 'N') as tgname, tgtype,
tgdeferrable, tginitdeferred FROM pg_trigger t JOIN pg_constraint c ON (t.tgconstraint = c.oid) WHERE conrelid = 'fktable'::regclass AND conname = 'fktable_fk_fkey' ORDERBY tgrelid, tgtype;
-- Verify that a deferrable, initially deferred foreign key still works -- as expected after being set to NOT ENFORCED and then re-enabled
BEGIN;
-- doesn't match PK, but no error yet INSERTINTO fktable VALUES (2, 20);
-- should catch error from INSERT at commit COMMIT;
DROPTABLE fktable, pktable;
-- tricky behavior: according to SQL99, if a deferred constraint is set -- to 'immediate' mode, it should be checked for validity *immediately*, -- not when the current transaction commits (i.e. the mode change applies -- retroactively) CREATETABLE pktable (
id INT4PRIMARYKEY,
other INT4
);
CREATETABLE fktable (
id INT4PRIMARYKEY,
fk INT4REFERENCES pktable DEFERRABLE
);
BEGIN;
SET CONSTRAINTS ALL DEFERRED;
-- should succeed, for now INSERTINTO fktable VALUES (1000, 2000);
-- should cause transaction abort, due to preceding error SET CONSTRAINTS ALL IMMEDIATE;
INSERTINTO pktable VALUES (2000, 3); -- too late
COMMIT;
DROPTABLE fktable, pktable;
-- deferrable, initially deferred CREATETABLE pktable (
id INT4PRIMARYKEY,
other INT4
);
CREATETABLE fktable (
id INT4PRIMARYKEY,
fk INT4REFERENCES pktable DEFERRABLE INITIALLY DEFERRED
);
BEGIN;
-- no error here INSERTINTO fktable VALUES (100, 200);
-- error here on commit COMMIT;
DROPTABLE pktable, fktable;
-- test notice about expensive referential integrity checks, -- where the index cannot be used because of type incompatibilities.
-- test a tricky case: we can elide firing the FK check trigger during -- an UPDATE if the UPDATE did not change the foreign key -- field. However, we can't do this if our transaction was the one that -- created the updated row and the trigger is deferred, since our UPDATE -- will have invalidated the original newly-inserted tuple, and therefore -- cause the on-INSERT RI trigger not to be fired.
CREATE TEMP TABLE pktable (
id intprimarykey,
other int
);
CREATE TEMP TABLE fktable (
id intprimarykey,
fk intreferences pktable deferrable initially deferred
);
INSERTINTO pktable VALUES (5, 10);
BEGIN;
-- doesn't match PK, but no error yet INSERTINTO fktable VALUES (0, 20);
-- don't change FK UPDATE fktable SET id = id + 1;
-- should catch error from initial INSERT COMMIT;
-- check same case when insert is in a different subtransaction than update
BEGIN;
-- doesn't match PK, but no error yet INSERTINTO fktable VALUES (0, 20);
-- UPDATE will be in a subxact
SAVEPOINT savept1;
-- don't change FK UPDATE fktable SET id = id + 1;
-- should catch error from initial INSERT COMMIT;
BEGIN;
-- INSERT will be in a subxact
SAVEPOINT savept1;
-- doesn't match PK, but no error yet INSERTINTO fktable VALUES (0, 20);
RELEASE SAVEPOINT savept1;
-- don't change FK UPDATE fktable SET id = id + 1;
-- should catch error from initial INSERT COMMIT;
BEGIN;
-- doesn't match PK, but no error yet INSERTINTO fktable VALUES (0, 20);
-- UPDATE will be in a subxact
SAVEPOINT savept1;
-- don't change FK UPDATE fktable SET id = id + 1;
-- doesn't match FK, should throw error now UPDATE pktable SET id = 10WHERE id = 5;
COMMIT;
BEGIN;
-- doesn't match PK, should throw error now INSERTINTO fktable VALUES (0, 20);
COMMIT;
ALTERTABLE fktable ALTERCONSTRAINT fktable_fk_fkey NOT ENFORCED;
BEGIN;
-- doesn't match FK, but no error. UPDATE pktable SET id = 10WHERE id = 5; -- doesn't match PK, but no error. INSERTINTO fktable VALUES (0, 20);
ROLLBACK;
-- try additional syntax ALTERTABLE fktable ALTERCONSTRAINT fktable_fk_fkey NOT DEFERRABLE; -- illegal options ALTERTABLE fktable ALTERCONSTRAINT fktable_fk_fkey NOT DEFERRABLE INITIALLY DEFERRED; ALTERTABLE fktable ALTERCONSTRAINT fktable_fk_fkey NO INHERIT; ALTERTABLE fktable ALTERCONSTRAINT fktable_fk_fkey NOT VALID; ALTERTABLE fktable ALTERCONSTRAINT fktable_fk_fkey ENFORCED NOT ENFORCED; CREATE TEMP TABLE fktable2 (fk intreferences pktable ENFORCED NOT ENFORCED);
-- test order of firing of FK triggers when several RI-induced changes need to -- be made to the same row. This was broken by subtransaction-related -- changes in 8.0.
CREATE TEMP TABLE users (
id INTPRIMARYKEY,
name VARCHARNOTNULL
);
-- could fail with only 2 changes to make, if row was already updated
BEGIN; UPDATE tasks set id=id WHERE id=2; SELECT * FROM tasks; DELETEFROM users WHERE id = 2; SELECT * FROM tasks; COMMIT;
-- -- Test self-referential FK with CASCADE (bug #6268) -- create temp table selfref (
a intprimarykey,
b int, foreignkey (b) references selfref (a) onupdatecascadeondeletecascade
);
insertinto selfref (a, b) values
(0, 0),
(1, 1);
begin; update selfref set a = 123where a = 0; select a, b from selfref; update selfref set a = 456where a = 123; select a, b from selfref; commit;
-- -- Test that SET DEFAULT actions recognize updates to default values -- create temp table defp (f1 intprimarykey); create temp table defc (f1 intdefault0 references defp ondeletesetdefault); insertinto defp values (0), (1), (2); insertinto defc values (2); select * from defc; deletefrom defp where f1 = 2; select * from defc; deletefrom defp where f1 = 0; -- fail altertable defc altercolumn f1 setdefault1; deletefrom defp where f1 = 0; select * from defc; deletefrom defp where f1 = 1; -- fail
-- -- Test the difference between NO ACTION and RESTRICT -- create temp table pp (f1 intprimarykey); create temp table cc (f1 intreferences pp onupdate no action ondelete no action); insertinto pp values(12); insertinto pp values(11); update pp set f1=f1+1; insertinto cc values(13); update pp set f1=f1+1; update pp set f1=f1+1; -- fail deletefrom pp where f1 = 13; -- fail droptable pp, cc;
create temp table pp (f1 intprimarykey); create temp table cc (f1 intreferences pp onupdaterestrictondeleterestrict); insertinto pp values(12); insertinto pp values(11); update pp set f1=f1+1; insertinto cc values(13); update pp set f1=f1+1; -- fail deletefrom pp where f1 = 13; -- fail droptable pp, cc;
-- -- Test interaction of foreign-key optimization with rules (bug #14219) -- create temp table t1 (a integerprimarykey, b text); create temp table t2 (a integerprimarykey, b integerreferences t1); create rule r1 asondeleteto t1 do deletefrom t2 where t2.b = old.a;
explain (costs off) deletefrom t1 where a = 1; deletefrom t1 where a = 1;
-- Test a primary key with attributes located in later attnum positions -- compared to the fk attributes. createtable pktable2 (a int, b int, c int, d int, e int, primarykey (d, e)); createtable fktable2 (d int, e int, foreignkey (d, e) references pktable2); insertinto pktable2 values (1, 2, 3, 4, 5); insertinto fktable2 values (4, 5); deletefrom pktable2; update pktable2 set d = 5; droptable pktable2, fktable2;
-- Test truncation of long foreign key names createtable pktable1 (a intprimarykey); createtable pktable2 (a int, b int, primarykey (a, b)); createtable fktable2 (
a int,
b int,
very_very_long_column_name_to_exceed_63_characters int, foreignkey (very_very_long_column_name_to_exceed_63_characters) references pktable1, foreignkey (a, very_very_long_column_name_to_exceed_63_characters) references pktable2, foreignkey (a, very_very_long_column_name_to_exceed_63_characters) references pktable2
); select conname from pg_constraint where conrelid = 'fktable2'::regclass orderby conname; droptable pktable1, pktable2, fktable2;
-- -- Test deferred FK check on a tuple deleted by a rolled-back subtransaction -- createtable pktable2(f1 intprimarykey); createtable fktable2(f1 intreferences pktable2 deferrable initially deferred); insertinto pktable2 values(1);
-- -- Test that we prevent dropping FK constraint with pending trigger events --
begin; insertinto fktable2 values(2); altertable fktable2 dropconstraint fktable2_f1_fkey; commit;
begin; deletefrom pktable2 where f1 = 1; altertable fktable2 dropconstraint fktable2_f1_fkey; commit;
droptable pktable2, fktable2;
-- -- Test keys that "look" different but compare as equal -- createtable pktable2 (a float8, b float8, primarykey (a, b)); createtable fktable2 (x float8, y float8, foreignkey (x, y) references pktable2 (a, b) onupdatecascade);
select * from pktable2; -- should have updated fktable2.x select * from fktable2;
droptable pktable2, fktable2;
-- -- Foreign keys and partitioned tables --
-- Creation of a partitioned hierarchy with irregular definitions CREATETABLE fk_notpartitioned_pk (fdrop1 int, a int, fdrop2 int, b int, PRIMARYKEY (a, b)); ALTERTABLE fk_notpartitioned_pk DROPCOLUMN fdrop1, DROPCOLUMN fdrop2; CREATETABLE fk_partitioned_fk (b int, fdrop1 int, a int) PARTITION BY RANGE (a, b); ALTERTABLE fk_partitioned_fk DROPCOLUMN fdrop1; CREATETABLE fk_partitioned_fk_1 (fdrop1 int, fdrop2 int, a int, fdrop3 int, b int); ALTERTABLE fk_partitioned_fk_1 DROPCOLUMN fdrop1, DROPCOLUMN fdrop2, DROPCOLUMN fdrop3; ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_1 FORVALUESFROM (0,0) TO (1000,1000); ALTERTABLE fk_partitioned_fk ADDCONSTRAINT fk_partitioned_fk_a_b_fkey FOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk NOT ENFORCED; CREATETABLE fk_partitioned_fk_2 (b int, fdrop1 int, fdrop2 int, a int); ALTERTABLE fk_partitioned_fk_2 DROPCOLUMN fdrop1, DROPCOLUMN fdrop2; ALTERTABLE fk_partitioned_fk_2 ADDCONSTRAINT fk_partitioned_fk_a_b_fkey FOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk NOT ENFORCED; ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FORVALUESFROM (1000,1000) TO (2000,2000); ALTERTABLE fk_partitioned_fk ALTERCONSTRAINT fk_partitioned_fk_a_b_fkey ENFORCED; CREATETABLE fk_partitioned_fk_3 (fdrop1 int, fdrop2 int, fdrop3 int, fdrop4 int, b int, a int)
PARTITION BY HASH (a); ALTERTABLE fk_partitioned_fk_3 DROPCOLUMN fdrop1, DROPCOLUMN fdrop2, DROPCOLUMN fdrop3, DROPCOLUMN fdrop4; CREATETABLE fk_partitioned_fk_3_0 PARTITION OF fk_partitioned_fk_3 FORVALUESWITH (MODULUS 5, REMAINDER 0); CREATETABLE fk_partitioned_fk_3_1 PARTITION OF fk_partitioned_fk_3 FORVALUESWITH (MODULUS 5, REMAINDER 1); ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_3 FORVALUESFROM (2000,2000) TO (3000,3000);
-- Creating a foreign key with ONLY on a partitioned table referencing -- a non-partitioned table fails. ALTERTABLE ONLY fk_partitioned_fk ADDFOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk;
-- these inserts, targeting both the partition directly as well as the -- partitioned table, should all fail INSERTINTO fk_partitioned_fk (a,b) VALUES (500, 501); INSERTINTO fk_partitioned_fk_1 (a,b) VALUES (500, 501); INSERTINTO fk_partitioned_fk (a,b) VALUES (1500, 1501); INSERTINTO fk_partitioned_fk_2 (a,b) VALUES (1500, 1501); INSERTINTO fk_partitioned_fk (a,b) VALUES (2500, 2502); INSERTINTO fk_partitioned_fk_3 (a,b) VALUES (2500, 2502); INSERTINTO fk_partitioned_fk (a,b) VALUES (2501, 2503); INSERTINTO fk_partitioned_fk_3 (a,b) VALUES (2501, 2503);
-- but if we insert the values that make them valid, then they work INSERTINTO fk_notpartitioned_pk VALUES (500, 501), (1500, 1501),
(2500, 2502), (2501, 2503); INSERTINTO fk_partitioned_fk (a,b) VALUES (500, 501); INSERTINTO fk_partitioned_fk (a,b) VALUES (1500, 1501); INSERTINTO fk_partitioned_fk (a,b) VALUES (2500, 2502); INSERTINTO fk_partitioned_fk (a,b) VALUES (2501, 2503);
-- this update fails because there is no referenced row UPDATE fk_partitioned_fk SET a = a + 1WHERE a = 2501; -- but we can fix it thusly: INSERTINTO fk_notpartitioned_pk (a,b) VALUES (2502, 2503); UPDATE fk_partitioned_fk SET a = a + 1WHERE a = 2501;
-- these updates would leave lingering rows in the referencing table; disallow UPDATE fk_notpartitioned_pk SET b = 502WHERE a = 500; UPDATE fk_notpartitioned_pk SET b = 1502WHERE a = 1500; UPDATE fk_notpartitioned_pk SET b = 2504WHERE a = 2500; -- check psql behavior
\d fk_notpartitioned_pk
-- Check the existing FK trigger SELECT conname, tgrelid::regclass as tgrel, regexp_replace(tgname, '[0-9]+', 'N') as tgname, tgtype FROM pg_trigger t JOIN pg_constraint c ON (t.tgconstraint = c.oid) WHERE tgrelid IN (SELECT relid FROM pg_partition_tree('fk_partitioned_fk'::regclass) UNIONALLSELECT'fk_notpartitioned_pk'::regclass) ORDERBY tgrelid, tgtype;
ALTERTABLE fk_partitioned_fk ALTERCONSTRAINT fk_partitioned_fk_a_b_fkey NOT ENFORCED; -- No triggers SELECT conname, tgrelid::regclass as tgrel, regexp_replace(tgname, '[0-9]+', 'N') as tgname, tgtype FROM pg_trigger t JOIN pg_constraint c ON (t.tgconstraint = c.oid) WHERE tgrelid IN (SELECT relid FROM pg_partition_tree('fk_partitioned_fk'::regclass) UNIONALLSELECT'fk_notpartitioned_pk'::regclass) ORDERBY tgrelid, tgtype;
-- Changing it back to ENFORCED will recreate the necessary triggers. ALTERTABLE fk_partitioned_fk ALTERCONSTRAINT fk_partitioned_fk_a_b_fkey ENFORCED;
-- Should be exactly the same number of triggers found as before SELECT conname, tgrelid::regclass as tgrel, regexp_replace(tgname, '[0-9]+', 'N') as tgname, tgtype FROM pg_trigger t JOIN pg_constraint c ON (t.tgconstraint = c.oid) WHERE tgrelid IN (SELECT relid FROM pg_partition_tree('fk_partitioned_fk'::regclass) UNIONALLSELECT'fk_notpartitioned_pk'::regclass) ORDERBY tgrelid, tgtype;
-- Altering a type referenced by a foreign key needs to drop/recreate the FK. -- Ensure that works. CREATETABLE fk_notpartitioned_pk (a INT, PRIMARYKEY(a), CHECK (a > 0)); CREATETABLE fk_partitioned_fk (a INTREFERENCES fk_notpartitioned_pk(a) PRIMARYKEY) PARTITION BY RANGE(a); CREATETABLE fk_partitioned_fk_1 PARTITION OF fk_partitioned_fk FORVALUESFROM (MINVALUE) TO (MAXVALUE); INSERTINTO fk_notpartitioned_pk VALUES (1); INSERTINTO fk_partitioned_fk VALUES (1); ALTERTABLE fk_notpartitioned_pk ALTERCOLUMN a TYPE bigint; DELETEFROM fk_notpartitioned_pk WHERE a = 1; DROPTABLE fk_notpartitioned_pk, fk_partitioned_fk;
-- NOT VALID foreign keys on partitioned table CREATETABLE fk_notpartitioned_pk (a int, b int, PRIMARYKEY (a, b)); CREATETABLE fk_partitioned_fk (b int, a int) PARTITION BY RANGE (a, b); ALTERTABLE fk_partitioned_fk ADDFOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk NOT VALID;
-- Attaching a child table with the same valid foreign key constraint. CREATETABLE fk_partitioned_fk_1 (a int, b int); ALTERTABLE fk_partitioned_fk_1 ADDFOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk; ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_1 FORVALUESFROM (0,0) TO (1000,1000);
-- Child constraint will remain valid. SELECT conname, convalidated, conrelid::regclass FROM pg_constraint WHERE conrelid::regclass::text like'fk_partitioned_fk%'ORDERBY oid::regclass::text;
-- Validate the constraint ALTERTABLE fk_partitioned_fk VALIDATE CONSTRAINT fk_partitioned_fk_a_b_fkey;
-- All constraints are now valid. SELECT conname, convalidated, conrelid::regclass FROM pg_constraint WHERE conrelid::regclass::text like'fk_partitioned_fk%'ORDERBY oid::regclass::text;
-- Attaching a child with a NOT VALID constraint. CREATETABLE fk_partitioned_fk_2 (a int, b int); INSERTINTO fk_partitioned_fk_2 VALUES(1000, 1000); -- doesn't exist in referenced table ALTERTABLE fk_partitioned_fk_2 ADDFOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk NOTVALID;
-- It will fail because the attach operation implicitly validates the data. ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FORVALUESFROM (1000,1000) TO (2000,2000);
-- Remove the invalid data and try again.
TRUNCATE fk_partitioned_fk_2; ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FORVALUESFROM (1000,1000) TO (2000,2000);
-- The child constraint will also be valid. SELECT conname, convalidated FROM pg_constraint WHERE conrelid = 'fk_partitioned_fk_2'::regclass ORDERBY oid::regclass::text;
-- Test case where the child constraint is invalid, the grandchild constraint -- is valid, and the validation for the grandchild should be skipped when a -- valid constraint is applied to the top parent. CREATETABLE fk_partitioned_fk_3 (a int, b int) PARTITION BY RANGE (a, b); ALTERTABLE fk_partitioned_fk_3 ADDFOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk NOTVALID; CREATETABLE fk_partitioned_fk_3_1 (a int, b int); ALTERTABLE fk_partitioned_fk_3_1 ADDFOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk; ALTERTABLE fk_partitioned_fk_3 ATTACH PARTITION fk_partitioned_fk_3_1 FORVALUESFROM (2000,2000) TO (3000,3000); ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_3 FORVALUESFROM (2000,2000) TO (3000,3000);
-- All constraints are now valid. SELECT conname, convalidated, conrelid::regclass FROM pg_constraint WHERE conrelid::regclass::text like'fk_partitioned_fk%'ORDERBY oid::regclass::text;
-- NOT VALID and NOT ENFORCED foreign key on a non-partitioned table -- referencing a partitioned table CREATETABLE fk_partitioned_pk (a int, b int, PRIMARYKEY (a, b)) PARTITION BY RANGE (a, b); CREATETABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FORVALUESFROM (0,0) TO (1000,1000); CREATETABLE fk_partitioned_pk_2 PARTITION OF fk_partitioned_pk FORVALUESFROM (1000,1000) TO (2000,2000); CREATETABLE fk_notpartitioned_fk (b int, a int); INSERTINTO fk_partitioned_pk VALUES(100,100), (1000,1000); INSERTINTO fk_notpartitioned_fk VALUES(100,100), (1000,1000); ALTERTABLE fk_notpartitioned_fk ADDCONSTRAINT fk_notpartitioned_fk_a_b_fkey FOREIGNKEY (a, b) REFERENCES fk_partitioned_pk NOT VALID; ALTERTABLE fk_notpartitioned_fk ADDCONSTRAINT fk_notpartitioned_fk_a_b_fkey2 FOREIGNKEY (a, b) REFERENCES fk_partitioned_pk NOT ENFORCED;
-- All constraints will be invalid, and _fkey2 constraints will not be enforced. SELECT conname, conenforced, convalidated FROM pg_constraint WHERE conrelid = 'fk_notpartitioned_fk'::regclass ORDERBY oid::regclass::text;
-- All constraints are now valid and enforced. SELECT conname, conenforced, convalidated FROM pg_constraint WHERE conrelid = 'fk_notpartitioned_fk'::regclass ORDERBY oid::regclass::text;
-- test a self-referential FK ALTERTABLE fk_partitioned_pk ADDCONSTRAINT selffk FOREIGNKEY (a, b) REFERENCES fk_partitioned_pk NOT VALID; CREATETABLE fk_partitioned_pk_3 PARTITION OF fk_partitioned_pk FORVALUESFROM (2000,2000) TO (3000,3000)
PARTITION BY RANGE (a); CREATETABLE fk_partitioned_pk_3_1 PARTITION OF fk_partitioned_pk_3 FORVALUESFROM (2000) TO (2100); SELECT conname, conenforced, convalidated FROM pg_constraint WHERE conrelid = 'fk_partitioned_pk'::regclass AND contype = 'f' ORDERBY oid::regclass::text; ALTERTABLE fk_partitioned_pk_2 VALIDATE CONSTRAINT selffk; ALTERTABLE fk_partitioned_pk VALIDATE CONSTRAINT selffk; SELECT conname, conenforced, convalidated FROM pg_constraint WHERE conrelid = 'fk_partitioned_pk'::regclass AND contype = 'f' ORDERBY oid::regclass::text;
-- Test some other exotic foreign key features: MATCH SIMPLE, ON UPDATE/DELETE -- actions CREATETABLE fk_notpartitioned_pk (a int, b int, primarykey (a, b)); CREATETABLE fk_partitioned_fk (a intdefault2501, b intdefault142857) PARTITION BY LIST (a); CREATETABLE fk_partitioned_fk_1 PARTITION OF fk_partitioned_fk FORVALUESIN (NULL,500,501,502); ALTERTABLE fk_partitioned_fk ADDFOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk MATCH SIMPLE ONDELETESETNULLONUPDATESETNULL; CREATETABLE fk_partitioned_fk_2 PARTITION OF fk_partitioned_fk FORVALUESIN (1500,1502); CREATETABLE fk_partitioned_fk_3 (a int, b int); ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_3 FORVALUESIN (2500,2501,2502,2503);
-- this insert fails INSERTINTO fk_partitioned_fk (a, b) VALUES (2502, 2503); INSERTINTO fk_partitioned_fk_3 (a, b) VALUES (2502, 2503); -- but since the FK is MATCH SIMPLE, this one doesn't INSERTINTO fk_partitioned_fk_3 (a, b) VALUES (2502, NULL); -- now create the referenced row ... INSERTINTO fk_notpartitioned_pk VALUES (2502, 2503); --- and now the same insert work INSERTINTO fk_partitioned_fk_3 (a, b) VALUES (2502, 2503); -- this always works INSERTINTO fk_partitioned_fk (a,b) VALUES (NULL, NULL);
-- MATCH FULL INSERTINTO fk_notpartitioned_pk VALUES (1, 2); CREATETABLE fk_partitioned_fk_full (x int, y int) PARTITION BY RANGE (x); CREATETABLE fk_partitioned_fk_full_1 PARTITION OF fk_partitioned_fk_full DEFAULT; INSERTINTO fk_partitioned_fk_full VALUES (1, NULL); ALTERTABLE fk_partitioned_fk_full ADDFOREIGNKEY (x, y) REFERENCES fk_notpartitioned_pk MATCH FULL; -- fails
TRUNCATE fk_partitioned_fk_full; ALTERTABLE fk_partitioned_fk_full ADDFOREIGNKEY (x, y) REFERENCES fk_notpartitioned_pk MATCH FULL; INSERTINTO fk_partitioned_fk_full VALUES (1, NULL); -- fails DROPTABLE fk_partitioned_fk_full;
-- ON UPDATE SET NULL SELECT tableoid::regclass, a, b FROM fk_partitioned_fk WHERE b ISNULLORDERBY a; UPDATE fk_notpartitioned_pk SET a = a + 1WHERE a = 2502; SELECT tableoid::regclass, a, b FROM fk_partitioned_fk WHERE b ISNULLORDERBY a;
-- ON DELETE SET NULL INSERTINTO fk_partitioned_fk VALUES (2503, 2503); SELECT count(*) FROM fk_partitioned_fk WHERE a ISNULL; DELETEFROM fk_notpartitioned_pk; SELECT count(*) FROM fk_partitioned_fk WHERE a ISNULL;
-- ON UPDATE/DELETE SET DEFAULT ALTERTABLE fk_partitioned_fk DROPCONSTRAINT fk_partitioned_fk_a_b_fkey; ALTERTABLE fk_partitioned_fk ADDFOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk ONDELETESETDEFAULTONUPDATESETDEFAULT; INSERTINTO fk_notpartitioned_pk VALUES (2502, 2503); INSERTINTO fk_partitioned_fk_3 (a, b) VALUES (2502, 2503); -- this fails, because the defaults for the referencing table are not present -- in the referenced table: UPDATE fk_notpartitioned_pk SET a = 1500WHERE a = 2502; -- but inserting the row we can make it work: INSERTINTO fk_notpartitioned_pk VALUES (2501, 142857); UPDATE fk_notpartitioned_pk SET a = 1500WHERE a = 2502; SELECT * FROM fk_partitioned_fk WHERE b = 142857;
-- ON DELETE SET NULL column_list ALTERTABLE fk_partitioned_fk DROPCONSTRAINT fk_partitioned_fk_a_b_fkey; ALTERTABLE fk_partitioned_fk ADDFOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk ONDELETESETNULL (a);
BEGIN; DELETEFROM fk_notpartitioned_pk WHERE b = 142857; SELECT * FROM fk_partitioned_fk WHERE a ISNOTNULLOR b ISNOTNULLORDERBY a NULLS LAST;
ROLLBACK;
-- ON DELETE SET DEFAULT column_list ALTERTABLE fk_partitioned_fk DROPCONSTRAINT fk_partitioned_fk_a_b_fkey; ALTERTABLE fk_partitioned_fk ADDFOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk ONDELETESETDEFAULT (a);
BEGIN; DELETEFROM fk_partitioned_fk; DELETEFROM fk_notpartitioned_pk; INSERTINTO fk_notpartitioned_pk VALUES (500, 100000), (2501, 100000); INSERTINTO fk_partitioned_fk VALUES (500, 100000); DELETEFROM fk_notpartitioned_pk WHERE a = 500; SELECT * FROM fk_partitioned_fk ORDERBY a;
ROLLBACK;
-- ON UPDATE/DELETE CASCADE ALTERTABLE fk_partitioned_fk DROPCONSTRAINT fk_partitioned_fk_a_b_fkey; ALTERTABLE fk_partitioned_fk ADDFOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk ONDELETECASCADEONUPDATECASCADE; UPDATE fk_notpartitioned_pk SET a = 2502WHERE a = 2501; SELECT * FROM fk_partitioned_fk WHERE b = 142857;
-- Now you see it ... SELECT * FROM fk_partitioned_fk WHERE b = 142857; DELETEFROM fk_notpartitioned_pk WHERE b = 142857; -- now you don't. SELECT * FROM fk_partitioned_fk WHERE a = 142857;
-- verify that DROP works DROPTABLE fk_partitioned_fk_2;
-- Test behavior of the constraint together with attaching and detaching -- partitions. CREATETABLE fk_partitioned_fk_2 PARTITION OF fk_partitioned_fk FORVALUESIN (1500,1502); ALTERTABLE fk_partitioned_fk DETACH PARTITION fk_partitioned_fk_2;
BEGIN; DROPTABLE fk_partitioned_fk; -- constraint should still be there
\d fk_partitioned_fk_2;
ROLLBACK; ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FORVALUESIN (1500,1502); DROPTABLE fk_partitioned_fk_2; CREATETABLE fk_partitioned_fk_2 (b int, c text, a int, FOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk ONUPDATECASCADEONDELETECASCADE); ALTERTABLE fk_partitioned_fk_2 DROPCOLUMN c; ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FORVALUESIN (1500,1502); -- should have only one constraint
\d fk_partitioned_fk_2 DROPTABLE fk_partitioned_fk_2;
CREATETABLE fk_partitioned_fk_2 (b int, a int, CONSTRAINT fk_part_con FOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk ONUPDATECASCADEONDELETECASCADENOT ENFORCED); -- fail -- cannot merge constraints with different enforceability. ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FORVALUESIN (1500,1502); -- If the constraint is modified to match the enforceability of the parent, it will work.
BEGIN; -- change child constraint ALTERTABLE fk_partitioned_fk_2 ALTERCONSTRAINT fk_part_con ENFORCED; ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FORVALUESIN (1500,1502);
\d fk_partitioned_fk_2
ROLLBACK;
BEGIN; -- or change parent constraint ALTERTABLE fk_partitioned_fk ALTERCONSTRAINT fk_partitioned_fk_a_b_fkey NOT ENFORCED; ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FORVALUESIN (1500,1502);
\d fk_partitioned_fk_2
ROLLBACK; DROPTABLE fk_partitioned_fk_2;
CREATETABLE fk_partitioned_fk_4 (a int, b int, FOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ONUPDATECASCADEONDELETECASCADE) PARTITION BY RANGE (b, a); CREATETABLE fk_partitioned_fk_4_1 PARTITION OF fk_partitioned_fk_4 FORVALUESFROM (1,1) TO (100,100); CREATETABLE fk_partitioned_fk_4_2 (a int, b int, FOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ONUPDATESETNULL); ALTERTABLE fk_partitioned_fk_4 ATTACH PARTITION fk_partitioned_fk_4_2 FORVALUESFROM (100,100) TO (1000,1000); ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_4 FORVALUESIN (3500,3502); ALTERTABLE fk_partitioned_fk DETACH PARTITION fk_partitioned_fk_4; ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_4 FORVALUESIN (3500,3502); -- should only have one constraint
\d fk_partitioned_fk_4
\d fk_partitioned_fk_4_1 -- this one has an FK with mismatched properties
\d fk_partitioned_fk_4_2
CREATETABLE fk_partitioned_fk_5 (a int, b int, FOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ONUPDATECASCADEONDELETECASCADEDEFERRABLE, FOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) MATCH FULL ONUPDATECASCADEONDELETECASCADE)
PARTITION BY RANGE (a); CREATETABLE fk_partitioned_fk_5_1 (a int, b int, FOREIGNKEY (a, b) REFERENCES fk_notpartitioned_pk); ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_5 FORVALUESIN (4500); ALTERTABLE fk_partitioned_fk_5 ATTACH PARTITION fk_partitioned_fk_5_1 FORVALUESFROM (0) TO (10); ALTERTABLE fk_partitioned_fk DETACH PARTITION fk_partitioned_fk_5; ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_5 FORVALUESIN (4500); -- this one has two constraints, similar but not quite the one in the parent, -- so it gets a new one
\d fk_partitioned_fk_5 -- verify that it works to reattaching a child with multiple candidate -- constraints ALTERTABLE fk_partitioned_fk_5 DETACH PARTITION fk_partitioned_fk_5_1; ALTERTABLE fk_partitioned_fk_5 ATTACH PARTITION fk_partitioned_fk_5_1 FORVALUESFROM (0) TO (10);
\d fk_partitioned_fk_5_1
-- verify that attaching a table checks that the existing data satisfies the -- constraint CREATETABLE fk_partitioned_fk_2 (a int, b int) PARTITION BY RANGE (b); CREATETABLE fk_partitioned_fk_2_1 PARTITION OF fk_partitioned_fk_2 FORVALUESFROM (0) TO(1000); CREATETABLE fk_partitioned_fk_2_2 PARTITION OF fk_partitioned_fk_2 FORVALUESFROM (1000) TO (2000); INSERTINTO fk_partitioned_fk_2 VALUES (1600, 601), (1600, 1601); ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FORVALUESIN (1600); INSERTINTO fk_notpartitioned_pk VALUES (1600, 601), (1600, 1601); ALTERTABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FORVALUESIN (1600);
-- leave these tables around intentionally
-- Verify that attaching a table that's referenced by an existing FK -- in the parent throws an error CREATETABLE fk_partitioned_pk_6 (a intPRIMARYKEY); CREATETABLE fk_partitioned_fk_6 (a intREFERENCES fk_partitioned_pk_6) PARTITION BY LIST (a); ALTERTABLE fk_partitioned_fk_6 ATTACH PARTITION fk_partitioned_pk_6 FORVALUESIN (1); DROPTABLE fk_partitioned_pk_6, fk_partitioned_fk_6;
-- Verify that attaching to a parent with two identical constraints work CREATETABLE fk_partitioned_pk_6 (a intPRIMARYKEY); CREATETABLE fk_partitioned_fk_6 (a int, FOREIGNKEY (a) REFERENCES fk_partitioned_pk_6, FOREIGNKEY (a) REFERENCES fk_partitioned_pk_6
) PARTITION BY LIST (a); CREATETABLE fk_partitioned_fk_6_1 PARTITION OF fk_partitioned_fk_6 FORVALUESIN (1); ALTERTABLE fk_partitioned_fk_6 DETACH PARTITION fk_partitioned_fk_6_1; ALTERTABLE fk_partitioned_fk_6 ATTACH PARTITION fk_partitioned_fk_6_1 FORVALUESIN (1); DROPTABLE fk_partitioned_pk_6, fk_partitioned_fk_6;
-- This case is similar to above, but the referenced relation is one level -- lower in the hierarchy. This one fails in a different way as the above, -- because we don't bother to protect against this case explicitly. If the -- current error stops happening, we'll need to add a better protection. CREATETABLE fk_partitioned_pk_6 (a intPRIMARYKEY) PARTITION BY list (a); CREATETABLE fk_partitioned_pk_61 PARTITION OF fk_partitioned_pk_6 FORVALUESIN (1); CREATETABLE fk_partitioned_fk_6 (a intREFERENCES fk_partitioned_pk_61) PARTITION BY LIST (a); ALTERTABLE fk_partitioned_fk_6 ATTACH PARTITION fk_partitioned_pk_6 FORVALUESIN (1); DROPTABLE fk_partitioned_pk_6, fk_partitioned_fk_6;
-- test the case when the referenced table is owned by a different user create role regress_other_partitioned_fk_owner; grantreferenceson fk_notpartitioned_pk to regress_other_partitioned_fk_owner; set role regress_other_partitioned_fk_owner; createtable other_partitioned_fk(a int, b int) partition by list (a); createtable other_partitioned_fk_1 partition of other_partitioned_fk forvaluesin (2048); insertinto other_partitioned_fk select2048, x from generate_series(1,10) x; -- this should fail altertable other_partitioned_fk addforeignkey (a, b) references fk_notpartitioned_pk(a, b); -- add the missing keys and retry
reset role; insertinto fk_notpartitioned_pk (a, b) select2048, x from generate_series(1,10) x; set role regress_other_partitioned_fk_owner; altertable other_partitioned_fk addforeignkey (a, b) references fk_notpartitioned_pk(a, b); -- clean up droptable other_partitioned_fk;
reset role; revokeallon fk_notpartitioned_pk from regress_other_partitioned_fk_owner; drop role regress_other_partitioned_fk_owner;
-- -- Test self-referencing foreign key with partition. -- This should create only one fk constraint per partition -- CREATETABLE parted_self_fk (
id bigintNOTNULLPRIMARYKEY,
id_abc bigint, FOREIGNKEY (id_abc) REFERENCES parted_self_fk(id)
)
PARTITION BY RANGE (id); CREATETABLE part1_self_fk (
id bigintNOTNULLPRIMARYKEY,
id_abc bigint
); ALTERTABLE parted_self_fk ATTACH PARTITION part1_self_fk FORVALUESFROM (0) TO (10); CREATETABLE part2_self_fk PARTITION OF parted_self_fk FORVALUESFROM (10) TO (20); CREATETABLE part3_self_fk ( -- a partitioned partition
id bigintNOTNULLPRIMARYKEY,
id_abc bigint
) PARTITION BY RANGE (id); CREATETABLE part32_self_fk PARTITION OF part3_self_fk FORVALUESFROM (20) TO (30); ALTERTABLE parted_self_fk ATTACH PARTITION part3_self_fk FORVALUESFROM (20) TO (40); CREATETABLE part33_self_fk (
id bigintNOTNULLPRIMARYKEY,
id_abc bigint
); ALTERTABLE part3_self_fk ATTACH PARTITION part33_self_fk FORVALUESFROM (30) TO (40);
-- verify that this constraint works INSERTINTO parted_self_fk VALUES (1, NULL), (2, NULL), (3, NULL); INSERTINTO parted_self_fk VALUES (10, 1), (11, 2), (12, 3) RETURNING tableoid::regclass;
SELECT cr.relname, co.conname, co.convalidated,
p.conname AS conparent, p.convalidated, cf.relname AS foreignrel FROM pg_constraint co JOIN pg_class cr ON cr.oid = co.conrelid LEFTJOIN pg_class cf ON cf.oid = co.confrelid LEFTJOIN pg_constraint p ON p.oid = co.conparentid WHERE co.contype = 'f'AND
cr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk')) ORDERBY cr.relname, co.conname, p.conname;
-- detach and re-attach multiple times just to ensure everything is kosher ALTERTABLE parted_self_fk DETACH PARTITION part2_self_fk;
SELECT cr.relname, co.conname, co.convalidated,
p.conname AS conparent, p.convalidated, cf.relname AS foreignrel FROM pg_constraint co JOIN pg_class cr ON cr.oid = co.conrelid LEFTJOIN pg_class cf ON cf.oid = co.confrelid LEFTJOIN pg_constraint p ON p.oid = co.conparentid WHERE co.contype = 'f'AND
cr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk')) ORDERBY cr.relname, co.conname, p.conname;
-- Leave this table around, for pg_upgrade/pg_dump tests
-- Test creating a constraint at the parent that already exists in partitions. -- There should be no duplicated constraints, and attempts to drop the -- constraint in partitions should raise appropriate errors. createschema fkpart0 createtable pkey (a intprimarykey) createtable fk_part (a int) partition by list (a) createtable fk_part_1 partition of fk_part
(foreignkey (a) references fkpart0.pkey) forvaluesin (1) createtable fk_part_23 partition of fk_part
(foreignkey (a) references fkpart0.pkey) forvaluesin (2, 3)
partition by list (a) createtable fk_part_23_2 partition of fk_part_23 forvaluesin (2);
altertable fkpart0.fk_part addforeignkey (a) references fkpart0.pkey;
\d fkpart0.fk_part_1 \\ -- should have only one FK altertable fkpart0.fk_part_1 dropconstraint fk_part_1_a_fkey;
\d fkpart0.fk_part_23 \\ -- should have only one FK
\d fkpart0.fk_part_23_2 \\ -- should have only one FK altertable fkpart0.fk_part_23 dropconstraint fk_part_23_a_fkey; altertable fkpart0.fk_part_23_2 dropconstraint fk_part_23_a_fkey;
createtable fkpart0.fk_part_56 partition of fkpart0.fk_part forvaluesin (5,6) partition by list (a); createtable fkpart0.fk_part_56_5 partition of fkpart0.fk_part_56 forvaluesin (5);
\d fkpart0.fk_part_56 altertable fkpart0.fk_part_56 dropconstraint fk_part_a_fkey; altertable fkpart0.fk_part_56_5 dropconstraint fk_part_a_fkey;
-- verify that attaching and detaching partitions maintains the right set of -- triggers createschema fkpart1 createtable pkey (a intprimarykey) createtable fk_part (a int) partition by list (a) createtable fk_part_1 partition of fk_part forvaluesin (1) partition by list (a) createtable fk_part_1_1 partition of fk_part_1 forvaluesin (1); altertable fkpart1.fk_part addforeignkey (a) references fkpart1.pkey; insertinto fkpart1.fk_part values (1); -- should fail insertinto fkpart1.pkey values (1); insertinto fkpart1.fk_part values (1); deletefrom fkpart1.pkey where a = 1; -- should fail altertable fkpart1.fk_part detach partition fkpart1.fk_part_1; createtable fkpart1.fk_part_1_2 partition of fkpart1.fk_part_1 forvaluesin (2); insertinto fkpart1.fk_part_1 values (2); -- should fail deletefrom fkpart1.pkey where a = 1;
-- verify that attaching and detaching partitions manipulates the inheritance -- properties of their FK constraints correctly createschema fkpart2 createtable pkey (a intprimarykey) createtable fk_part (a int, constraint fkey foreignkey (a) references fkpart2.pkey) partition by list (a) createtable fk_part_1 partition of fkpart2.fk_part forvaluesin (1) partition by list (a) createtable fk_part_1_1 (a int, constraint my_fkey foreignkey (a) references fkpart2.pkey); altertable fkpart2.fk_part_1 attach partition fkpart2.fk_part_1_1 forvaluesin (1); altertable fkpart2.fk_part_1 dropconstraint fkey; -- should fail altertable fkpart2.fk_part_1_1 dropconstraint my_fkey; -- should fail altertable fkpart2.fk_part detach partition fkpart2.fk_part_1; altertable fkpart2.fk_part_1 dropconstraint fkey; -- ok altertable fkpart2.fk_part_1_1 dropconstraint my_fkey; -- doesn't exist
-- verify constraint deferrability createschema fkpart3 createtable pkey (a intprimarykey) createtable fk_part (a int, constraint fkey foreignkey (a) references fkpart3.pkey deferrable initially immediate) partition by list (a) createtable fk_part_1 partition of fkpart3.fk_part forvaluesin (1) partition by list (a) createtable fk_part_1_1 partition of fkpart3.fk_part_1 forvaluesin (1) createtable fk_part_2 partition of fkpart3.fk_part forvaluesin (2);
begin; set constraints fkpart3.fkey deferred; insertinto fkpart3.fk_part values (1); insertinto fkpart3.pkey values (1); commit;
begin; set constraints fkpart3.fkey deferred; deletefrom fkpart3.pkey; deletefrom fkpart3.fk_part; commit;
-- 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. CREATESCHEMA fkpart3; SET search_path TO fkpart3;
CREATETABLE pk (a intPRIMARYKEY) PARTITION BY RANGE (a); CREATETABLE pk1 PARTITION OF pk FORVALUESFROM (0) TO (1000); CREATETABLE pk2 (b int, a int); ALTERTABLE pk2 DROPCOLUMN b; ALTERTABLE pk2 ALTER a SETNOTNULL; ALTERTABLE pk ATTACH PARTITION pk2 FORVALUESFROM (1000) TO (2000);
CREATETABLE fk (a int) PARTITION BY RANGE (a); CREATETABLE fk1 PARTITION OF fk FORVALUESFROM (0) TO (750); ALTERTABLE fk ADDFOREIGNKEY (a) REFERENCES pk; CREATETABLE fk2 (b int, a int) ; ALTERTABLE fk2 DROPCOLUMN b; ALTERTABLE fk ATTACH PARTITION fk2 FORVALUESFROM (750) TO (3500);
CREATETABLE pk3 PARTITION OF pk FORVALUESFROM (2000) TO (3000); CREATETABLE pk4 (LIKE pk); ALTERTABLE pk ATTACH PARTITION pk4 FORVALUESFROM (3000) TO (4000);
CREATETABLE pk5 (c int, b int, a intNOTNULL) PARTITION BY RANGE (a); ALTERTABLE pk5 DROPCOLUMN b, DROPCOLUMN c; CREATETABLE pk51 PARTITION OF pk5 FORVALUESFROM (4000) TO (4500); CREATETABLE pk52 PARTITION OF pk5 FORVALUESFROM (4500) TO (5000); ALTERTABLE pk ATTACH PARTITION pk5 FORVALUESFROM (4000) TO (5000);
CREATETABLE fk3 PARTITION OF fk FORVALUESFROM (3500) TO (5000);
-- these should fail: referenced value not present INSERTinto fk VALUES (1); INSERTinto fk VALUES (1000); INSERTinto fk VALUES (2000); INSERTinto fk VALUES (3000); INSERTinto fk VALUES (4000); INSERTinto fk VALUES (4500); -- insert into the referenced table, now they should work INSERTinto pk VALUES (1), (1000), (2000), (3000), (4000), (4500); INSERTinto fk VALUES (1), (1000), (2000), (3000), (4000), (4500);
-- should fail: referencing value present DELETEFROM pk WHERE a = 1; DELETEFROM pk WHERE a = 1000; DELETEFROM pk WHERE a = 2000; DELETEFROM pk WHERE a = 3000; DELETEFROM pk WHERE a = 4000; DELETEFROM pk WHERE a = 4500; UPDATE pk SET a = 2WHERE a = 1; UPDATE pk SET a = 1002WHERE a = 1000; UPDATE pk SET a = 2002WHERE a = 2000; UPDATE pk SET a = 3002WHERE a = 3000; UPDATE pk SET a = 4002WHERE a = 4000; UPDATE pk SET a = 4502WHERE a = 4500; -- now they should work DELETEFROM fk; UPDATE pk SET a = 2WHERE a = 1; DELETEFROM pk WHERE a = 2; UPDATE pk SET a = 1002WHERE a = 1000; DELETEFROM pk WHERE a = 1002; UPDATE pk SET a = 2002WHERE a = 2000; DELETEFROM pk WHERE a = 2002; UPDATE pk SET a = 3002WHERE a = 3000; DELETEFROM pk WHERE a = 3002; UPDATE pk SET a = 4002WHERE a = 4000; DELETEFROM pk WHERE a = 4002; UPDATE pk SET a = 4502WHERE a = 4500; DELETEFROM pk WHERE a = 4502;
-- Also, detaching a partition that has the FK itself should work -- https://postgr.es/m/CAAJ_b97GuPh6wQPbxQS-Zpy16Oh+0aMv-w64QcGrLhCOZZ6p+g@mail.gmail.com CREATETABLE ffk (a int, b intREFERENCES pk) PARTITION BY list (a); CREATETABLE ffk1 PARTITION OF ffk FORVALUESIN (1); ALTERTABLE ffk1 ADDFOREIGNKEY (a) REFERENCES pk; ALTERTABLE ffk DETACH PARTITION ffk1; DROPTABLE ffk, ffk1;
CREATESCHEMA fkpart4; SET search_path TO fkpart4; -- dropping/detaching PARTITIONs is prevented if that would break -- a foreign key's existing data CREATETABLE droppk (a intPRIMARYKEY) PARTITION BY RANGE (a); CREATETABLE droppk1 PARTITION OF droppk FORVALUESFROM (0) TO (1000); CREATETABLE droppk_d PARTITION OF droppk DEFAULT; CREATETABLE droppk2 PARTITION OF droppk FORVALUESFROM (1000) TO (2000)
PARTITION BY RANGE (a); CREATETABLE droppk21 PARTITION OF droppk2 FORVALUESFROM (1000) TO (1400); CREATETABLE droppk2_d PARTITION OF droppk2 DEFAULT; INSERTinto droppk VALUES (1), (1000), (1500), (2000); CREATETABLE dropfk (a intREFERENCES droppk); INSERTinto dropfk VALUES (1), (1000), (1500), (2000); -- these should all fail ALTERTABLE droppk DETACH PARTITION droppk_d; ALTERTABLE droppk2 DETACH PARTITION droppk2_d; ALTERTABLE droppk DETACH PARTITION droppk1; ALTERTABLE droppk DETACH PARTITION droppk2; ALTERTABLE droppk2 DETACH PARTITION droppk21; -- dropping partitions is disallowed DROPTABLE droppk_d; DROPTABLE droppk2_d; DROPTABLE droppk1; DROPTABLE droppk2; DROPTABLE droppk21; DELETEFROM dropfk; -- dropping partitions is disallowed, even when no referencing values DROPTABLE droppk_d; DROPTABLE droppk2_d; DROPTABLE droppk1; -- but DETACH is allowed, and DROP afterwards works ALTERTABLE droppk2 DETACH PARTITION droppk21; DROPTABLE droppk2;
-- Verify that initial constraint creation and cloning behave correctly CREATESCHEMA fkpart5; SET search_path TO fkpart5; CREATETABLE pk (a intPRIMARYKEY) PARTITION BY LIST (a); CREATETABLE pk1 PARTITION OF pk FORVALUESIN (1) PARTITION BY LIST (a); CREATETABLE pk11 PARTITION OF pk1 FORVALUESIN (1); CREATETABLE fk (a int) PARTITION BY LIST (a); CREATETABLE fk1 PARTITION OF fk FORVALUESIN (1) PARTITION BY LIST (a); CREATETABLE fk11 PARTITION OF fk1 FORVALUESIN (1); ALTERTABLE fk ADDFOREIGNKEY (a) REFERENCES pk; CREATETABLE pk2 PARTITION OF pk FORVALUESIN (2); CREATETABLE pk3 (a intNOTNULL) PARTITION BY LIST (a); CREATETABLE pk31 PARTITION OF pk3 FORVALUESIN (31); CREATETABLE pk32 (b int, a intNOTNULL); ALTERTABLE pk32 DROPCOLUMN b; ALTERTABLE pk3 ATTACH PARTITION pk32 FORVALUESIN (32); ALTERTABLE pk ATTACH PARTITION pk3 FORVALUESIN (31, 32); CREATETABLE fk2 PARTITION OF fk FORVALUESIN (2); CREATETABLE fk3 (b int, a int); ALTERTABLE fk3 DROPCOLUMN b; ALTERTABLE fk ATTACH PARTITION fk3 FORVALUESIN (3); SELECT pg_describe_object('pg_constraint'::regclass, oid, 0), confrelid::regclass, CASEWHEN conparentid <> 0THEN 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')) ORDERBY conrelid::regclass::text, conname; CREATETABLE fk4 (LIKE fk); INSERTINTO fk4 VALUES (50); ALTERTABLE fk ATTACH PARTITION fk4 FORVALUESIN (50);
-- Verify constraint deferrability CREATESCHEMA fkpart9; SET search_path TO fkpart9; CREATETABLE pk (a intPRIMARYKEY) PARTITION BY LIST (a); CREATETABLE pk1 PARTITION OF pk FORVALUESIN (1, 2) PARTITION BY LIST (a); CREATETABLE pk11 PARTITION OF pk1 FORVALUESIN (1); CREATETABLE pk3 PARTITION OF pk FORVALUESIN (3); CREATETABLE fk (a intREFERENCES pk DEFERRABLE INITIALLY IMMEDIATE); INSERTINTO fk VALUES (1); -- should fail
BEGIN; SET CONSTRAINTS fk_a_fkey DEFERRED; INSERTINTO fk VALUES (1); COMMIT; -- should fail
BEGIN; SET CONSTRAINTS fk_a_fkey DEFERRED; INSERTINTO fk VALUES (1); INSERTINTO pk VALUES (1); COMMIT; -- OK
BEGIN; SET CONSTRAINTS fk_a_fkey DEFERRED; DELETEFROM pk WHERE a = 1; DELETEFROM fk WHERE a = 1; COMMIT; -- OK
-- Verify constraint deferrability when changed by ALTER -- Partitioned table at referencing end CREATETABLE pt(f1 int, f2 int, f3 int, PRIMARYKEY(f1,f2)); CREATETABLE ref(f1 int, f2 int, f3 int)
PARTITION BY list(f1); CREATETABLE ref1 PARTITION OF ref FORVALUESIN (1); CREATETABLE ref2 PARTITION OF ref FORVALUESin (2); ALTERTABLE ref ADDFOREIGNKEY(f1,f2) REFERENCES pt; ALTERTABLE ref ALTERCONSTRAINT ref_f1_f2_fkey
DEFERRABLE INITIALLY DEFERRED; INSERTINTO pt VALUES(1,2,3); INSERTINTO ref VALUES(1,2,3);
BEGIN; DELETEFROM pt; DELETEFROM ref;
ABORT; DROPTABLE pt, ref; -- Multi-level partitioning at referencing end CREATETABLE pt(f1 int, f2 int, f3 int, PRIMARYKEY(f1,f2)); CREATETABLE ref(f1 int, f2 int, f3 int)
PARTITION BY list(f1); CREATETABLE ref1_2 PARTITION OF ref FORVALUESIN (1, 2) PARTITION BY list (f2); CREATETABLE ref1 PARTITION OF ref1_2 FORVALUESIN (1); CREATETABLE ref2 PARTITION OF ref1_2 FORVALUESIN (2) PARTITION BY list (f2); CREATETABLE ref22 PARTITION OF ref2 FORVALUESIN (2); ALTERTABLE ref ADDFOREIGNKEY(f1,f2) REFERENCES pt; INSERTINTO pt VALUES(1,2,3); INSERTINTO ref VALUES(1,2,3); ALTERTABLE ref22 ALTERCONSTRAINT ref_f1_f2_fkey
DEFERRABLE INITIALLY IMMEDIATE; -- fails ALTERTABLE ref ALTERCONSTRAINT ref_f1_f2_fkey
DEFERRABLE INITIALLY DEFERRED;
BEGIN; DELETEFROM pt; DELETEFROM ref;
ABORT; DROPTABLE pt, ref;
-- Verify ON UPDATE/DELETE behavior CREATESCHEMA fkpart6; SET search_path TO fkpart6; CREATETABLE pk (a intPRIMARYKEY) PARTITION BY RANGE (a); CREATETABLE pk1 PARTITION OF pk FORVALUESFROM (1) TO (100) PARTITION BY RANGE (a); CREATETABLE pk11 PARTITION OF pk1 FORVALUESFROM (1) TO (50); CREATETABLE pk12 PARTITION OF pk1 FORVALUESFROM (50) TO (100); CREATETABLE fk (a int) PARTITION BY RANGE (a); CREATETABLE fk1 PARTITION OF fk FORVALUESFROM (1) TO (100) PARTITION BY RANGE (a); CREATETABLE fk11 PARTITION OF fk1 FORVALUESFROM (1) TO (10); CREATETABLE fk12 PARTITION OF fk1 FORVALUESFROM (10) TO (100); ALTERTABLE fk ADDFOREIGNKEY (a) REFERENCES pk ONUPDATECASCADEONDELETECASCADE; CREATETABLE fk_d PARTITION OF fk DEFAULT; INSERTINTO pk VALUES (1); INSERTINTO fk VALUES (1); UPDATE pk SET a = 20; SELECT tableoid::regclass, * FROM fk; DELETEFROM pk WHERE a = 20; SELECT tableoid::regclass, * FROM fk; DROPTABLE fk;
TRUNCATE TABLE pk; INSERTINTO pk VALUES (20), (50); CREATETABLE fk (a int) PARTITION BY RANGE (a); CREATETABLE fk1 PARTITION OF fk FORVALUESFROM (1) TO (100) PARTITION BY RANGE (a); CREATETABLE fk11 PARTITION OF fk1 FORVALUESFROM (1) TO (10); CREATETABLE fk12 PARTITION OF fk1 FORVALUESFROM (10) TO (100); ALTERTABLE fk ADDFOREIGNKEY (a) REFERENCES pk ONUPDATESETNULLONDELETESETNULL; CREATETABLE fk_d PARTITION OF fk DEFAULT; INSERTINTO fk VALUES (20), (50); UPDATE pk SET a = 21WHERE a = 20; DELETEFROM pk WHERE a = 50; SELECT tableoid::regclass, * FROM fk; DROPTABLE fk;
TRUNCATE TABLE pk; INSERTINTO pk VALUES (20), (30), (50); CREATETABLE fk (id int, a intDEFAULT50) PARTITION BY RANGE (a); CREATETABLE fk1 PARTITION OF fk FORVALUESFROM (1) TO (100) PARTITION BY RANGE (a); CREATETABLE fk11 PARTITION OF fk1 FORVALUESFROM (1) TO (10); CREATETABLE fk12 PARTITION OF fk1 FORVALUESFROM (10) TO (100); ALTERTABLE fk ADDFOREIGNKEY (a) REFERENCES pk ONUPDATESETDEFAULTONDELETESETDEFAULT; CREATETABLE fk_d PARTITION OF fk DEFAULT; INSERTINTO fk VALUES (1, 20), (2, 30); DELETEFROM pk WHERE a = 20 RETURNING *; UPDATE pk SET a = 90WHERE a = 30 RETURNING *; SELECT tableoid::regclass, * FROM fk; DROPTABLE fk;
TRUNCATE TABLE pk; INSERTINTO pk VALUES (20), (30); CREATETABLE fk (a intDEFAULT50) PARTITION BY RANGE (a); CREATETABLE fk1 PARTITION OF fk FORVALUESFROM (1) TO (100) PARTITION BY RANGE (a); CREATETABLE fk11 PARTITION OF fk1 FORVALUESFROM (1) TO (10); CREATETABLE fk12 PARTITION OF fk1 FORVALUESFROM (10) TO (100); ALTERTABLE fk ADDFOREIGNKEY (a) REFERENCES pk ONUPDATERESTRICTONDELETERESTRICT; CREATETABLE fk_d PARTITION OF fk DEFAULT; INSERTINTO fk VALUES (20), (30); DELETEFROM pk WHERE a = 20; UPDATE pk SET a = 90WHERE a = 30; SELECT tableoid::regclass, * FROM fk; DROPTABLE fk;
-- test for reported bug: relispartition not set -- https://postgr.es/m/CA+HiwqHMsRtRYRWYTWavKJ8x14AFsv7bmAV46mYwnfD3vy8goQ@mail.gmail.com CREATESCHEMA fkpart7 CREATETABLE pkpart (a int) PARTITION BY LIST (a) CREATETABLE pkpart1 PARTITION OF pkpart FORVALUESIN (1); ALTERTABLE fkpart7.pkpart1 ADDPRIMARYKEY (a); ALTERTABLE fkpart7.pkpart ADDPRIMARYKEY (a); CREATETABLE fkpart7.fk (a intREFERENCES fkpart7.pkpart); DROPSCHEMA fkpart7 CASCADE;
-- ensure we check partitions are "not used" when dropping constraints CREATESCHEMA fkpart8 CREATETABLE tbl1(f1 intPRIMARYKEY) CREATETABLE tbl2(f1 intREFERENCES tbl1 DEFERRABLE INITIALLY DEFERRED) PARTITION BY RANGE(f1) CREATETABLE tbl2_p1 PARTITION OF tbl2 FORVALUESFROM (minvalue) TO (maxvalue); INSERTINTO fkpart8.tbl1 VALUES(1);
BEGIN; INSERTINTO fkpart8.tbl2 VALUES(1); ALTERTABLE fkpart8.tbl2 DROPCONSTRAINT tbl2_f1_fkey; COMMIT; DROPSCHEMA fkpart8 CASCADE;
-- ensure FK referencing a multi-level partitioned table are -- enforce reference to sub-children. CREATESCHEMA fkpart9 CREATETABLE pk (a INTPRIMARYKEY) PARTITION BY RANGE (a) CREATETABLE fk (
fk_a INTREFERENCES pk(a) ONDELETECASCADE
) CREATETABLE pk1 PARTITION OF pk FORVALUESFROM (30) TO (50) PARTITION BY RANGE (a) CREATETABLE pk11 PARTITION OF pk1 FORVALUESFROM (30) TO (40); INSERTINTO fkpart9.pk VALUES (35); INSERTINTO fkpart9.fk VALUES (35); DELETEFROM fkpart9.pk WHERE a=35; SELECT * FROM fkpart9.pk; SELECT * FROM fkpart9.fk; DROPSCHEMA fkpart9 CASCADE;
-- test that ri_Check_Pk_Match() scans the correct partition for a deferred -- ON DELETE/UPDATE NO ACTION constraint CREATESCHEMA fkpart10 CREATETABLE tbl1(f1 intPRIMARYKEY) PARTITION BY RANGE(f1) CREATETABLE tbl1_p1 PARTITION OF tbl1 FORVALUESFROM (minvalue) TO (1) CREATETABLE tbl1_p2 PARTITION OF tbl1 FORVALUESFROM (1) TO (maxvalue) CREATETABLE tbl2(f1 intREFERENCES tbl1 DEFERRABLE INITIALLY DEFERRED) CREATETABLE tbl3(f1 intPRIMARYKEY) PARTITION BY RANGE(f1) CREATETABLE tbl3_p1 PARTITION OF tbl3 FORVALUESFROM (minvalue) TO (1) CREATETABLE tbl3_p2 PARTITION OF tbl3 FORVALUESFROM (1) TO (maxvalue) CREATETABLE tbl4(f1 intREFERENCES tbl3 DEFERRABLE INITIALLY DEFERRED); INSERTINTO fkpart10.tbl1 VALUES (0), (1); INSERTINTO fkpart10.tbl2 VALUES (0), (1); INSERTINTO fkpart10.tbl3 VALUES (-2), (-1), (0); INSERTINTO fkpart10.tbl4 VALUES (-2), (-1);
BEGIN; DELETEFROM fkpart10.tbl1 WHERE f1 = 0; UPDATE fkpart10.tbl1 SET f1 = 2WHERE f1 = 1; INSERTINTO fkpart10.tbl1 VALUES (0), (1); COMMIT;
-- test that cross-partition updates correctly enforces the foreign key -- restriction (specifically testing INITIALLY DEFERRED)
BEGIN; UPDATE fkpart10.tbl1 SET f1 = 3WHERE f1 = 0; UPDATE fkpart10.tbl3 SET f1 = f1 * -1; INSERTINTO fkpart10.tbl1 VALUES (4); COMMIT;
BEGIN; UPDATE fkpart10.tbl3 SET f1 = f1 * -1; UPDATE fkpart10.tbl3 SET f1 = f1 + 3; UPDATE fkpart10.tbl1 SET f1 = 3WHERE f1 = 0; INSERTINTO fkpart10.tbl1 VALUES (0); COMMIT;
BEGIN; UPDATE fkpart10.tbl3 SET f1 = f1 * -1; UPDATE fkpart10.tbl1 SET f1 = 3WHERE f1 = 0; INSERTINTO fkpart10.tbl1 VALUES (0); INSERTINTO fkpart10.tbl3 VALUES (-2), (-1); COMMIT;
-- test where the updated table now has both an IMMEDIATE and a DEFERRED -- constraint pointing into it CREATETABLE fkpart10.tbl5(f1 intREFERENCES fkpart10.tbl3); INSERTINTO fkpart10.tbl5 VALUES (-2), (-1);
BEGIN; UPDATE fkpart10.tbl3 SET f1 = f1 * -3; COMMIT;
-- Now test where the row referenced from the table with an IMMEDIATE -- constraint stays in place, while those referenced from the table with a -- DEFERRED constraint don't. DELETEFROM fkpart10.tbl5; INSERTINTO fkpart10.tbl5 VALUES (0);
BEGIN; UPDATE fkpart10.tbl3 SET f1 = f1 * -3; COMMIT;
DROPSCHEMA fkpart10 CASCADE;
-- verify foreign keys are enforced during cross-partition updates, -- especially on the PK side CREATESCHEMA fkpart11 CREATETABLE pk (a INTPRIMARYKEY, b text) PARTITION BY LIST (a) CREATETABLE fk (
a INT, CONSTRAINT fkey FOREIGNKEY (a) REFERENCES pk(a) ONUPDATECASCADEONDELETECASCADE
) CREATETABLE fk_parted (
a INTPRIMARYKEY, CONSTRAINT fkey FOREIGNKEY (a) REFERENCES pk(a) ONUPDATECASCADEONDELETECASCADE
) PARTITION BY LIST (a) CREATETABLE fk_another (
a INT, CONSTRAINT fkey FOREIGNKEY (a) REFERENCES fk_parted (a) ONUPDATECASCADEONDELETECASCADE
) CREATETABLE pk1 PARTITION OF pk FORVALUESIN (1, 2) PARTITION BY LIST (a) CREATETABLE pk2 PARTITION OF pk FORVALUESIN (3) CREATETABLE pk3 PARTITION OF pk FORVALUESIN (4) CREATETABLE fk1 PARTITION OF fk_parted FORVALUESIN (1, 2) CREATETABLE fk2 PARTITION OF fk_parted FORVALUESIN (3) CREATETABLE fk3 PARTITION OF fk_parted FORVALUESIN (4); CREATETABLE fkpart11.pk11 (b text, a intNOTNULL); ALTERTABLE fkpart11.pk1 ATTACH PARTITION fkpart11.pk11 FORVALUESIN (1); CREATETABLE fkpart11.pk12 (b text, c int, a intNOTNULL); ALTERTABLE fkpart11.pk12 DROP c; ALTERTABLE fkpart11.pk1 ATTACH PARTITION fkpart11.pk12 FORVALUESIN (2); INSERTINTO fkpart11.pk VALUES (1, 'xxx'), (3, 'yyy'); INSERTINTO fkpart11.fk VALUES (1), (3); INSERTINTO fkpart11.fk_parted VALUES (1), (3); INSERTINTO fkpart11.fk_another VALUES (1), (3); -- moves 2 rows from one leaf partition to another, with both updates being -- cascaded to fk and fk_parted. Updates of fk_parted, of which one is -- cross-partition (3 -> 4), are further cascaded to fk_another. UPDATE fkpart11.pk SET a = a + 1 RETURNING tableoid::pg_catalog.regclass, *; SELECT tableoid::pg_catalog.regclass, * FROM fkpart11.fk; SELECT tableoid::pg_catalog.regclass, * FROM fkpart11.fk_parted; SELECT tableoid::pg_catalog.regclass, * FROM fkpart11.fk_another;
-- let's try with the foreign key pointing at tables in the partition tree -- that are not the same as the query's target table
-- 1. foreign key pointing into a non-root ancestor -- -- A cross-partition update on the root table will fail, because we currently -- can't enforce the foreign keys pointing into a non-leaf partition ALTERTABLE fkpart11.fk DROPCONSTRAINT fkey; DELETEFROM fkpart11.fk WHERE a = 4; ALTERTABLE fkpart11.fk ADDCONSTRAINT fkey FOREIGNKEY (a) REFERENCES fkpart11.pk1 (a) ONUPDATECASCADEONDELETECASCADE; UPDATE fkpart11.pk SET a = a - 1; -- it's okay though if the non-leaf partition is updated directly UPDATE fkpart11.pk1 SET a = a - 1; SELECT tableoid::pg_catalog.regclass, * FROM fkpart11.pk; SELECT tableoid::pg_catalog.regclass, * FROM fkpart11.fk; SELECT tableoid::pg_catalog.regclass, * FROM fkpart11.fk_parted; SELECT tableoid::pg_catalog.regclass, * FROM fkpart11.fk_another;
-- 2. foreign key pointing into a single leaf partition -- -- A cross-partition update that deletes from the pointed-to leaf partition -- is allowed to succeed ALTERTABLE fkpart11.fk DROPCONSTRAINT fkey; ALTERTABLE fkpart11.fk ADDCONSTRAINT fkey FOREIGNKEY (a) REFERENCES fkpart11.pk11 (a) ONUPDATECASCADEONDELETECASCADE; -- will delete (1) from p11 which is cascaded to fk UPDATE fkpart11.pk SET a = a + 1WHERE a = 1; SELECT tableoid::pg_catalog.regclass, * FROM fkpart11.fk; DROPTABLE fkpart11.fk;
-- check that regular and deferrable AR triggers on the PK tables -- still work as expected CREATE FUNCTION fkpart11.print_row () RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
RAISE NOTICE 'TABLE: %, OP: %, OLD: %, NEW: %', TG_RELNAME, TG_OP, OLD, NEW; RETURNNULL;
END;
$$; CREATETRIGGER trig_upd_pk AFTER UPDATEON fkpart11.pk FOREACH ROW EXECUTE FUNCTION fkpart11.print_row(); CREATETRIGGER trig_del_pk AFTER DELETEON fkpart11.pk FOREACH ROW EXECUTE FUNCTION fkpart11.print_row(); CREATETRIGGER trig_ins_pk AFTER INSERTON fkpart11.pk FOREACH ROW EXECUTE FUNCTION fkpart11.print_row(); CREATECONSTRAINTTRIGGER trig_upd_fk_parted AFTER UPDATEON fkpart11.fk_parted INITIALLY DEFERRED FOREACH ROW EXECUTE FUNCTION fkpart11.print_row(); CREATECONSTRAINTTRIGGER trig_del_fk_parted AFTER DELETEON fkpart11.fk_parted INITIALLY DEFERRED FOREACH ROW EXECUTE FUNCTION fkpart11.print_row(); CREATECONSTRAINTTRIGGER trig_ins_fk_parted AFTER INSERTON fkpart11.fk_parted INITIALLY DEFERRED FOREACH ROW EXECUTE FUNCTION fkpart11.print_row(); UPDATE fkpart11.pk SET a = 3WHERE a = 4; UPDATE fkpart11.pk SET a = 1WHERE a = 2;
DROPSCHEMA fkpart11 CASCADE;
-- When a table is attached as partition to a partitioned table that has -- a foreign key to another partitioned table, it acquires a clone of the -- FK. Upon detach, this clone is not removed, but instead becomes an -- independent FK. If it then attaches to the partitioned table again, -- the FK from the parent "takes over" ownership of the independent FK rather -- than creating a separate one. CREATESCHEMA fkpart12 CREATETABLE fk_p ( id int, jd int, PRIMARYKEY(id, jd)) PARTITION BY list (id) CREATETABLE fk_p_1 PARTITION OF fk_p FORVALUESIN (1) PARTITION BY list (jd) CREATETABLE fk_p_1_1 PARTITION OF fk_p_1 FORVALUESIN (1) CREATETABLE fk_p_1_2 (x int, y int, jd intNOTNULL, id intNOTNULL) CREATETABLE fk_p_2 PARTITION OF fk_p FORVALUESIN (2) PARTITION BY list (jd) CREATETABLE fk_p_2_1 PARTITION OF fk_p_2 FORVALUESIN (1) CREATETABLE fk_p_2_2 PARTITION OF fk_p_2 FORVALUESIN (2) CREATETABLE fk_r_1 ( p_jd intNOTNULL, x int, id intPRIMARYKEY, p_id intNOTNULL) CREATETABLE fk_r_2 ( id intPRIMARYKEY, p_id intNOTNULL, p_jd intNOTNULL) PARTITION BY list (id) CREATETABLE fk_r_2_1 PARTITION OF fk_r_2 FORVALUESIN (2, 1) CREATETABLE fk_r ( id intPRIMARYKEY, p_id intNOTNULL, p_jd intNOTNULL, FOREIGNKEY (p_id, p_jd) REFERENCES fk_p (id, jd)
) PARTITION BY list (id); SET search_path TO fkpart12;
-- these should all fail ALTERTABLE fk_r_1 DROPCONSTRAINT fk_r_p_id_p_jd_fkey; ALTERTABLE fk_r DROPCONSTRAINT fk_r_p_id_p_jd_fkey_1; ALTERTABLE fk_r_2 DROPCONSTRAINT fk_r_p_id_p_jd_fkey;
SET client_min_messages TO warning; DROPSCHEMA fkpart12 CASCADE;
RESET client_min_messages;
RESET search_path;
-- Exercise the column mapping code with foreign keys. In this test we'll -- create a partitioned table which has a partition with a dropped column and -- check to ensure that an UPDATE cascades the changes correctly to the -- partitioned table. CREATESCHEMA fkpart13; SET search_path TO fkpart13;
CREATETABLE fkpart13_t1 (a intPRIMARYKEY);
CREATETABLE fkpart13_t2 (
part_id intPRIMARYKEY,
column_to_drop int, FOREIGNKEY (part_id) REFERENCES fkpart13_t1 ONUPDATECASCADEONDELETECASCADE
) PARTITION BY LIST (part_id);
CREATETABLE fkpart13_t2_p1 PARTITION OF fkpart13_t2 FORVALUESIN (1);
-- drop the column ALTERTABLE fkpart13_t2 DROPCOLUMN column_to_drop;
-- create a new partition without the dropped column CREATETABLE fkpart13_t2_p2 PARTITION OF fkpart13_t2 FORVALUESIN (2);
-- Test a cascading update works correctly with with the dropped column UPDATE fkpart13_t1 SET a = 2WHERE a = 1; SELECT tableoid::regclass,* FROM fkpart13_t2; SELECT tableoid::regclass,* FROM fkpart13_t3;
-- Exercise code in ExecGetTriggerResultRel() as there's been previous issues -- with ResultRelInfos being returned with the incorrect ri_RootResultRelInfo WITH cte AS ( UPDATE fkpart13_t2_p1 SET part_id = part_id
) UPDATE fkpart13_t1 SET a = 2WHERE a = 1;
DROPSCHEMA fkpart13 CASCADE;
RESET search_path;
Messung V0.5 in Prozent
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.128Bemerkung:
(vorverarbeitet am 2026-08-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.