Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtp (Exim 4.84_2) (envelope-from ) id 1eVjbo-0000MW-Vm for pgsql-hackers@arkaria.postgresql.org; Sun, 31 Dec 2017 19:49:25 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.84_2) (envelope-from ) id 1eVjbo-0002Kb-4Y for pgsql-hackers@arkaria.postgresql.org; Sun, 31 Dec 2017 19:49:24 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.84_2) (envelope-from ) id 1eVjbn-0002KR-Pq for pgsql-hackers@lists.postgresql.org; Sun, 31 Dec 2017 19:49:23 +0000 Received: from smtprelay0174.b.hostedemail.com ([64.98.42.174] helo=smtprelay.b.hostedemail.com) by magus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.89) (envelope-from ) id 1eVjbg-0005Z5-W8 for pgsql-hackers@postgresql.org; Sun, 31 Dec 2017 19:49:23 +0000 Received: from filter.hostedemail.com (10.5.19.248.rfc1918.com [10.5.19.248]) by smtprelay02.b.hostedemail.com (Postfix) with ESMTP id 50361D3813 for ; Sun, 31 Dec 2017 19:49:13 +0000 (UTC) X-Session-Marker: 616C76686572726540616C76682E6E6F2D69702E6F7267 X-Spam-Summary: 40,2.5,0,,d41d8cd98f00b204,alvherre@alvh.no-ip.org,:,RULES_HIT:41:355:379:599:967:973:988:989:1260:1311:1312:1313:1314:1345:1359:1381:1515:1516:1518:1519:1533:1536:1575:1594:1595:1596:1711:1714:1730:1747:1764:1777:1792:2393:2525:2560:2563:2682:2685:2859:2933:2937:2939:2942:2945:2947:2951:2954:3022:3138:3139:3140:3141:3142:3876:3877:3934:3936:3938:3941:3944:3947:3950:3953:3956:3959:4605:5007:6114:6261:6642:9025:9121:10011:10848:11658:11914:12196:13439:13894:13895:14658:21080:21627:30003:30010:30025:30029:30034:30051:30054:30070:30079:30090:30091,0,RBL:none,CacheIP:none,Bayesian:0.5,0.5,0.5,Netcheck:none,DomainCache:0,MSF:not bulk,SPF:,MSBL:0,DNSBL:none,Custom_rules:0:1:0,LFtime:2,LUA_SUMMARY:none X-HE-Tag: leaf18_6d579159b010c X-Filterd-Recvd-Size: 101352 Received: from alvin.alvh.no-ip.org (unknown [179.56.51.13]) (Authenticated sender: alvherre@alvh.no-ip.org) by omf10.b.hostedemail.com (Postfix) with ESMTPA for ; Sun, 31 Dec 2017 19:49:11 +0000 (UTC) Received: by alvin.alvh.no-ip.org (Postfix, from userid 1000) id 734C7264; Sun, 31 Dec 2017 16:49:08 -0300 (-03) Date: Sun, 31 Dec 2017 16:49:08 -0300 From: Alvaro Herrera To: Pg Hackers Subject: Re: Foreign keys and partitioned tables Message-ID: <20171231194908.qbtfvrt77lgda4xf@alvherre.pgsql> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="4mbzjl3eru7reumb" Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: <20171231194359.cvojcour423ulha4@alvherre.pgsql> User-Agent: NeoMutt/20170306-137-4415bd-dirty (1.8.0) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk --4mbzjl3eru7reumb Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: inline Content-Transfer-Encoding: 8bit -- Álvaro Herrera https://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services --4mbzjl3eru7reumb Content-Type: application/x-gunzip Content-Disposition: attachment; filename="0001-Local-partitioned-indexes.patch.gz" Content-Transfer-Encoding: base64 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 rVJ99uz/B5p/7zgaEQMA --4mbzjl3eru7reumb Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="0002-WIP-Allow-foreign-key-triggers-on-partitioned-tables.patch" From b7e85e873ba77509793180e9076295fae2fd88a7 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Tue, 21 Nov 2017 15:54:14 -0300 Subject: [PATCH 2/2] [WIP] Allow foreign key triggers on partitioned tables --- src/backend/catalog/pg_constraint.c | 192 +++++++++++++++++++++++++++++ src/backend/commands/tablecmds.c | 105 +++++++++++++--- src/backend/parser/parse_utilcmd.c | 12 -- src/backend/utils/adt/ri_triggers.c | 50 +++----- src/include/catalog/pg_constraint_fn.h | 2 + src/include/commands/tablecmds.h | 4 + src/test/regress/expected/alter_table.out | 115 ++++++++++++++++- src/test/regress/expected/create_table.out | 10 -- src/test/regress/expected/foreign_key.out | 67 ++++++++++ src/test/regress/sql/alter_table.sql | 57 ++++++++- src/test/regress/sql/create_table.sql | 8 -- src/test/regress/sql/foreign_key.sql | 28 +++++ 12 files changed, 566 insertions(+), 84 deletions(-) diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 7dee6db0eb..9dc91fb67f 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -26,6 +26,7 @@ #include "catalog/pg_operator.h" #include "catalog/pg_type.h" #include "commands/defrem.h" +#include "commands/tablecmds.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -375,6 +376,197 @@ CreateConstraintEntry(const char *constraintName, return conOid; } +/* + * For each foreign key constraint in relation parentId, create a cloned + * copy of it for relationId. + * + * relationId is a partition of parentId, so we can be certain that it has + * the same columns with the same datatypes. They may be in different order, + * though. + */ +void +CloneForeignKeyConstraints(Oid parentId, Oid relationId) +{ + Relation pg_constraint; + Relation rel; + ScanKeyData key; + SysScanDesc scan; + TupleDesc tupdesc; + HeapTuple tuple; + + /* see ATAddForeignKeyConstraint about lock level */ + rel = heap_open(relationId, AccessExclusiveLock); + + pg_constraint = heap_open(ConstraintRelationId, RowShareLock); + tupdesc = RelationGetDescr(pg_constraint); + + ScanKeyInit(&key, + Anum_pg_constraint_conrelid, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(parentId)); + scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true, + NULL, 1, &key); + + while ((tuple = systable_getnext(scan)) != NULL) + { + Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple); + AttrNumber conkey[INDEX_MAX_KEYS]; + AttrNumber confkey[INDEX_MAX_KEYS]; + Oid conpfeqop[INDEX_MAX_KEYS]; + Oid conppeqop[INDEX_MAX_KEYS]; + Oid conffeqop[INDEX_MAX_KEYS]; + Constraint *fkconstraint; + Oid constrOid; + ObjectAddress parentAddr, + childAddr; + int nelem; + ArrayType *arr; + Datum datum; + bool isnull; + + /* only foreign keys */ + if (constrForm->contype != CONSTRAINT_FOREIGN) + continue; + + ObjectAddressSet(parentAddr, ConstraintRelationId, + HeapTupleGetOid(tuple)); + + datum = fastgetattr(tuple, Anum_pg_constraint_conkey, + tupdesc, &isnull); + if (isnull) + elog(ERROR, "null conkey"); + arr = DatumGetArrayTypeP(datum); + nelem = ARR_DIMS(arr)[0]; + if (ARR_NDIM(arr) != 1 || + nelem < 1 || + nelem > INDEX_MAX_KEYS || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != INT2OID) + elog(ERROR, "conkey is not a 1-D smallint array"); + memcpy(conkey, ARR_DATA_PTR(arr), nelem * sizeof(AttrNumber)); + + datum = fastgetattr(tuple, Anum_pg_constraint_confkey, + tupdesc, &isnull); + if (isnull) + elog(ERROR, "null confkey"); + arr = DatumGetArrayTypeP(datum); + nelem = ARR_DIMS(arr)[0]; + if (ARR_NDIM(arr) != 1 || + nelem < 1 || + nelem > INDEX_MAX_KEYS || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != INT2OID) + elog(ERROR, "confkey is not a 1-D smallint array"); + memcpy(confkey, ARR_DATA_PTR(arr), nelem * sizeof(AttrNumber)); + + datum = fastgetattr(tuple, Anum_pg_constraint_conpfeqop, + tupdesc, &isnull); + if (isnull) + elog(ERROR, "null conpfeqop"); + arr = DatumGetArrayTypeP(datum); + nelem = ARR_DIMS(arr)[0]; + if (ARR_NDIM(arr) != 1 || + nelem < 1 || + nelem > INDEX_MAX_KEYS || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != OIDOID) + elog(ERROR, "conpfeqop is not a 1-D OID array"); + memcpy(conpfeqop, ARR_DATA_PTR(arr), nelem * sizeof(Oid)); + + datum = fastgetattr(tuple, Anum_pg_constraint_conpfeqop, + tupdesc, &isnull); + if (isnull) + elog(ERROR, "null conpfeqop"); + arr = DatumGetArrayTypeP(datum); + nelem = ARR_DIMS(arr)[0]; + if (ARR_NDIM(arr) != 1 || + nelem < 1 || + nelem > INDEX_MAX_KEYS || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != OIDOID) + elog(ERROR, "conpfeqop is not a 1-D OID array"); + memcpy(conpfeqop, ARR_DATA_PTR(arr), nelem * sizeof(Oid)); + + datum = fastgetattr(tuple, Anum_pg_constraint_conppeqop, + tupdesc, &isnull); + if (isnull) + elog(ERROR, "null conppeqop"); + arr = DatumGetArrayTypeP(datum); + nelem = ARR_DIMS(arr)[0]; + if (ARR_NDIM(arr) != 1 || + nelem < 1 || + nelem > INDEX_MAX_KEYS || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != OIDOID) + elog(ERROR, "conppeqop is not a 1-D OID array"); + memcpy(conppeqop, ARR_DATA_PTR(arr), nelem * sizeof(Oid)); + + datum = fastgetattr(tuple, Anum_pg_constraint_conffeqop, + tupdesc, &isnull); + if (isnull) + elog(ERROR, "null conffeqop"); + arr = DatumGetArrayTypeP(datum); + nelem = ARR_DIMS(arr)[0]; + if (ARR_NDIM(arr) != 1 || + nelem < 1 || + nelem > INDEX_MAX_KEYS || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != OIDOID) + elog(ERROR, "conffeqop is not a 1-D OID array"); + memcpy(conffeqop, ARR_DATA_PTR(arr), nelem * sizeof(Oid)); + + constrOid = + CreateConstraintEntry(NameStr(constrForm->conname), + constrForm->connamespace, + CONSTRAINT_FOREIGN, + constrForm->condeferrable, + constrForm->condeferred, + constrForm->convalidated, + relationId, + conkey, + nelem, + InvalidOid, /* not a domain constraint */ + constrForm->conindid, /* same index */ + constrForm->confrelid, /* same foreign rel */ + confkey, + conpfeqop, + conppeqop, + conffeqop, + nelem, + constrForm->confupdtype, + constrForm->confdeltype, + constrForm->confmatchtype, + NULL, + NULL, + NULL, + NULL, + false, + 1, false, true); + + ObjectAddressSet(childAddr, ConstraintRelationId, constrOid); + recordDependencyOn(&childAddr, &parentAddr, DEPENDENCY_INTERNAL); + + fkconstraint = makeNode(Constraint); + /* for now this is all we need */ + fkconstraint->fk_upd_action = constrForm->confupdtype; + fkconstraint->fk_del_action = constrForm->confdeltype; + fkconstraint->deferrable = constrForm->condeferrable; + fkconstraint->initdeferred = constrForm->condeferred; + + createForeignKeyTriggers(rel, constrForm->confrelid, fkconstraint, + constrOid, constrForm->conindid); + + /* + * XXX Normal constraint creation can be invoked during ALTER and + * so it needs ALTER TABLE's phase 3 checking. Current caller is just + * CREATE TABLE .. PARTITION OF so we don't need it, but maybe for + * ALTER TABLE .. ATTACH PARTITION we'll need it. + */ + } + systable_endscan(scan); + + heap_close(rel, NoLock); /* keep lock till commit */ + heap_close(pg_constraint, RowShareLock); +} /* * Test whether given name is currently used as a constraint name diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index b0ddfb9110..d21f37216d 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -337,9 +337,6 @@ static void validateCheckConstraint(Relation rel, HeapTuple constrtup); static void validateForeignKeyConstraint(char *conname, Relation rel, Relation pkrel, Oid pkindOid, Oid constraintOid); -static void createForeignKeyTriggers(Relation rel, Oid refRelOid, - Constraint *fkconstraint, - Oid constraintOid, Oid indexOid); static void ATController(AlterTableStmt *parsetree, Relation rel, List *cmds, bool recurse, LOCKMODE lockmode); static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, @@ -410,8 +407,10 @@ static ObjectAddress ATAddCheckConstraint(List **wqueue, Constraint *constr, bool recurse, bool recursing, bool is_readd, LOCKMODE lockmode); -static ObjectAddress ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, - Constraint *fkconstraint, LOCKMODE lockmode); +static ObjectAddress ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, + Relation rel, + Constraint *fkconstraint, bool recurse, bool recursing, + LOCKMODE lockmode); static void ATExecDropConstraint(Relation rel, const char *constrName, DropBehavior behavior, bool recurse, bool recursing, @@ -503,6 +502,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, * relkind: relkind to assign to the new relation * ownerId: if not InvalidOid, use this as the new relation's owner. * typaddress: if not null, it's set to the pg_type entry's address. + * queryString: for error reporting * * Note that permissions checks are done against current user regardless of * ownerId. A nonzero ownerId is used when someone is creating a relation @@ -958,6 +958,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, } list_free(idxlist); + + CloneForeignKeyConstraints(parentId, relationId); + heap_close(parent, NoLock); } @@ -7043,7 +7046,8 @@ ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, RelationGetNamespace(rel), NIL); - address = ATAddForeignKeyConstraint(tab, rel, newConstraint, + address = ATAddForeignKeyConstraint(wqueue, tab, rel, + newConstraint, recurse, false, lockmode); break; @@ -7198,8 +7202,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, * We do permissions checks here, however. */ static ObjectAddress -ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, - Constraint *fkconstraint, LOCKMODE lockmode) +ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, + Constraint *fkconstraint, bool recurse, + bool recursing, LOCKMODE lockmode) { Relation pkrel; int16 pkattnum[INDEX_MAX_KEYS]; @@ -7233,12 +7238,22 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, * numbers) */ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot reference partitioned table \"%s\"", - RelationGetRelationName(pkrel)))); + { + if (!recurse) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("foreign key referencing partitioned table \"%s\" must not be ONLY", + RelationGetRelationName(pkrel)))); + /* fix recursion in ATExecValidateConstraint to enable this case */ + if (fkconstraint->skip_validation && !fkconstraint->initially_valid) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot add NOT VALID foreign key to relation \"%s\"", + RelationGetRelationName(pkrel)))); + } - if (pkrel->rd_rel->relkind != RELKIND_RELATION) + if (pkrel->rd_rel->relkind != RELKIND_RELATION && + pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("referenced relation \"%s\" is not a table", @@ -7598,6 +7613,45 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, } /* + * If this is a partitioned table, recurse to create the constraint on the + * partitions also. + */ + if (recurse) + { + List *children; + ListCell *child; + + /* XXX why not find_all_inheritors? */ + children = find_inheritance_children(RelationGetRelid(rel), lockmode); + + foreach(child, children) + { + Oid childrelid = lfirst_oid(child); + Relation childrel; + AlteredTableInfo *childtab; + ObjectAddress childAddr; + + /* find_inheritance_children already got lock */ + childrel = heap_open(childrelid, NoLock); + CheckTableNotInUse(childrel, "ALTER TABLE"); /* XXX do we need this? */ + + /* Find or create work queue entry for this table */ + childtab = ATGetQueueEntry(wqueue, childrel); + + /* Recurse to child */ + childAddr = + ATAddForeignKeyConstraint(wqueue, childtab, childrel, + fkconstraint, recurse, true, + lockmode); + + /* make sure they go away together, or not at all */ + recordDependencyOn(&childAddr, &address, DEPENDENCY_INTERNAL); + + heap_close(childrel, NoLock); + } + } + + /* * Close pk table, but keep lock until we've committed. */ heap_close(pkrel, NoLock); @@ -7859,8 +7913,8 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse, heap_close(refrel, NoLock); /* - * Foreign keys do not inherit, so we purposely ignore the - * recursion bit here + * We disallow creating invalid foreign keys to or from + * partitioned tables, so ignoring the recursion bit is okay. */ } else if (con->contype == CONSTRAINT_CHECK) @@ -8511,7 +8565,7 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint, * NB: if you change any trigger properties here, see also * ATExecAlterConstraint. */ -static void +void createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint, Oid constraintOid, Oid indexOid) { @@ -8641,6 +8695,25 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint, indexOid, true); CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint, constraintOid, indexOid, false); + + /* + * If this is a partitioned table, recurse to create triggers for each + * child. We consider that one pg_constraint entry is enough; we only + * need the triggers to appear per-partition. + */ + if (get_rel_relkind(refRelOid) == RELKIND_PARTITIONED_TABLE) + { + ListCell *cell; + List *dchildren; + + /* XXX maybe we need a stronger lock? */ + dchildren = find_inheritance_children(refRelOid, RowShareLock); + foreach(cell, dchildren) + { + createForeignKeyTriggers(rel, lfirst_oid(cell), fkconstraint, + constraintOid, indexOid); + } + } } /* diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 917b9bd636..19fd914b55 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -730,12 +730,6 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) errmsg("foreign key constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); - if (cxt->ispartitioned) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("foreign key constraints are not supported on partitioned tables"), - parser_errposition(cxt->pstate, - constraint->location))); /* * Fill in the current attribute's name and throw it into the @@ -849,12 +843,6 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint) errmsg("foreign key constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); - if (cxt->ispartitioned) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("foreign key constraints are not supported on partitioned tables"), - parser_errposition(cxt->pstate, - constraint->location))); cxt->fkconstraints = lappend(cxt->fkconstraints, constraint); break; diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index b1ae9e5f96..f9c0e62417 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -401,7 +401,7 @@ RI_FKey_check(TriggerData *trigdata) /* ---------- * The query string built is - * SELECT 1 FROM ONLY x WHERE pkatt1 = $1 [AND ...] + * SELECT 1 FROM x WHERE pkatt1 = $1 [AND ...] * FOR KEY SHARE OF x * The type id's for the $ parameters are those of the * corresponding FK attributes. @@ -409,7 +409,7 @@ RI_FKey_check(TriggerData *trigdata) */ initStringInfo(&querybuf); quoteRelationName(pkrelname, pk_rel); - appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname); + appendStringInfo(&querybuf, "SELECT 1 FROM %s x", pkrelname); querysep = "WHERE"; for (i = 0; i < riinfo->nkeys; i++) { @@ -537,7 +537,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, /* ---------- * The query string built is - * SELECT 1 FROM ONLY x WHERE pkatt1 = $1 [AND ...] + * SELECT 1 FROM x WHERE pkatt1 = $1 [AND ...] * FOR KEY SHARE OF x * The type id's for the $ parameters are those of the * PK attributes themselves. @@ -545,7 +545,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, */ initStringInfo(&querybuf); quoteRelationName(pkrelname, pk_rel); - appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname); + appendStringInfo(&querybuf, "SELECT 1 FROM %s x", pkrelname); querysep = "WHERE"; for (i = 0; i < riinfo->nkeys; i++) { @@ -793,7 +793,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action) /* ---------- * The query string built is - * SELECT 1 FROM ONLY x WHERE $1 = fkatt1 [AND ...] + * SELECT 1 FROM x WHERE $1 = fkatt1 [AND ...] * FOR KEY SHARE OF x * The type id's for the $ parameters are those of the * corresponding PK attributes. @@ -801,7 +801,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action) */ initStringInfo(&querybuf); quoteRelationName(fkrelname, fk_rel); - appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", + appendStringInfo(&querybuf, "SELECT 1 FROM %s x", fkrelname); querysep = "WHERE"; for (i = 0; i < riinfo->nkeys; i++) @@ -951,14 +951,14 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) /* ---------- * The query string built is - * DELETE FROM ONLY WHERE $1 = fkatt1 [AND ...] + * DELETE FROM WHERE $1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the * corresponding PK attributes. * ---------- */ initStringInfo(&querybuf); quoteRelationName(fkrelname, fk_rel); - appendStringInfo(&querybuf, "DELETE FROM ONLY %s", fkrelname); + appendStringInfo(&querybuf, "DELETE FROM %s", fkrelname); querysep = "WHERE"; for (i = 0; i < riinfo->nkeys; i++) { @@ -1122,7 +1122,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) /* ---------- * The query string built is - * UPDATE ONLY SET fkatt1 = $1 [, ...] + * UPDATE SET fkatt1 = $1 [, ...] * WHERE $n = fkatt1 [AND ...] * The type id's for the $ parameters are those of the * corresponding PK attributes. Note that we are assuming @@ -1133,7 +1133,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) initStringInfo(&querybuf); initStringInfo(&qualbuf); quoteRelationName(fkrelname, fk_rel); - appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname); + appendStringInfo(&querybuf, "UPDATE %s SET", fkrelname); querysep = ""; qualsep = "WHERE"; for (i = 0, j = riinfo->nkeys; i < riinfo->nkeys; i++, j++) @@ -1342,7 +1342,7 @@ ri_setnull(TriggerData *trigdata) /* ---------- * The query string built is - * UPDATE ONLY SET fkatt1 = NULL [, ...] + * UPDATE SET fkatt1 = NULL [, ...] * WHERE $1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the * corresponding PK attributes. @@ -1351,7 +1351,7 @@ ri_setnull(TriggerData *trigdata) initStringInfo(&querybuf); initStringInfo(&qualbuf); quoteRelationName(fkrelname, fk_rel); - appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname); + appendStringInfo(&querybuf, "UPDATE %s SET", fkrelname); querysep = ""; qualsep = "WHERE"; for (i = 0; i < riinfo->nkeys; i++) @@ -1559,7 +1559,7 @@ ri_setdefault(TriggerData *trigdata) /* ---------- * The query string built is - * UPDATE ONLY SET fkatt1 = DEFAULT [, ...] + * UPDATE SET fkatt1 = DEFAULT [, ...] * WHERE $1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the * corresponding PK attributes. @@ -1568,7 +1568,7 @@ ri_setdefault(TriggerData *trigdata) initStringInfo(&querybuf); initStringInfo(&qualbuf); quoteRelationName(fkrelname, fk_rel); - appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname); + appendStringInfo(&querybuf, "UPDATE %s SET", fkrelname); querysep = ""; qualsep = "WHERE"; for (i = 0; i < riinfo->nkeys; i++) @@ -1895,8 +1895,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) /*---------- * The query string built is: - * SELECT fk.keycols FROM ONLY relname fk - * LEFT OUTER JOIN ONLY pkrelname pk + * SELECT fk.keycols FROM relname fk + * LEFT OUTER JOIN pkrelname pk * ON (pk.pkkeycol1=fk.keycol1 [AND ...]) * WHERE pk.pkkeycol1 IS NULL AND * For MATCH SIMPLE: @@ -1922,7 +1922,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) quoteRelationName(pkrelname, pk_rel); quoteRelationName(fkrelname, fk_rel); appendStringInfo(&querybuf, - " FROM ONLY %s fk LEFT OUTER JOIN ONLY %s pk ON", + " FROM %s fk LEFT OUTER JOIN %s pk ON", fkrelname, pkrelname); strcpy(pkattname, "pk."); @@ -2345,22 +2345,6 @@ ri_FetchConstraintInfo(Trigger *trigger, Relation trig_rel, bool rel_is_pk) /* Find or create a hashtable entry for the constraint */ riinfo = ri_LoadConstraintInfo(constraintOid); - /* Do some easy cross-checks against the trigger call data */ - if (rel_is_pk) - { - if (riinfo->fk_relid != trigger->tgconstrrelid || - riinfo->pk_relid != RelationGetRelid(trig_rel)) - elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"", - trigger->tgname, RelationGetRelationName(trig_rel)); - } - else - { - if (riinfo->fk_relid != RelationGetRelid(trig_rel) || - riinfo->pk_relid != trigger->tgconstrrelid) - elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"", - trigger->tgname, RelationGetRelationName(trig_rel)); - } - return riinfo; } diff --git a/src/include/catalog/pg_constraint_fn.h b/src/include/catalog/pg_constraint_fn.h index 37b0b4ba82..438f5402fe 100644 --- a/src/include/catalog/pg_constraint_fn.h +++ b/src/include/catalog/pg_constraint_fn.h @@ -56,6 +56,8 @@ extern Oid CreateConstraintEntry(const char *constraintName, bool conNoInherit, bool is_internal); +extern void CloneForeignKeyConstraints(Oid parentId, Oid relationId); + extern void RemoveConstraintById(Oid conId); extern void RenameConstraintById(Oid conId, const char *newname); extern void SetValidatedConstraintById(Oid conId); diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index da3ff5dbee..c68515345b 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -74,6 +74,10 @@ extern void find_composite_type_dependencies(Oid typeOid, extern void check_of_type(HeapTuple typetuple); +extern void createForeignKeyTriggers(Relation rel, Oid refRelOid, + Constraint *fkconstraint, Oid constraintOid, + Oid indexOid); + extern void register_on_commit_action(Oid relid, OnCommitAction action); extern void remove_on_commit_action(Oid relid); diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 2caf930242..d4602d62ee 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -470,6 +470,117 @@ DROP TABLE tmp5; DROP TABLE tmp4; DROP TABLE tmp3; DROP TABLE tmp2; +-- Ensure we can add foreign keys to and from partitioned tables +SET search_path TO at_tst; +CREATE SCHEMA at_tst; +CREATE TABLE at_regular1 (col1 INT PRIMARY KEY); +CREATE TABLE at_partitioned (col2 INT PRIMARY KEY, + reg1_col1 INT NOT NULL) PARTITION BY RANGE (col2); +CREATE TABLE at_regular2 (col3 INT); +ALTER TABLE at_regular2 ADD FOREIGN KEY (col3) REFERENCES at_partitioned; +ALTER TABLE at_partitioned ADD FOREIGN KEY (reg1_col1) REFERENCES at_regular1; +CREATE TABLE at_partitioned_0 PARTITION OF at_partitioned + FOR VALUES FROM (0) TO (10000); +-- these fail: +INSERT INTO at_regular2 VALUES (1000); +ERROR: insert or update on table "at_regular2" violates foreign key constraint "at_regular2_col3_fkey" +DETAIL: Key (col3)=(1000) is not present in table "at_partitioned". +INSERT INTO at_partitioned VALUES (1000, 42); +ERROR: insert or update on table "at_partitioned_0" violates foreign key constraint "at_partitioned_reg1_col1_fkey" +DETAIL: Key (reg1_col1)=(42) is not present in table "at_regular1". +-- these work: +INSERT INTO at_regular1 VALUES (1000); +INSERT INTO at_partitioned VALUES (42, 1000); +INSERT INTO at_regular2 VALUES (42); +CREATE TABLE at_partitioned_1 PARTITION OF at_partitioned + FOR VALUES FROM (10000) TO (20000); +CREATE TABLE at_partitioned_2 (reg1_col1 INT, col2 INT); +ALTER TABLE at_partitioned ATTACH PARTITION at_partitioned_2 + FOR VALUES FROM (20000) TO (30000); +ERROR: column "col2" in child table must be marked NOT NULL +ALTER TABLE at_partitioned_2 + ALTER col2 SET NOT NULL, + ALTER reg1_col1 SET NOT NULL; +ALTER TABLE at_partitioned ATTACH PARTITION at_partitioned_2 + FOR VALUES FROM (20000) TO (30000); +\d at_regular2 + Table "at_tst.at_regular2" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + col3 | integer | | | +Foreign-key constraints: + "at_regular2_col3_fkey" FOREIGN KEY (col3) REFERENCES at_partitioned(col2) + +\d at_partitioned + Table "at_tst.at_partitioned" + Column | Type | Collation | Nullable | Default +-----------+---------+-----------+----------+--------- + col2 | integer | | not null | + reg1_col1 | integer | | not null | +Partition key: RANGE (col2) +Indexes: + "at_partitioned_pkey" PRIMARY KEY, btree (col2) +Foreign-key constraints: + "at_partitioned_reg1_col1_fkey" FOREIGN KEY (reg1_col1) REFERENCES at_regular1(col1) +Referenced by: + TABLE "at_regular2" CONSTRAINT "at_regular2_col3_fkey" FOREIGN KEY (col3) REFERENCES at_partitioned(col2) +Number of partitions: 3 (Use \d+ to list them.) + +\d at_partitioned_0 + Table "at_tst.at_partitioned_0" + Column | Type | Collation | Nullable | Default +-----------+---------+-----------+----------+--------- + col2 | integer | | not null | + reg1_col1 | integer | | not null | +Partition of: at_partitioned FOR VALUES FROM (0) TO (10000) +Indexes: + "at_partitioned_0_pkey" PRIMARY KEY, btree (col2) +Foreign-key constraints: + "at_partitioned_reg1_col1_fkey" FOREIGN KEY (reg1_col1) REFERENCES at_regular1(col1) + +\d at_partitioned_1 + Table "at_tst.at_partitioned_1" + Column | Type | Collation | Nullable | Default +-----------+---------+-----------+----------+--------- + col2 | integer | | not null | + reg1_col1 | integer | | not null | +Partition of: at_partitioned FOR VALUES FROM (10000) TO (20000) +Indexes: + "at_partitioned_1_pkey" PRIMARY KEY, btree (col2) +Foreign-key constraints: + "at_partitioned_reg1_col1_fkey" FOREIGN KEY (reg1_col1) REFERENCES at_regular1(col1) + +\d at_partitioned_2 + Table "at_tst.at_partitioned_2" + Column | Type | Collation | Nullable | Default +-----------+---------+-----------+----------+--------- + reg1_col1 | integer | | not null | + col2 | integer | | not null | +Partition of: at_partitioned FOR VALUES FROM (20000) TO (30000) +Indexes: + "at_partitioned_2_pkey" PRIMARY KEY, btree (col2) + +INSERT INTO at_partitioned VALUES (5000, 42); +ERROR: insert or update on table "at_partitioned_0" violates foreign key constraint "at_partitioned_reg1_col1_fkey" +DETAIL: Key (reg1_col1)=(42) is not present in table "at_regular1". +INSERT INTO at_regular1 VALUES (42), (1042), (2042); +INSERT INTO at_partitioned VALUES (5000, 42), (15000, 1042), (25000, 2042); +INSERT INTO at_regular2 VALUES (5000), (15000), (25000); +INSERT INTO at_regular2 VALUES (35000); +ERROR: insert or update on table "at_regular2" violates foreign key constraint "at_regular2_col3_fkey" +DETAIL: Key (col3)=(35000) is not present in table "at_partitioned". +-- ok +ALTER TABLE at_regular2 DROP CONSTRAINT at_regular2_col3_fkey; +-- disallowed: must drop it from parent instead +ALTER TABLE at_partitioned_0 DROP CONSTRAINT at_partitioned_reg1_col1_fkey; +ERROR: cannot drop inherited constraint "at_partitioned_reg1_col1_fkey" of relation "at_partitioned_0" +-- ok +ALTER TABLE at_partitioned DROP CONSTRAINT at_partitioned_reg1_col1_fkey; +\set VERBOSITY terse +DROP SCHEMA at_tst CASCADE; +NOTICE: drop cascades to 3 other objects +\set VERBOSITY default +RESET search_path; -- NOT VALID with plan invalidation -- ensure we don't use a constraint for -- exclusion until validated set constraint_exclusion TO 'partition'; @@ -3290,10 +3401,6 @@ CREATE TABLE partitioned ( a int, b int ) PARTITION BY RANGE (a, (a+b+1)); -ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah; -ERROR: foreign key constraints are not supported on partitioned tables -LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah; - ^ ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&); ERROR: exclusion constraints are not supported on partitioned tables LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&); diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out index 866cc99b9f..7c3703b73d 100644 --- a/src/test/regress/expected/create_table.out +++ b/src/test/regress/expected/create_table.out @@ -276,16 +276,6 @@ CREATE TABLE partitioned ( ) PARTITION BY LIST (a1, a2); -- fail ERROR: cannot use "list" partition strategy with more than one column -- unsupported constraint type for partitioned tables -CREATE TABLE pkrel ( - a int PRIMARY KEY -); -CREATE TABLE partitioned ( - a int REFERENCES pkrel(a) -) PARTITION BY RANGE (a); -ERROR: foreign key constraints are not supported on partitioned tables -LINE 2: a int REFERENCES pkrel(a) - ^ -DROP TABLE pkrel; CREATE TABLE partitioned ( a int, EXCLUDE USING gist (a WITH &&) diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index fef072eddf..8c71c21973 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -1415,3 +1415,70 @@ alter table fktable2 drop constraint fktable2_f1_fkey; ERROR: cannot ALTER TABLE "pktable2" because it has pending trigger events commit; drop table pktable2, fktable2; +-- +-- Foreign keys and partitioned tables +-- +-- Test that it's possible to have a FK from a partitioned table to a regular +-- one +CREATE TABLE pkregular (f1 int primary key); +CREATE TABLE fkpartit (f1 int references pkregular) PARTITION BY RANGE (f1); +CREATE TABLE fkpart1 PARTITION OF fkpartit FOR VALUES FROM (0) TO (1000); +INSERT INTO fkpartit VALUES (500); +ERROR: insert or update on table "fkpart1" violates foreign key constraint "fkpartit_f1_fkey" +DETAIL: Key (f1)=(500) is not present in table "pkregular". +INSERT INTO fkpart1 VALUES (500); +ERROR: insert or update on table "fkpart1" violates foreign key constraint "fkpartit_f1_fkey" +DETAIL: Key (f1)=(500) is not present in table "pkregular". +INSERT INTO pkregular VALUES (500); +INSERT INTO fkpartit VALUES (500); +INSERT INTO fkpart1 VALUES (500); +DELETE FROM pkregular; +ERROR: update or delete on table "pkregular" violates foreign key constraint "fkpartit_f1_fkey" on table "fkpartit" +DETAIL: Key (f1)=(500) is still referenced from table "fkpartit". +UPDATE pkregular SET f1 = 501; +ERROR: update or delete on table "pkregular" violates foreign key constraint "fkpartit_f1_fkey" on table "fkpartit" +DETAIL: Key (f1)=(500) is still referenced from table "fkpartit". +ALTER TABLE fkpart1 DROP CONSTRAINT fkpartit_f1_fkey; -- nope +ERROR: cannot drop inherited constraint "fkpartit_f1_fkey" of relation "fkpart1" +ALTER TABLE fkpartit DROP CONSTRAINT fkpartit_f1_fkey; +\d fkpartit + Table "public.fkpartit" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + f1 | integer | | | +Partition key: RANGE (f1) +Number of partitions: 1 (Use \d+ to list them.) + +\d fkpart1 + Table "public.fkpart1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + f1 | integer | | | +Partition of: fkpartit FOR VALUES FROM (0) TO (1000) + +ALTER TABLE fkpartit ADD CONSTRAINT fkpartit_f1_fkey FOREIGN KEY (f1) REFERENCES pkregular ON DELETE CASCADE; +\d fkpartit + Table "public.fkpartit" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + f1 | integer | | | +Partition key: RANGE (f1) +Foreign-key constraints: + "fkpartit_f1_fkey" FOREIGN KEY (f1) REFERENCES pkregular(f1) ON DELETE CASCADE +Number of partitions: 1 (Use \d+ to list them.) + +\d fkpart1 + Table "public.fkpart1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + f1 | integer | | | +Partition of: fkpartit FOR VALUES FROM (0) TO (1000) +Foreign-key constraints: + "fkpartit_f1_fkey" FOREIGN KEY (f1) REFERENCES pkregular(f1) ON DELETE CASCADE + +DELETE FROM pkregular; +SELECT * FROM fkpartit; + f1 +---- +(0 rows) + diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index ed0bb7845b..40b3d0256b 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -375,6 +375,62 @@ DROP TABLE tmp3; DROP TABLE tmp2; +-- Ensure we can add foreign keys to and from partitioned tables +SET search_path TO at_tst; +CREATE SCHEMA at_tst; +CREATE TABLE at_regular1 (col1 INT PRIMARY KEY); +CREATE TABLE at_partitioned (col2 INT PRIMARY KEY, + reg1_col1 INT NOT NULL) PARTITION BY RANGE (col2); +CREATE TABLE at_regular2 (col3 INT); +ALTER TABLE at_regular2 ADD FOREIGN KEY (col3) REFERENCES at_partitioned; +ALTER TABLE at_partitioned ADD FOREIGN KEY (reg1_col1) REFERENCES at_regular1; +CREATE TABLE at_partitioned_0 PARTITION OF at_partitioned + FOR VALUES FROM (0) TO (10000); +-- these fail: +INSERT INTO at_regular2 VALUES (1000); +INSERT INTO at_partitioned VALUES (1000, 42); + +-- these work: +INSERT INTO at_regular1 VALUES (1000); +INSERT INTO at_partitioned VALUES (42, 1000); +INSERT INTO at_regular2 VALUES (42); + +CREATE TABLE at_partitioned_1 PARTITION OF at_partitioned + FOR VALUES FROM (10000) TO (20000); +CREATE TABLE at_partitioned_2 (reg1_col1 INT, col2 INT); +ALTER TABLE at_partitioned ATTACH PARTITION at_partitioned_2 + FOR VALUES FROM (20000) TO (30000); +ALTER TABLE at_partitioned_2 + ALTER col2 SET NOT NULL, + ALTER reg1_col1 SET NOT NULL; +ALTER TABLE at_partitioned ATTACH PARTITION at_partitioned_2 + FOR VALUES FROM (20000) TO (30000); + +\d at_regular2 +\d at_partitioned +\d at_partitioned_0 +\d at_partitioned_1 +\d at_partitioned_2 + +INSERT INTO at_partitioned VALUES (5000, 42); +INSERT INTO at_regular1 VALUES (42), (1042), (2042); +INSERT INTO at_partitioned VALUES (5000, 42), (15000, 1042), (25000, 2042); +INSERT INTO at_regular2 VALUES (5000), (15000), (25000); +INSERT INTO at_regular2 VALUES (35000); + +-- ok +ALTER TABLE at_regular2 DROP CONSTRAINT at_regular2_col3_fkey; + +-- disallowed: must drop it from parent instead +ALTER TABLE at_partitioned_0 DROP CONSTRAINT at_partitioned_reg1_col1_fkey; +-- ok +ALTER TABLE at_partitioned DROP CONSTRAINT at_partitioned_reg1_col1_fkey; + +\set VERBOSITY terse +DROP SCHEMA at_tst CASCADE; +\set VERBOSITY default +RESET search_path; + -- NOT VALID with plan invalidation -- ensure we don't use a constraint for -- exclusion until validated set constraint_exclusion TO 'partition'; @@ -2016,7 +2072,6 @@ CREATE TABLE partitioned ( a int, b int ) PARTITION BY RANGE (a, (a+b+1)); -ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah; ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&); -- cannot drop column that is part of the partition key diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql index fefccf21a2..09a634d79d 100644 --- a/src/test/regress/sql/create_table.sql +++ b/src/test/regress/sql/create_table.sql @@ -294,14 +294,6 @@ CREATE TABLE partitioned ( ) PARTITION BY LIST (a1, a2); -- fail -- unsupported constraint type for partitioned tables -CREATE TABLE pkrel ( - a int PRIMARY KEY -); -CREATE TABLE partitioned ( - a int REFERENCES pkrel(a) -) PARTITION BY RANGE (a); -DROP TABLE pkrel; - CREATE TABLE partitioned ( a int, EXCLUDE USING gist (a WITH &&) diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index 5f19dad03c..1a2c93fcce 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -1055,3 +1055,31 @@ alter table fktable2 drop constraint fktable2_f1_fkey; commit; drop table pktable2, fktable2; + + +-- +-- Foreign keys and partitioned tables +-- + +-- Test that it's possible to have a FK from a partitioned table to a regular +-- one +CREATE TABLE pkregular (f1 int primary key); +CREATE TABLE fkpartit (f1 int references pkregular) PARTITION BY RANGE (f1); +CREATE TABLE fkpart1 PARTITION OF fkpartit FOR VALUES FROM (0) TO (1000); +INSERT INTO fkpartit VALUES (500); +INSERT INTO fkpart1 VALUES (500); +INSERT INTO pkregular VALUES (500); +INSERT INTO fkpartit VALUES (500); +INSERT INTO fkpart1 VALUES (500); +DELETE FROM pkregular; +UPDATE pkregular SET f1 = 501; + +ALTER TABLE fkpart1 DROP CONSTRAINT fkpartit_f1_fkey; -- nope +ALTER TABLE fkpartit DROP CONSTRAINT fkpartit_f1_fkey; +\d fkpartit +\d fkpart1 +ALTER TABLE fkpartit ADD CONSTRAINT fkpartit_f1_fkey FOREIGN KEY (f1) REFERENCES pkregular ON DELETE CASCADE; +\d fkpartit +\d fkpart1 +DELETE FROM pkregular; +SELECT * FROM fkpartit; -- 2.11.0 --4mbzjl3eru7reumb--