diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index c1d11be73f..025db98763 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -2708,6 +2708,18 @@ SCRAM-SHA-256$<iteration count>:&l + + + confdelsetcols int2[] + (references pg_attribute.attnum) + + + If a foreign key with a SET NULL or SET + DEFAULT delete action, the columns that will be updated. + If null, all of the referencing columns will be updated. + + + conexclop oid[] diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 642ea2a70d..64d9030652 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -1083,10 +1083,41 @@ CREATE TABLE order_items ( manager to null or a default might be useful. + + The actions SET NULL and SET DEFAULT + can take a column list to specify which columns to set. Normally, all + columns of the foreign-key constraint are set; setting only a subset is + useful in some special cases. Consider the following example: + +CREATE TABLE tenants ( + tenant_id integer PRIMARY KEY +); + +CREATE TABLE users ( + tenant_id integer REFERENCES tenants ON DELETE CASCADE, + user_id integer NOT NULL, + PRIMARY KEY (tenant_id, user_id) +); + +CREATE TABLE posts ( + tenant_id integer REFERENCES tenants ON DELETE CASCADE, + post_id integer NOT NULL, + author_id integer, + PRIMARY KEY (tenant_id, post_id), + FOREIGN KEY (tenant_id, author_id) REFERENCES users ON DELETE SET NULL (author_id) +); + + Without the specification of the column, the foreign key would also set + the column tenant_id to null, but that column is still + required as part of the primary key. + + Analogous to ON DELETE there is also ON UPDATE which is invoked when a referenced - column is changed (updated). The possible actions are the same. + column is changed (updated). The possible actions are the same, + except that column lists cannot be specified for SET + NULL and SET DEFAULT. In this case, CASCADE means that the updated values of the referenced column(s) should be copied into the referencing row(s). diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index bc5dcba59c..8f14e4a5c4 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -138,7 +138,7 @@ WITH ( MODULUS numeric_literal, REM referential_action in a FOREIGN KEY/REFERENCES constraint is: -{ NO ACTION | RESTRICT | CASCADE | SET NULL | SET DEFAULT } +{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( column_name [, ... ] ) ] | SET DEFAULT [ ( column_name [, ... ] ) ] } diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 57d51a676a..b97bb9ded1 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -108,7 +108,7 @@ WITH ( MODULUS numeric_literal, REM referential_action in a FOREIGN KEY/REFERENCES constraint is: -{ NO ACTION | RESTRICT | CASCADE | SET NULL | SET DEFAULT } +{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( column_name [, ... ] ) ] | SET DEFAULT [ ( column_name [, ... ] ) ] } @@ -1169,19 +1169,23 @@ WITH ( MODULUS numeric_literal, REM - SET NULL + SET NULL [ ( column_name [, ... ] ) ] - Set the referencing column(s) to null. + Set all of the referencing columns, or a specified subset of the + referencing columns, to null. A subset of columns can only be + specified for ON DELETE actions. - SET DEFAULT + SET DEFAULT [ ( column_name [, ... ] ) ] - Set the referencing column(s) to their default values. + Set all of the referencing columns, or a specified subset of the + referencing columns, to their default values. A subset of columns + can only be specified for ON DELETE actions. (There must be a row in the referenced table matching the default values, if they are not null, or the operation will fail.) @@ -2223,6 +2227,16 @@ CREATE TABLE cities_partdef + + Foreign-Key Constraint Actions + + + The ability to specify column lists in the foreign-key actions + SET DEFAULT and SET NULL is a + PostgreSQL extension. + + + <literal>NULL</literal> <quote>Constraint</quote> diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index eadc3ff76b..6780ec53b7 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -2441,6 +2441,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr, 0, ' ', ' ', + NULL, + 0, ' ', NULL, /* not an exclusion constraint */ expr, /* Tree form of check constraint */ diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 27f0385ae0..1757cd3446 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -1969,6 +1969,8 @@ index_constraint_create(Relation heapRelation, 0, ' ', ' ', + NULL, + 0, ' ', indexInfo->ii_ExclusionOps, NULL, /* no check constraint */ diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index a4e890020f..2ced770c39 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -68,6 +68,8 @@ CreateConstraintEntry(const char *constraintName, int foreignNKeys, char foreignUpdateType, char foreignDeleteType, + const int16 *fkDeleteSetCols, + int numFkDeleteSetCols, char foreignMatchType, const Oid *exclOp, Node *conExpr, @@ -88,6 +90,7 @@ CreateConstraintEntry(const char *constraintName, ArrayType *conppeqopArray; ArrayType *conffeqopArray; ArrayType *conexclopArray; + ArrayType *confdelsetcolsArray; NameData cname; int i; ObjectAddress conobject; @@ -136,6 +139,16 @@ CreateConstraintEntry(const char *constraintName, fkdatums[i] = ObjectIdGetDatum(ffEqOp[i]); conffeqopArray = construct_array(fkdatums, foreignNKeys, OIDOID, sizeof(Oid), true, TYPALIGN_INT); + + if (numFkDeleteSetCols > 0) + { + for (i = 0; i < numFkDeleteSetCols; i++) + fkdatums[i] = Int16GetDatum(fkDeleteSetCols[i]); + confdelsetcolsArray = construct_array(fkdatums, numFkDeleteSetCols, + INT2OID, 2, true, TYPALIGN_SHORT); + } + else + confdelsetcolsArray = NULL; } else { @@ -143,6 +156,7 @@ CreateConstraintEntry(const char *constraintName, conpfeqopArray = NULL; conppeqopArray = NULL; conffeqopArray = NULL; + confdelsetcolsArray = NULL; } if (exclOp != NULL) @@ -211,6 +225,11 @@ CreateConstraintEntry(const char *constraintName, else nulls[Anum_pg_constraint_conffeqop - 1] = true; + if (confdelsetcolsArray) + values[Anum_pg_constraint_confdelsetcols - 1] = PointerGetDatum(confdelsetcolsArray); + else + nulls[Anum_pg_constraint_confdelsetcols - 1] = true; + if (conexclopArray) values[Anum_pg_constraint_conexclop - 1] = PointerGetDatum(conexclopArray); else @@ -1157,13 +1176,15 @@ get_primary_key_attnos(Oid relid, bool deferrableOk, Oid *constraintOid) /* * Extract data from the pg_constraint tuple of a foreign-key constraint. * - * All arguments save the first are output arguments; the last three of them - * can be passed as NULL if caller doesn't need them. + * All arguments save the first are output arguments. All output arguments + * other than numfks, conkey and confkey can be passed as NULL if caller + * doesn't need them. */ void DeconstructFkConstraintRow(HeapTuple tuple, int *numfks, AttrNumber *conkey, AttrNumber *confkey, - Oid *pf_eq_oprs, Oid *pp_eq_oprs, Oid *ff_eq_oprs) + Oid *pf_eq_oprs, Oid *pp_eq_oprs, Oid *ff_eq_oprs, + int *num_fk_del_set_cols, AttrNumber *fk_del_set_cols) { Oid constrId; Datum adatum; @@ -1260,6 +1281,32 @@ DeconstructFkConstraintRow(HeapTuple tuple, int *numfks, pfree(arr); /* free de-toasted copy, if any */ } + if (fk_del_set_cols) + { + adatum = SysCacheGetAttr(CONSTROID, tuple, + Anum_pg_constraint_confdelsetcols, &isNull); + if (isNull) + { + *num_fk_del_set_cols = 0; + } + else + { + int num_delete_cols; + + arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */ + if (ARR_NDIM(arr) != 1 || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != INT2OID) + elog(ERROR, "confdelsetcols is not a 1-D smallint array"); + num_delete_cols = ARR_DIMS(arr)[0]; + memcpy(fk_del_set_cols, ARR_DATA_PTR(arr), num_delete_cols * sizeof(int16)); + if ((Pointer) arr != DatumGetPointer(adatum)) + pfree(arr); /* free de-toasted copy, if any */ + + *num_fk_del_set_cols = num_delete_cols; + } + } + *numfks = numkeys; } diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index c821271306..47b29001d5 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -482,11 +482,16 @@ static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstra Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr, int numfks, int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators, + int numfkdelsetcols, int16 *fkdelsetcols, bool old_check_ok); +static void validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums, + int numfksetcols, const int16 *fksetcolsattnums, + List *fksetcols); static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr, int numfks, int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators, + int numfkdelsetcols, int16 *fkdelsetcols, bool old_check_ok, LOCKMODE lockmode); static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel, Relation partitionRel); @@ -8973,9 +8978,11 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, Oid pfeqoperators[INDEX_MAX_KEYS]; Oid ppeqoperators[INDEX_MAX_KEYS]; Oid ffeqoperators[INDEX_MAX_KEYS]; + int16 fkdelsetcols[INDEX_MAX_KEYS]; int i; int numfks, - numpks; + numpks, + numfkdelsetcols; Oid indexOid; bool old_check_ok; ObjectAddress address; @@ -9071,11 +9078,19 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, MemSet(pfeqoperators, 0, sizeof(pfeqoperators)); MemSet(ppeqoperators, 0, sizeof(ppeqoperators)); MemSet(ffeqoperators, 0, sizeof(ffeqoperators)); + MemSet(fkdelsetcols, 0, sizeof(fkdelsetcols)); numfks = transformColumnNameList(RelationGetRelid(rel), fkconstraint->fk_attrs, fkattnum, fktypoid); + numfkdelsetcols = transformColumnNameList(RelationGetRelid(rel), + fkconstraint->fk_del_set_cols, + fkdelsetcols, NULL); + validateFkOnDeleteSetColumns(numfks, fkattnum, + numfkdelsetcols, fkdelsetcols, + fkconstraint->fk_del_set_cols); + /* * If the attribute list for the referenced table was omitted, lookup the * definition of the primary key and use it. Otherwise, validate the @@ -9350,6 +9365,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, pfeqoperators, ppeqoperators, ffeqoperators, + numfkdelsetcols, + fkdelsetcols, old_check_ok); /* Now handle the referencing side. */ @@ -9362,6 +9379,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, pfeqoperators, ppeqoperators, ffeqoperators, + numfkdelsetcols, + fkdelsetcols, old_check_ok, lockmode); @@ -9373,6 +9392,40 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, return address; } +/* + * validateFkActionSetColumns + * Verifies that columns used in ON DELETE SET NULL/DEFAULT (...) + * column lists are valid. + */ +void +validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums, + int numfksetcols, const int16 *fksetcolsattnums, + List *fksetcols) +{ + for (int i = 0; i < numfksetcols; i++) + { + int16 setcol_attnum = fksetcolsattnums[i]; + bool seen = false; + + for (int j = 0; j < numfks; j++) + { + if (fkattnums[j] == setcol_attnum) + { + seen = true; + break; + } + } + + if (!seen) + { + char *col = strVal(list_nth(fksetcols, i)); + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("column \"%s\" referenced in ON DELETE SET action must be part of foreign key", col))); + } + } +} + /* * addFkRecurseReferenced * subroutine for ATAddForeignKeyConstraint; recurses on the referenced @@ -9394,6 +9447,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, * numfks is the number of columns in the foreign key * pkattnum is the attnum array of referenced attributes. * fkattnum is the attnum array of referencing attributes. + * numfkdelsetcols is the number of columns in the ON DELETE SET NULL/DELETE + * (...) clause + * fkdelsetcols is the attnum array of the columns in the ON DELETE SET + * NULL/DELETE clause * pf/pp/ffeqoperators are OID array of operators between columns. * old_check_ok signals that this constraint replaces an existing one that * was already validated (thus this one doesn't need validation). @@ -9403,7 +9460,9 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr, int numfks, int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators, - Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok) + Oid *ppeqoperators, Oid *ffeqoperators, + int numfkdelsetcols, int16 *fkdelsetcols, + bool old_check_ok) { ObjectAddress address; Oid constrOid; @@ -9478,6 +9537,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel, numfks, fkconstraint->fk_upd_action, fkconstraint->fk_del_action, + fkdelsetcols, + numfkdelsetcols, fkconstraint->fk_matchtype, NULL, /* no exclusion constraint */ NULL, /* no check constraint */ @@ -9559,6 +9620,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel, partIndexId, constrOid, numfks, mapped_pkattnum, fkattnum, pfeqoperators, ppeqoperators, ffeqoperators, + numfkdelsetcols, fkdelsetcols, old_check_ok); /* Done -- clean up (but keep the lock) */ @@ -9599,6 +9661,10 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel, * pkattnum is the attnum array of referenced attributes. * fkattnum is the attnum array of referencing attributes. * pf/pp/ffeqoperators are OID array of operators between columns. + * numfkdelsetcols is the number of columns in the ON DELETE SET NULL/DELETE + * (...) clause + * fkdelsetcols is the attnum array of the columns in the ON DELETE SET + * NULL/DELETE clause * old_check_ok signals that this constraint replaces an existing one that * was already validated (thus this one doesn't need validation). * lockmode is the lockmode to acquire on partitions when recursing. @@ -9608,6 +9674,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr, int numfks, int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators, + int numfkdelsetcols, int16 *fkdelsetcols, bool old_check_ok, LOCKMODE lockmode) { AssertArg(OidIsValid(parentConstr)); @@ -9746,6 +9813,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel, numfks, fkconstraint->fk_upd_action, fkconstraint->fk_del_action, + fkdelsetcols, + numfkdelsetcols, fkconstraint->fk_matchtype, NULL, NULL, @@ -9778,6 +9847,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel, pfeqoperators, ppeqoperators, ffeqoperators, + numfkdelsetcols, + fkdelsetcols, old_check_ok, lockmode); @@ -9883,6 +9954,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel) Oid conpfeqop[INDEX_MAX_KEYS]; Oid conppeqop[INDEX_MAX_KEYS]; Oid conffeqop[INDEX_MAX_KEYS]; + int numfkdelsetcols; + AttrNumber confdelsetcols[INDEX_MAX_KEYS]; Constraint *fkconstraint; tuple = SearchSysCache1(CONSTROID, constrOid); @@ -9915,7 +9988,9 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel) confkey, conpfeqop, conppeqop, - conffeqop); + conffeqop, + &numfkdelsetcols, + confdelsetcols); for (int i = 0; i < numfks; i++) mapped_confkey[i] = attmap->attnums[confkey[i] - 1]; @@ -9962,6 +10037,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel) conpfeqop, conppeqop, conffeqop, + numfkdelsetcols, + confdelsetcols, true); table_close(fkRel, NoLock); @@ -10032,6 +10109,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel) Oid conpfeqop[INDEX_MAX_KEYS]; Oid conppeqop[INDEX_MAX_KEYS]; Oid conffeqop[INDEX_MAX_KEYS]; + int numfkdelsetcols; + AttrNumber confdelsetcols[INDEX_MAX_KEYS]; Constraint *fkconstraint; bool attached; Oid indexOid; @@ -10063,7 +10142,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel) ShareRowExclusiveLock, NULL); DeconstructFkConstraintRow(tuple, &numfks, conkey, confkey, - conpfeqop, conppeqop, conffeqop); + conpfeqop, conppeqop, conffeqop, + &numfkdelsetcols, confdelsetcols); for (int i = 0; i < numfks; i++) mapped_conkey[i] = attmap->attnums[conkey[i] - 1]; @@ -10148,6 +10228,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel) numfks, fkconstraint->fk_upd_action, fkconstraint->fk_del_action, + confdelsetcols, + numfkdelsetcols, fkconstraint->fk_matchtype, NULL, NULL, @@ -10183,6 +10265,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel) conpfeqop, conppeqop, conffeqop, + numfkdelsetcols, + confdelsetcols, false, /* no old check exists */ AccessExclusiveLock); table_close(pkrel, NoLock); @@ -10804,7 +10888,7 @@ ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName, /* * transformColumnNameList - transform list of column names * - * Lookup each name and return its attnum and type OID + * Lookup each name and return its attnum and, optionally, type OID */ static int transformColumnNameList(Oid relId, List *colList, @@ -10831,7 +10915,8 @@ transformColumnNameList(Oid relId, List *colList, errmsg("cannot have more than %d keys in a foreign key", INDEX_MAX_KEYS))); attnums[attnum] = ((Form_pg_attribute) GETSTRUCT(atttuple))->attnum; - atttypids[attnum] = ((Form_pg_attribute) GETSTRUCT(atttuple))->atttypid; + if (atttypids != NULL) + atttypids[attnum] = ((Form_pg_attribute) GETSTRUCT(atttuple))->atttypid; ReleaseSysCache(atttuple); attnum++; } diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index d8890d2c74..7c8826089b 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -829,6 +829,8 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString, 0, ' ', ' ', + NULL, + 0, ' ', NULL, /* no exclusion */ NULL, /* no check constraint */ diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 9ab4034179..9dc4fdad49 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -3545,6 +3545,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid, 0, ' ', ' ', + NULL, + 0, ' ', NULL, /* not an exclusion constraint */ expr, /* Tree form of check constraint */ diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 297b6ee715..df0b747883 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3083,6 +3083,7 @@ _copyConstraint(const Constraint *from) COPY_SCALAR_FIELD(fk_matchtype); COPY_SCALAR_FIELD(fk_upd_action); COPY_SCALAR_FIELD(fk_del_action); + COPY_NODE_FIELD(fk_del_set_cols); COPY_NODE_FIELD(old_conpfeqop); COPY_SCALAR_FIELD(old_pktable_oid); COPY_SCALAR_FIELD(skip_validation); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index f537d3eb96..cb7ddd463c 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2725,6 +2725,7 @@ _equalConstraint(const Constraint *a, const Constraint *b) COMPARE_SCALAR_FIELD(fk_matchtype); COMPARE_SCALAR_FIELD(fk_upd_action); COMPARE_SCALAR_FIELD(fk_del_action); + COMPARE_NODE_FIELD(fk_del_set_cols); COMPARE_NODE_FIELD(old_conpfeqop); COMPARE_SCALAR_FIELD(old_pktable_oid); COMPARE_SCALAR_FIELD(skip_validation); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 3600c9fa36..91a89b6d51 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -3735,6 +3735,7 @@ _outConstraint(StringInfo str, const Constraint *node) WRITE_CHAR_FIELD(fk_matchtype); WRITE_CHAR_FIELD(fk_upd_action); WRITE_CHAR_FIELD(fk_del_action); + WRITE_NODE_FIELD(fk_del_set_cols); WRITE_NODE_FIELD(old_conpfeqop); WRITE_OID_FIELD(old_pktable_oid); WRITE_BOOL_FIELD(skip_validation); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 86ce33bd97..3d4dd43e47 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -141,6 +141,19 @@ typedef struct GroupClause List *list; } GroupClause; +/* Private structs for the result of key_actions and key_action productions */ +typedef struct KeyAction +{ + char action; + List *cols; +} KeyAction; + +typedef struct KeyActions +{ + KeyAction *updateAction; + KeyAction *deleteAction; +} KeyActions; + /* ConstraintAttributeSpec yields an integer bitmask of these flags: */ #define CAS_NOT_DEFERRABLE 0x01 #define CAS_DEFERRABLE 0x02 @@ -265,6 +278,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); struct SelectLimit *selectlimit; SetQuantifier setquantifier; struct GroupClause *groupclause; + struct KeyActions *keyactions; + struct KeyAction *keyaction; } %type stmt toplevel_stmt schema_stmt routine_body_stmt @@ -570,7 +585,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type column_compression opt_column_compression %type ColQualList %type ColConstraint ColConstraintElem ConstraintAttr -%type key_actions key_delete key_match key_update key_action +%type key_match +%type key_delete key_update key_action +%type key_actions %type ConstraintAttributeSpec ConstraintAttributeElem %type ExistingIndex @@ -3690,8 +3707,9 @@ ColConstraintElem: n->fk_attrs = NIL; n->pk_attrs = $3; n->fk_matchtype = $4; - n->fk_upd_action = (char) ($5 >> 8); - n->fk_del_action = (char) ($5 & 0xFF); + n->fk_upd_action = ($5)->updateAction->action; + n->fk_del_action = ($5)->deleteAction->action; + n->fk_del_set_cols = ($5)->deleteAction->cols; n->skip_validation = false; n->initially_valid = true; $$ = (Node *)n; @@ -3901,8 +3919,9 @@ ConstraintElem: n->fk_attrs = $4; n->pk_attrs = $8; n->fk_matchtype = $9; - n->fk_upd_action = (char) ($10 >> 8); - n->fk_del_action = (char) ($10 & 0xFF); + n->fk_upd_action = ($10)->updateAction->action; + n->fk_del_action = ($10)->deleteAction->action; + n->fk_del_set_cols = ($10)->deleteAction->cols; processCASbits($11, @11, "FOREIGN KEY", &n->deferrable, &n->initdeferred, &n->skip_validation, NULL, @@ -3980,37 +3999,106 @@ OptWhereClause: | /*EMPTY*/ { $$ = NULL; } ; -/* - * We combine the update and delete actions into one value temporarily - * for simplicity of parsing, and then break them down again in the - * calling production. update is in the left 8 bits, delete in the right. - * Note that NOACTION is the default. - */ key_actions: key_update - { $$ = ($1 << 8) | (FKCONSTR_ACTION_NOACTION & 0xFF); } + { + KeyActions *n = palloc(sizeof(KeyActions)); + n->updateAction = $1; + n->deleteAction = palloc(sizeof(KeyAction)); + n->deleteAction->action = FKCONSTR_ACTION_NOACTION; + n->deleteAction->cols = NIL; + $$ = n; + } | key_delete - { $$ = (FKCONSTR_ACTION_NOACTION << 8) | ($1 & 0xFF); } + { + KeyActions *n = palloc(sizeof(KeyActions)); + n->updateAction = palloc(sizeof(KeyAction)); + n->updateAction->action = FKCONSTR_ACTION_NOACTION; + n->updateAction->cols = NIL; + n->deleteAction = $1; + $$ = n; + } | key_update key_delete - { $$ = ($1 << 8) | ($2 & 0xFF); } + { + KeyActions *n = palloc(sizeof(KeyActions)); + n->updateAction = $1; + n->deleteAction = $2; + $$ = n; + } | key_delete key_update - { $$ = ($2 << 8) | ($1 & 0xFF); } + { + KeyActions *n = palloc(sizeof(KeyActions)); + n->updateAction = $2; + n->deleteAction = $1; + $$ = n; + } | /*EMPTY*/ - { $$ = (FKCONSTR_ACTION_NOACTION << 8) | (FKCONSTR_ACTION_NOACTION & 0xFF); } + { + KeyActions *n = palloc(sizeof(KeyActions)); + n->updateAction = palloc(sizeof(KeyAction)); + n->updateAction->action = FKCONSTR_ACTION_NOACTION; + n->updateAction->cols = NIL; + n->deleteAction = palloc(sizeof(KeyAction)); + n->deleteAction->action = FKCONSTR_ACTION_NOACTION; + n->deleteAction->cols = NIL; + $$ = n; + } ; -key_update: ON UPDATE key_action { $$ = $3; } +key_update: ON UPDATE key_action + { + if (($3)->cols) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("a column list with %s is only supported for ON DELETE actions", + ($3)->action == FKCONSTR_ACTION_SETNULL ? "SET NULL" : "SET DEFAULT"), + parser_errposition(@1))); + $$ = $3; + } ; -key_delete: ON DELETE_P key_action { $$ = $3; } +key_delete: ON DELETE_P key_action + { + $$ = $3; + } ; key_action: - NO ACTION { $$ = FKCONSTR_ACTION_NOACTION; } - | RESTRICT { $$ = FKCONSTR_ACTION_RESTRICT; } - | CASCADE { $$ = FKCONSTR_ACTION_CASCADE; } - | SET NULL_P { $$ = FKCONSTR_ACTION_SETNULL; } - | SET DEFAULT { $$ = FKCONSTR_ACTION_SETDEFAULT; } + NO ACTION + { + KeyAction *n = palloc(sizeof(KeyAction)); + n->action = FKCONSTR_ACTION_NOACTION; + n->cols = NIL; + $$ = n; + } + | RESTRICT + { + KeyAction *n = palloc(sizeof(KeyAction)); + n->action = FKCONSTR_ACTION_RESTRICT; + n->cols = NIL; + $$ = n; + } + | CASCADE + { + KeyAction *n = palloc(sizeof(KeyAction)); + n->action = FKCONSTR_ACTION_CASCADE; + n->cols = NIL; + $$ = n; + } + | SET NULL_P opt_column_list + { + KeyAction *n = palloc(sizeof(KeyAction)); + n->action = FKCONSTR_ACTION_SETNULL; + n->cols = $3; + $$ = n; + } + | SET DEFAULT opt_column_list + { + KeyAction *n = palloc(sizeof(KeyAction)); + n->action = FKCONSTR_ACTION_SETDEFAULT; + n->cols = $3; + $$ = n; + } ; OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; } diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 96269fc2ad..8ebb2a50a1 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -73,11 +73,14 @@ #define RI_PLAN_CHECK_LOOKUPPK_FROM_PK 2 #define RI_PLAN_LAST_ON_PK RI_PLAN_CHECK_LOOKUPPK_FROM_PK /* these queries are executed against the FK (referencing) table: */ -#define RI_PLAN_CASCADE_DEL_DODELETE 3 -#define RI_PLAN_CASCADE_UPD_DOUPDATE 4 -#define RI_PLAN_RESTRICT_CHECKREF 5 -#define RI_PLAN_SETNULL_DOUPDATE 6 -#define RI_PLAN_SETDEFAULT_DOUPDATE 7 +#define RI_PLAN_CASCADE_ONDELETE 3 +#define RI_PLAN_CASCADE_ONUPDATE 4 +/* For RESTRICT, the same plan can be used for both ON DELETE and ON UPDATE triggers. */ +#define RI_PLAN_RESTRICT 5 +#define RI_PLAN_SETNULL_ONDELETE 6 +#define RI_PLAN_SETNULL_ONUPDATE 7 +#define RI_PLAN_SETDEFAULT_ONDELETE 8 +#define RI_PLAN_SETDEFAULT_ONUPDATE 9 #define MAX_QUOTED_NAME_LEN (NAMEDATALEN*2+3) #define MAX_QUOTED_REL_NAME_LEN (MAX_QUOTED_NAME_LEN*2) @@ -110,6 +113,8 @@ typedef struct RI_ConstraintInfo Oid fk_relid; /* referencing relation */ char confupdtype; /* foreign key's ON UPDATE action */ char confdeltype; /* foreign key's ON DELETE action */ + int ndelsetcols; /* number of columns referenced in ON DELETE SET clause */ + int16 confdelsetcols[RI_MAX_NUMKEYS]; /* attnums of cols to set on delete */ char confmatchtype; /* foreign key's match type */ int nkeys; /* number of key columns */ int16 pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */ @@ -180,7 +185,7 @@ static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, TupleTableSlot *oldslot, const RI_ConstraintInfo *riinfo); static Datum ri_restrict(TriggerData *trigdata, bool is_no_action); -static Datum ri_set(TriggerData *trigdata, bool is_set_null); +static Datum ri_set(TriggerData *trigdata, bool is_set_null, int tgkind); static void quoteOneName(char *buffer, const char *name); static void quoteRelationName(char *buffer, Relation rel); static void ri_GenerateQual(StringInfo buf, @@ -660,7 +665,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action) * Fetch or prepare a saved plan for the restrict lookup (it's the same * query for delete and update cases) */ - ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_RESTRICT_CHECKREF); + ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_RESTRICT); if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { @@ -767,7 +772,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) elog(ERROR, "SPI_connect failed"); /* Fetch or prepare a saved plan for the cascaded delete */ - ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_DEL_DODELETE); + ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_ONDELETE); if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { @@ -876,7 +881,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) elog(ERROR, "SPI_connect failed"); /* Fetch or prepare a saved plan for the cascaded update */ - ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_UPD_DOUPDATE); + ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_ONUPDATE); if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { @@ -970,7 +975,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS) ri_CheckTrigger(fcinfo, "RI_FKey_setnull_del", RI_TRIGTYPE_DELETE); /* Share code with UPDATE case */ - return ri_set((TriggerData *) fcinfo->context, true); + return ri_set((TriggerData *) fcinfo->context, true, RI_TRIGTYPE_DELETE); } /* @@ -985,7 +990,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) ri_CheckTrigger(fcinfo, "RI_FKey_setnull_upd", RI_TRIGTYPE_UPDATE); /* Share code with DELETE case */ - return ri_set((TriggerData *) fcinfo->context, true); + return ri_set((TriggerData *) fcinfo->context, true, RI_TRIGTYPE_UPDATE); } /* @@ -1000,7 +1005,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_del", RI_TRIGTYPE_DELETE); /* Share code with UPDATE case */ - return ri_set((TriggerData *) fcinfo->context, false); + return ri_set((TriggerData *) fcinfo->context, false, RI_TRIGTYPE_DELETE); } /* @@ -1015,7 +1020,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_upd", RI_TRIGTYPE_UPDATE); /* Share code with DELETE case */ - return ri_set((TriggerData *) fcinfo->context, false); + return ri_set((TriggerData *) fcinfo->context, false, RI_TRIGTYPE_UPDATE); } /* @@ -1025,7 +1030,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) * NULL, and ON UPDATE SET DEFAULT. */ static Datum -ri_set(TriggerData *trigdata, bool is_set_null) +ri_set(TriggerData *trigdata, bool is_set_null, int tgkind) { const RI_ConstraintInfo *riinfo; Relation fk_rel; @@ -1033,6 +1038,7 @@ ri_set(TriggerData *trigdata, bool is_set_null) TupleTableSlot *oldslot; RI_QueryKey qkey; SPIPlanPtr qplan; + int32 queryno; riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, trigdata->tg_relation, true); @@ -1051,18 +1057,28 @@ ri_set(TriggerData *trigdata, bool is_set_null) elog(ERROR, "SPI_connect failed"); /* - * Fetch or prepare a saved plan for the set null/default operation (it's - * the same query for delete and update cases) + * Fetch or prepare a saved plan for the trigger. */ - ri_BuildQueryKey(&qkey, riinfo, - (is_set_null - ? RI_PLAN_SETNULL_DOUPDATE - : RI_PLAN_SETDEFAULT_DOUPDATE)); + switch (tgkind) { + case RI_TRIGTYPE_UPDATE: + queryno = is_set_null + ? RI_PLAN_SETNULL_ONUPDATE + : RI_PLAN_SETDEFAULT_ONUPDATE; + break; + case RI_TRIGTYPE_DELETE: + queryno = is_set_null + ? RI_PLAN_SETNULL_ONDELETE + : RI_PLAN_SETDEFAULT_ONDELETE; + break; + default: + elog(ERROR, "invalid tgkind passed to ri_set"); + } + + ri_BuildQueryKey(&qkey, riinfo, queryno); if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { StringInfoData querybuf; - StringInfoData qualbuf; char fkrelname[MAX_QUOTED_REL_NAME_LEN]; char attname[MAX_QUOTED_NAME_LEN]; char paramname[16]; @@ -1070,6 +1086,32 @@ ri_set(TriggerData *trigdata, bool is_set_null) const char *qualsep; Oid queryoids[RI_MAX_NUMKEYS]; const char *fk_only; + int num_cols_to_set; + const int16 *set_cols; + + switch (tgkind) { + case RI_TRIGTYPE_UPDATE: + num_cols_to_set = riinfo->nkeys; + set_cols = riinfo->fk_attnums; + break; + case RI_TRIGTYPE_DELETE: + /* + * If confdelsetcols are present, then we only update + * the columns specified in that array, otherwise we + * update all the referencing columns. + */ + if (riinfo->ndelsetcols != 0) { + num_cols_to_set = riinfo->ndelsetcols; + set_cols = riinfo->confdelsetcols; + } + else { + num_cols_to_set = riinfo->nkeys; + set_cols = riinfo->fk_attnums; + } + break; + default: + elog(ERROR, "invalid tgkind passed to ri_set"); + } /* ---------- * The query string built is @@ -1080,13 +1122,29 @@ ri_set(TriggerData *trigdata, bool is_set_null) * ---------- */ initStringInfo(&querybuf); - initStringInfo(&qualbuf); fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? "" : "ONLY "; quoteRelationName(fkrelname, fk_rel); appendStringInfo(&querybuf, "UPDATE %s%s SET", fk_only, fkrelname); + + /* + * Add assignment clauses + */ querysep = ""; + for (int i = 0; i < num_cols_to_set; i++) + { + quoteOneName(attname, RIAttName(fk_rel, set_cols[i])); + appendStringInfo(&querybuf, + "%s %s = %s", + querysep, attname, + is_set_null ? "NULL" : "DEFAULT"); + querysep = ","; + } + + /* + * Add WHERE clause + */ qualsep = "WHERE"; for (int i = 0; i < riinfo->nkeys; i++) { @@ -1097,22 +1155,17 @@ ri_set(TriggerData *trigdata, bool is_set_null) quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[i])); - appendStringInfo(&querybuf, - "%s %s = %s", - querysep, attname, - is_set_null ? "NULL" : "DEFAULT"); + sprintf(paramname, "$%d", i + 1); - ri_GenerateQual(&qualbuf, qualsep, + ri_GenerateQual(&querybuf, qualsep, paramname, pk_type, riinfo->pf_eq_oprs[i], attname, fk_type); if (pk_coll != fk_coll && !get_collation_isdeterministic(pk_coll)) ri_GenerateQualCollation(&querybuf, pk_coll); - querysep = ","; qualsep = "AND"; queryoids[i] = pk_type; } - appendBinaryStringInfo(&querybuf, qualbuf.data, qualbuf.len); /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, @@ -2098,7 +2151,9 @@ ri_LoadConstraintInfo(Oid constraintOid) riinfo->pk_attnums, riinfo->pf_eq_oprs, riinfo->pp_eq_oprs, - riinfo->ff_eq_oprs); + riinfo->ff_eq_oprs, + &riinfo->ndelsetcols, + riinfo->confdelsetcols); ReleaseSysCache(tup); diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 6b4022c3bc..8da525c715 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -2287,6 +2287,16 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, if (string) appendStringInfo(&buf, " ON DELETE %s", string); + /* Add columns specified to SET NULL or SET DEFAULT if provided. */ + val = SysCacheGetAttr(CONSTROID, tup, + Anum_pg_constraint_confdelsetcols, &isnull); + if (!isnull) + { + appendStringInfo(&buf, " ("); + decompile_column_index_array(val, conForm->conrelid, &buf); + appendStringInfo(&buf, ")"); + } + break; } case CONSTRAINT_PRIMARY: diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 2b32edebe7..105d8d4601 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -4592,7 +4592,7 @@ RelationGetFKeyList(Relation relation) info->conkey, info->confkey, info->conpfeqop, - NULL, NULL); + NULL, NULL, NULL, NULL); /* Add FK's node to the result list */ result = lappend(result, info); diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index d0fa1d1222..c8259b833b 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202111301 +#define CATALOG_VERSION_NO 202112081 #endif diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h index e75baa8e1e..eab50053b0 100644 --- a/src/include/catalog/pg_constraint.h +++ b/src/include/catalog/pg_constraint.h @@ -138,6 +138,12 @@ CATALOG(pg_constraint,2606,ConstraintRelationId) */ Oid conffeqop[1] BKI_LOOKUP(pg_operator); + /* + * If a foreign key with an ON DELETE SET NULL/DEFAULT action, the subset + * of conkey to updated. If null, all columns are updated. + */ + int16 confdelsetcols[1]; + /* * If an exclusion constraint, the OIDs of the exclusion operators for * each column of the constraint @@ -220,6 +226,8 @@ extern Oid CreateConstraintEntry(const char *constraintName, int foreignNKeys, char foreignUpdateType, char foreignDeleteType, + const int16 *fkDeleteSetCols, + int numFkDeleteSetCols, char foreignMatchType, const Oid *exclOp, Node *conExpr, @@ -254,7 +262,8 @@ extern Bitmapset *get_primary_key_attnos(Oid relid, bool deferrableOk, Oid *constraintOid); extern void DeconstructFkConstraintRow(HeapTuple tuple, int *numfks, AttrNumber *conkey, AttrNumber *confkey, - Oid *pf_eq_oprs, Oid *pp_eq_oprs, Oid *ff_eq_oprs); + Oid *pf_eq_oprs, Oid *pp_eq_oprs, Oid *ff_eq_oprs, + int *num_fk_del_set_cols, AttrNumber *fk_del_set_cols); extern bool check_functional_grouping(Oid relid, Index varno, Index varlevelsup, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 067138e6b5..4c5a8a39bf 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2301,6 +2301,7 @@ typedef struct Constraint char fk_matchtype; /* FULL, PARTIAL, SIMPLE */ char fk_upd_action; /* ON UPDATE action */ char fk_del_action; /* ON DELETE action */ + List *fk_del_set_cols; /* ON DELETE SET NULL/DEFAULT (col1, col2) */ List *old_conpfeqop; /* pg_constraint.conpfeqop of my former self */ Oid old_pktable_oid; /* pg_constraint.confrelid of my former * self */ diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index bf794dce9d..4c5274983d 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -755,6 +755,44 @@ SELECT * from FKTABLE; | | | 1 (7 rows) +DROP TABLE FKTABLE; +DROP TABLE PKTABLE; +-- Test for ON DELETE SET NULL/DEFAULT (column_list); +CREATE TABLE PKTABLE (tid int, id int, PRIMARY KEY (tid, id)); +CREATE TABLE FKTABLE (tid int, id int, foo int, FOREIGN KEY (tid, id) REFERENCES PKTABLE ON DELETE SET NULL (bar)); +ERROR: column "bar" referenced in foreign key constraint does not exist +CREATE TABLE FKTABLE (tid int, id int, foo int, FOREIGN KEY (tid, id) REFERENCES PKTABLE ON DELETE SET NULL (foo)); +ERROR: column "foo" referenced in ON DELETE SET action must be part of foreign key +CREATE TABLE FKTABLE (tid int, id int, foo int, FOREIGN KEY (tid, foo) REFERENCES PKTABLE ON UPDATE SET NULL (foo)); +ERROR: a column list with SET NULL is only supported for ON DELETE actions +LINE 1: ...oo int, FOREIGN KEY (tid, foo) REFERENCES PKTABLE ON UPDATE ... + ^ +CREATE TABLE FKTABLE ( + tid int, id int, + fk_id_del_set_null int, + fk_id_del_set_default int DEFAULT 0, + FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES PKTABLE ON DELETE SET NULL (fk_id_del_set_null), + FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (fk_id_del_set_default) +); +SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass::oid ORDER BY oid; + pg_get_constraintdef +-------------------------------------------------------------------------------------------------------------------- + FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES pktable(tid, id) ON DELETE SET NULL (fk_id_del_set_null) + FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES pktable(tid, id) ON DELETE SET DEFAULT (fk_id_del_set_default) +(2 rows) + +INSERT INTO PKTABLE VALUES (1, 0), (1, 1), (1, 2); +INSERT INTO FKTABLE VALUES + (1, 1, 1, NULL), + (1, 2, NULL, 2); +DELETE FROM PKTABLE WHERE id = 1 OR id = 2; +SELECT * FROM FKTABLE ORDER BY id; + tid | id | fk_id_del_set_null | fk_id_del_set_default +-----+----+--------------------+----------------------- + 1 | 1 | | + 1 | 2 | | 0 +(2 rows) + DROP TABLE FKTABLE; DROP TABLE PKTABLE; CREATE TABLE PKTABLE (ptest1 int PRIMARY KEY); @@ -1734,6 +1772,39 @@ SELECT * FROM fk_partitioned_fk WHERE b = 142857; 2501 | 142857 (1 row) +-- ON DELETE SET NULL column_list +ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey; +ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b) + REFERENCES fk_notpartitioned_pk + ON DELETE SET NULL (a); +BEGIN; +DELETE FROM fk_notpartitioned_pk WHERE b = 142857; +SELECT * FROM fk_partitioned_fk WHERE a IS NOT NULL OR b IS NOT NULL ORDER BY a NULLS LAST; + a | b +------+-------- + 2502 | + | 142857 +(2 rows) + +ROLLBACK; +-- ON DELETE SET DEFAULT column_list +ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey; +ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b) + REFERENCES fk_notpartitioned_pk + ON DELETE SET DEFAULT (a); +BEGIN; +DELETE FROM fk_partitioned_fk; +DELETE FROM fk_notpartitioned_pk; +INSERT INTO fk_notpartitioned_pk VALUES (500, 100000), (2501, 100000); +INSERT INTO fk_partitioned_fk VALUES (500, 100000); +DELETE FROM fk_notpartitioned_pk WHERE a = 500; +SELECT * FROM fk_partitioned_fk ORDER BY a; + a | b +------+-------- + 2501 | 100000 +(1 row) + +ROLLBACK; -- ON UPDATE/DELETE CASCADE ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey; ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b) diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index de417b62b6..fa781b6e32 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -463,6 +463,33 @@ SELECT * from FKTABLE; DROP TABLE FKTABLE; DROP TABLE PKTABLE; +-- Test for ON DELETE SET NULL/DEFAULT (column_list); +CREATE TABLE PKTABLE (tid int, id int, PRIMARY KEY (tid, id)); +CREATE TABLE FKTABLE (tid int, id int, foo int, FOREIGN KEY (tid, id) REFERENCES PKTABLE ON DELETE SET NULL (bar)); +CREATE TABLE FKTABLE (tid int, id int, foo int, FOREIGN KEY (tid, id) REFERENCES PKTABLE ON DELETE SET NULL (foo)); +CREATE TABLE FKTABLE (tid int, id int, foo int, FOREIGN KEY (tid, foo) REFERENCES PKTABLE ON UPDATE SET NULL (foo)); +CREATE TABLE FKTABLE ( + tid int, id int, + fk_id_del_set_null int, + fk_id_del_set_default int DEFAULT 0, + FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES PKTABLE ON DELETE SET NULL (fk_id_del_set_null), + FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (fk_id_del_set_default) +); + +SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass::oid ORDER BY oid; + +INSERT INTO PKTABLE VALUES (1, 0), (1, 1), (1, 2); +INSERT INTO FKTABLE VALUES + (1, 1, 1, NULL), + (1, 2, NULL, 2); + +DELETE FROM PKTABLE WHERE id = 1 OR id = 2; + +SELECT * FROM FKTABLE ORDER BY id; + +DROP TABLE FKTABLE; +DROP TABLE PKTABLE; + CREATE TABLE PKTABLE (ptest1 int PRIMARY KEY); CREATE TABLE FKTABLE_FAIL1 ( ftest1 int, CONSTRAINT fkfail1 FOREIGN KEY (ftest2) REFERENCES PKTABLE); CREATE TABLE FKTABLE_FAIL2 ( ftest1 int, CONSTRAINT fkfail1 FOREIGN KEY (ftest1) REFERENCES PKTABLE(ptest2)); @@ -1284,6 +1311,30 @@ INSERT INTO fk_notpartitioned_pk VALUES (2501, 142857); UPDATE fk_notpartitioned_pk SET a = 1500 WHERE a = 2502; SELECT * FROM fk_partitioned_fk WHERE b = 142857; +-- ON DELETE SET NULL column_list +ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey; +ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b) + REFERENCES fk_notpartitioned_pk + ON DELETE SET NULL (a); +BEGIN; +DELETE FROM fk_notpartitioned_pk WHERE b = 142857; +SELECT * FROM fk_partitioned_fk WHERE a IS NOT NULL OR b IS NOT NULL ORDER BY a NULLS LAST; +ROLLBACK; + +-- ON DELETE SET DEFAULT column_list +ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey; +ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b) + REFERENCES fk_notpartitioned_pk + ON DELETE SET DEFAULT (a); +BEGIN; +DELETE FROM fk_partitioned_fk; +DELETE FROM fk_notpartitioned_pk; +INSERT INTO fk_notpartitioned_pk VALUES (500, 100000), (2501, 100000); +INSERT INTO fk_partitioned_fk VALUES (500, 100000); +DELETE FROM fk_notpartitioned_pk WHERE a = 500; +SELECT * FROM fk_partitioned_fk ORDER BY a; +ROLLBACK; + -- ON UPDATE/DELETE CASCADE ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey; ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b)