diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fd6910ddbe..61fc7fd0fa 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2703,6 +2703,30 @@ SCRAM-SHA-256$<iteration count>:&l
+
+
+ confupdsetcols int2[]
+ (references pg_attribute.attnum)
+
+
+ If a foreign key with a SET NULL or SET
+ DEFAULT update action, which columns should be updated.
+ If empty, all of the referencing columns should be updated.
+
+
+
+
+
+ confdelsetcols int2[]
+ (references pg_attribute.attnum)
+
+
+ If a foreign key with a SET NULL or SET
+ DEFAULT delete action, which columns should be updated.
+ If empty, all of the referencing columns should be updated.
+
+
+
conexclop oid[]
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..4542197cd2 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -105,6 +105,11 @@ WITH ( MODULUS numeric_literal, REM
exclude_element in an EXCLUDE constraint is:
{ column_name | ( expression ) } [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+
+referential_action in an EXCLUDE constraint is:
+
+{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( column_name [, ... ] ) ] | SET DEFAULT [ ( column_name [, ... ] ) ] }
+
@@ -1165,19 +1170,21 @@ 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.
- 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.
(There must be a row in the referenced table matching the default
values, if they are not null, or the operation will fail.)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 81cc39fb70..7846c1f182 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2483,7 +2483,11 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
NULL,
0,
' ',
+ NULL,
+ 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 26bfa74ce7..7673636aaf 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1958,7 +1958,11 @@ index_constraint_create(Relation heapRelation,
NULL,
0,
' ',
+ NULL,
+ 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..1a2f1ecee3 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -67,7 +67,11 @@ CreateConstraintEntry(const char *constraintName,
const Oid *ffEqOp,
int foreignNKeys,
char foreignUpdateType,
+ const int16 *fkUpdateSetCols,
+ int numFkUpdateSetCols,
char foreignDeleteType,
+ const int16 *fkDeleteSetCols,
+ int numFkDeleteSetCols,
char foreignMatchType,
const Oid *exclOp,
Node *conExpr,
@@ -88,6 +92,8 @@ CreateConstraintEntry(const char *constraintName,
ArrayType *conppeqopArray;
ArrayType *conffeqopArray;
ArrayType *conexclopArray;
+ ArrayType *confupdsetcolsArray;
+ ArrayType *confdelsetcolsArray;
NameData cname;
int i;
ObjectAddress conobject;
@@ -136,6 +142,14 @@ CreateConstraintEntry(const char *constraintName,
fkdatums[i] = ObjectIdGetDatum(ffEqOp[i]);
conffeqopArray = construct_array(fkdatums, foreignNKeys,
OIDOID, sizeof(Oid), true, TYPALIGN_INT);
+ for (i = 0; i < numFkUpdateSetCols; i++)
+ fkdatums[i] = Int16GetDatum(fkUpdateSetCols[i]);
+ confupdsetcolsArray = construct_array(fkdatums, numFkUpdateSetCols,
+ INT2OID, 2, true, TYPALIGN_SHORT);
+ for (i = 0; i < numFkDeleteSetCols; i++)
+ fkdatums[i] = Int16GetDatum(fkDeleteSetCols[i]);
+ confdelsetcolsArray = construct_array(fkdatums, numFkDeleteSetCols,
+ INT2OID, 2, true, TYPALIGN_SHORT);
}
else
{
@@ -143,6 +157,8 @@ CreateConstraintEntry(const char *constraintName,
conpfeqopArray = NULL;
conppeqopArray = NULL;
conffeqopArray = NULL;
+ confupdsetcolsArray = NULL;
+ confdelsetcolsArray = NULL;
}
if (exclOp != NULL)
@@ -211,6 +227,16 @@ CreateConstraintEntry(const char *constraintName,
else
nulls[Anum_pg_constraint_conffeqop - 1] = true;
+ if (confupdsetcolsArray)
+ values[Anum_pg_constraint_confupdsetcols - 1] = PointerGetDatum(confupdsetcolsArray);
+ else
+ nulls[Anum_pg_constraint_confupdsetcols - 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 +1183,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; fields 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_upd_set_cols, AttrNumber *fk_upd_set_cols,
+ int *num_fk_del_set_cols, AttrNumber *fk_del_set_cols)
{
Oid constrId;
Datum adatum;
@@ -1260,6 +1288,52 @@ DeconstructFkConstraintRow(HeapTuple tuple, int *numfks,
pfree(arr); /* free de-toasted copy, if any */
}
+ if (fk_upd_set_cols)
+ {
+ int num_update_cols = 0;
+ adatum = SysCacheGetAttr(CONSTROID, tuple,
+ Anum_pg_constraint_confupdsetcols, &isNull);
+ if (isNull)
+ elog(ERROR, "null confupdsetcols for foreign-key constraint %u", constrId);
+ arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
+ if (ARR_NDIM(arr) != 0)
+ {
+ if (ARR_NDIM(arr) != 1 ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != INT2OID)
+ elog(ERROR, "confupdsetcols is not or an empty or 1-D smallint array");
+ num_update_cols = ARR_DIMS(arr)[0];
+ memcpy(fk_upd_set_cols, ARR_DATA_PTR(arr), num_update_cols * sizeof(int16));
+ }
+ if ((Pointer) arr != DatumGetPointer(adatum))
+ pfree(arr); /* free de-toasted copy, if any */
+
+ *num_fk_upd_set_cols = num_update_cols;
+ }
+
+ if (fk_del_set_cols)
+ {
+ int num_delete_cols = 0;
+ adatum = SysCacheGetAttr(CONSTROID, tuple,
+ Anum_pg_constraint_confdelsetcols, &isNull);
+ if (isNull)
+ elog(ERROR, "null confdelsetcols for foreign-key constraint %u", constrId);
+ arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
+ if (ARR_NDIM(arr) != 0)
+ {
+ if (ARR_NDIM(arr) != 1 ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != INT2OID)
+ elog(ERROR, "confdelsetcols is not an empty or 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 1a2f159f24..7bebf3037d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -482,11 +482,19 @@ 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 numfkupdsetcols, int16 *fkupdsetcols,
+ int numfkdelsetcols, int16 *fkdelsetcols,
bool old_check_ok);
+static void validateFkActionSetColumns(int numfks, int16 *fkattnums,
+ int numfksetcols, int16 *fksetcolsattnums,
+ List *fksetcols,
+ char *trigger);
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 numfkupdsetcols, int16 *fkupdsetcols,
+ int numfkdelsetcols, int16 *fkdelsetcols,
bool old_check_ok, LOCKMODE lockmode);
static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
Relation partitionRel);
@@ -8961,9 +8969,13 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
Oid pfeqoperators[INDEX_MAX_KEYS];
Oid ppeqoperators[INDEX_MAX_KEYS];
Oid ffeqoperators[INDEX_MAX_KEYS];
+ int16 fkupdsetcols[INDEX_MAX_KEYS];
+ int16 fkdelsetcols[INDEX_MAX_KEYS];
int i;
int numfks,
- numpks;
+ numpks,
+ numfkupdsetcols,
+ numfkdelsetcols;
Oid indexOid;
bool old_check_ok;
ObjectAddress address;
@@ -9059,11 +9071,27 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
MemSet(pfeqoperators, 0, sizeof(pfeqoperators));
MemSet(ppeqoperators, 0, sizeof(ppeqoperators));
MemSet(ffeqoperators, 0, sizeof(ffeqoperators));
+ MemSet(fkupdsetcols, 0, sizeof(fkupdsetcols));
+ MemSet(fkdelsetcols, 0, sizeof(fkdelsetcols));
numfks = transformColumnNameList(RelationGetRelid(rel),
fkconstraint->fk_attrs,
fkattnum, fktypoid);
+ numfkupdsetcols = transformColumnNameList(RelationGetRelid(rel),
+ fkconstraint->fk_upd_set_cols,
+ fkupdsetcols, NULL);
+ validateFkActionSetColumns(numfks, fkattnum,
+ numfkupdsetcols, fkupdsetcols,
+ fkconstraint->fk_upd_set_cols, "UPDATE");
+
+ numfkdelsetcols = transformColumnNameList(RelationGetRelid(rel),
+ fkconstraint->fk_del_set_cols,
+ fkdelsetcols, NULL);
+ validateFkActionSetColumns(numfks, fkattnum,
+ numfkdelsetcols, fkdelsetcols,
+ fkconstraint->fk_del_set_cols, "DELETE");
+
/*
* If the attribute list for the referenced table was omitted, lookup the
* definition of the primary key and use it. Otherwise, validate the
@@ -9338,6 +9366,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkupdsetcols,
+ fkupdsetcols,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok);
/* Now handle the referencing side. */
@@ -9350,6 +9382,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkupdsetcols,
+ fkupdsetcols,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok,
lockmode);
@@ -9361,6 +9397,36 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
return address;
}
+/*
+ * validateFkActionSetColumns
+ * Verifies that columns used in ON UPDATE/DELETE SET NULL/DEFAULT (...)
+ * column lists are valid.
+ */
+void validateFkActionSetColumns(
+ int numfks, int16 *fkattnums,
+ int numfksetcols, int16 *fksetcolsattnums,
+ List *fksetcols,
+ char *trigger)
+{
+ for (int i = 0; i < numfksetcols; i++) {
+ int 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 %s SET action must be part of foreign key", col, trigger)));
+ }
+ }
+}
+
/*
* addFkRecurseReferenced
* subroutine for ATAddForeignKeyConstraint; recurses on the referenced
@@ -9382,6 +9448,14 @@ 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.
+ * numfkupdsetcols is the number of columns in the ON UPDATE SET NULL/DELETE
+ * (...) clause
+ * fkupdsetcols is the attnum array of the columns in the ON UPDATE SET
+ * NULL/DELETE clause
+ * 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).
@@ -9391,7 +9465,10 @@ 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 numfkupdsetcols, int16 *fkupdsetcols,
+ int numfkdelsetcols, int16 *fkdelsetcols,
+ bool old_check_ok)
{
ObjectAddress address;
Oid constrOid;
@@ -9465,7 +9542,11 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
ffeqoperators,
numfks,
fkconstraint->fk_upd_action,
+ fkupdsetcols,
+ numfkupdsetcols,
fkconstraint->fk_del_action,
+ fkdelsetcols,
+ numfkdelsetcols,
fkconstraint->fk_matchtype,
NULL, /* no exclusion constraint */
NULL, /* no check constraint */
@@ -9547,6 +9628,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
partIndexId, constrOid, numfks,
mapped_pkattnum, fkattnum,
pfeqoperators, ppeqoperators, ffeqoperators,
+ numfkupdsetcols, fkupdsetcols,
+ numfkdelsetcols, fkdelsetcols,
old_check_ok);
/* Done -- clean up (but keep the lock) */
@@ -9587,6 +9670,14 @@ 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.
+ * numfkupdsetcols is the number of columns in the ON UPDATE SET NULL/DELETE
+ * (...) clause
+ * fkupdsetcols is the attnum array of the columns in the ON UPDATE SET
+ * NULL/DELETE clause
+ * 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.
@@ -9596,6 +9687,8 @@ 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 numfkupdsetcols, int16 *fkupdsetcols,
+ int numfkdelsetcols, int16 *fkdelsetcols,
bool old_check_ok, LOCKMODE lockmode)
{
AssertArg(OidIsValid(parentConstr));
@@ -9733,7 +9826,11 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
ffeqoperators,
numfks,
fkconstraint->fk_upd_action,
+ fkupdsetcols,
+ numfkupdsetcols,
fkconstraint->fk_del_action,
+ fkdelsetcols,
+ numfkdelsetcols,
fkconstraint->fk_matchtype,
NULL,
NULL,
@@ -9766,6 +9863,10 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkupdsetcols,
+ fkupdsetcols,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok,
lockmode);
@@ -9871,6 +9972,10 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
Oid conpfeqop[INDEX_MAX_KEYS];
Oid conppeqop[INDEX_MAX_KEYS];
Oid conffeqop[INDEX_MAX_KEYS];
+ int numfkupdsetcols;
+ AttrNumber confupdsetcols[INDEX_MAX_KEYS];
+ int numfkdelsetcols;
+ AttrNumber confdelsetcols[INDEX_MAX_KEYS];
Constraint *fkconstraint;
tuple = SearchSysCache1(CONSTROID, constrOid);
@@ -9903,7 +10008,11 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
confkey,
conpfeqop,
conppeqop,
- conffeqop);
+ conffeqop,
+ &numfkupdsetcols,
+ confupdsetcols,
+ &numfkdelsetcols,
+ confdelsetcols);
for (int i = 0; i < numfks; i++)
mapped_confkey[i] = attmap->attnums[confkey[i] - 1];
@@ -9950,6 +10059,10 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
conpfeqop,
conppeqop,
conffeqop,
+ numfkupdsetcols,
+ confupdsetcols,
+ numfkdelsetcols,
+ confdelsetcols,
true);
table_close(fkRel, NoLock);
@@ -10020,6 +10133,10 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
Oid conpfeqop[INDEX_MAX_KEYS];
Oid conppeqop[INDEX_MAX_KEYS];
Oid conffeqop[INDEX_MAX_KEYS];
+ int numfkupdsetcols;
+ AttrNumber confupdsetcols[INDEX_MAX_KEYS];
+ int numfkdelsetcols;
+ AttrNumber confdelsetcols[INDEX_MAX_KEYS];
Constraint *fkconstraint;
bool attached;
Oid indexOid;
@@ -10051,7 +10168,9 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
ShareRowExclusiveLock, NULL);
DeconstructFkConstraintRow(tuple, &numfks, conkey, confkey,
- conpfeqop, conppeqop, conffeqop);
+ conpfeqop, conppeqop, conffeqop,
+ &numfkupdsetcols, confupdsetcols,
+ &numfkdelsetcols, confdelsetcols);
for (int i = 0; i < numfks; i++)
mapped_conkey[i] = attmap->attnums[conkey[i] - 1];
@@ -10135,7 +10254,11 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
conffeqop,
numfks,
fkconstraint->fk_upd_action,
+ confupdsetcols,
+ numfkupdsetcols,
fkconstraint->fk_del_action,
+ confdelsetcols,
+ numfkdelsetcols,
fkconstraint->fk_matchtype,
NULL,
NULL,
@@ -10171,6 +10294,10 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
conpfeqop,
conppeqop,
conffeqop,
+ numfkupdsetcols,
+ confupdsetcols,
+ numfkdelsetcols,
+ confdelsetcols,
false, /* no old check exists */
AccessExclusiveLock);
table_close(pkrel, NoLock);
@@ -10792,7 +10919,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, if needed, type OID
*/
static int
transformColumnNameList(Oid relId, List *colList,
@@ -10819,7 +10946,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..6b17dc4ec8 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -828,7 +828,11 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
NULL,
0,
' ',
+ NULL,
+ 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..1f823bef7d 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3544,7 +3544,11 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
NULL,
0,
' ',
+ NULL,
+ 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 70e9e54d3e..9222f50254 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3079,7 +3079,9 @@ _copyConstraint(const Constraint *from)
COPY_NODE_FIELD(pk_attrs);
COPY_SCALAR_FIELD(fk_matchtype);
COPY_SCALAR_FIELD(fk_upd_action);
+ COPY_NODE_FIELD(fk_upd_set_cols);
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 19eff20102..4b3fded348 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2712,7 +2712,9 @@ _equalConstraint(const Constraint *a, const Constraint *b)
COMPARE_NODE_FIELD(pk_attrs);
COMPARE_SCALAR_FIELD(fk_matchtype);
COMPARE_SCALAR_FIELD(fk_upd_action);
+ COMPARE_NODE_FIELD(fk_upd_set_cols);
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 2e5ed77e18..693feb7d3d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2870,6 +2870,41 @@ _outPLAssignStmt(StringInfo str, const PLAssignStmt *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outAlterTableStmt(StringInfo str, const AlterTableStmt *node)
+{
+ WRITE_NODE_TYPE("ALTERTABLE");
+
+ WRITE_NODE_FIELD(relation);
+ WRITE_NODE_FIELD(cmds);
+ WRITE_ENUM_FIELD(objtype, ObjectType);
+ WRITE_BOOL_FIELD(missing_ok);
+}
+
+static void
+_outAlterTableCmd(StringInfo str, const AlterTableCmd *node)
+{
+ WRITE_NODE_TYPE("ALTERTABLE_CMD");
+
+ WRITE_ENUM_FIELD(subtype, AlterTableType);
+ WRITE_STRING_FIELD(name);
+ WRITE_INT_FIELD(num);
+ WRITE_NODE_FIELD(newowner);
+ WRITE_NODE_FIELD(def);
+ WRITE_ENUM_FIELD(behavior, DropBehavior);
+ WRITE_BOOL_FIELD(missing_ok);
+}
+
+static void
+_outRoleSpec(StringInfo str, const RoleSpec *node)
+{
+ WRITE_NODE_TYPE("ROLE_SPEC");
+
+ WRITE_ENUM_FIELD(roletype, RoleSpecType);
+ WRITE_STRING_FIELD(rolename);
+ WRITE_INT_FIELD(location);
+}
+
static void
_outFuncCall(StringInfo str, const FuncCall *node)
{
@@ -3730,7 +3765,9 @@ _outConstraint(StringInfo str, const Constraint *node)
WRITE_NODE_FIELD(pk_attrs);
WRITE_CHAR_FIELD(fk_matchtype);
WRITE_CHAR_FIELD(fk_upd_action);
+ WRITE_NODE_FIELD(fk_upd_set_cols);
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);
@@ -4371,6 +4408,15 @@ outNode(StringInfo str, const void *obj)
case T_PLAssignStmt:
_outPLAssignStmt(str, obj);
break;
+ case T_AlterTableStmt:
+ _outAlterTableStmt(str, obj);
+ break;
+ case T_AlterTableCmd:
+ _outAlterTableCmd(str, obj);
+ break;
+ case T_RoleSpec:
+ _outRoleSpec(str, obj);
+ break;
case T_ColumnDef:
_outColumnDef(str, obj);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 08f1bf1031..6ffe3038e6 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
@@ -259,6 +272,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
@@ -563,7 +578,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
@@ -3683,8 +3700,10 @@ 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_upd_set_cols = ($5)->updateAction->cols;
+ 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;
@@ -3894,8 +3913,10 @@ 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_upd_set_cols = ($10)->updateAction->cols;
+ 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,
@@ -3973,23 +3994,50 @@ 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 = (KeyActions *) palloc(sizeof(KeyActions));
+ n->updateAction = $1;
+ n->deleteAction = (KeyAction *) palloc(sizeof(KeyAction));
+ n->deleteAction->action = FKCONSTR_ACTION_NOACTION;
+ n->deleteAction->cols = NIL;
+ $$ = n;
+ }
| key_delete
- { $$ = (FKCONSTR_ACTION_NOACTION << 8) | ($1 & 0xFF); }
+ {
+ KeyActions *n = (KeyActions *) palloc(sizeof(KeyActions));
+ n->updateAction = (KeyAction *) 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 = (KeyActions *) palloc(sizeof(KeyActions));
+ n->updateAction = $1;
+ n->deleteAction = $2;
+ $$ = n;
+ }
| key_delete key_update
- { $$ = ($2 << 8) | ($1 & 0xFF); }
- | /*EMPTY*/
- { $$ = (FKCONSTR_ACTION_NOACTION << 8) | (FKCONSTR_ACTION_NOACTION & 0xFF); }
+ {
+ KeyActions *n = (KeyActions *) palloc(sizeof(KeyActions));
+ n->updateAction = $2;
+ n->deleteAction = $1;
+ $$ = n;
+ }
+ | /*EMPTY*/
+ {
+ KeyActions *n = (KeyActions *) palloc(sizeof(KeyActions));
+ n->updateAction = (KeyAction *) palloc(sizeof(KeyAction));
+ n->updateAction->action = FKCONSTR_ACTION_NOACTION;
+ n->updateAction->cols = NIL;
+ n->deleteAction = (KeyAction *) palloc(sizeof(KeyAction));
+ n->deleteAction->action = FKCONSTR_ACTION_NOACTION;
+ n->deleteAction->cols = NIL;
+ $$ = n;
+ }
;
key_update: ON UPDATE key_action { $$ = $3; }
@@ -3999,11 +4047,41 @@ 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 = (KeyAction *) palloc(sizeof(KeyAction));
+ n->action = FKCONSTR_ACTION_NOACTION;
+ n->cols = NIL;
+ $$ = n;
+ }
+ | RESTRICT
+ {
+ KeyAction *n = (KeyAction *) palloc(sizeof(KeyAction));
+ n->action = FKCONSTR_ACTION_RESTRICT;
+ n->cols = NIL;
+ $$ = n;
+ }
+ | CASCADE
+ {
+ KeyAction *n = (KeyAction *) palloc(sizeof(KeyAction));
+ n->action = FKCONSTR_ACTION_CASCADE;
+ n->cols = NIL;
+ $$ = n;
+ }
+ | SET NULL_P opt_column_list
+ {
+ KeyAction *n = (KeyAction *) palloc(sizeof(KeyAction));
+ n->action = FKCONSTR_ACTION_SETNULL;
+ n->cols = $3;
+ $$ = n;
+ }
+ | SET DEFAULT opt_column_list
+ {
+ KeyAction *n = (KeyAction *) 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..c2b19dae99 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -72,12 +72,15 @@
#define RI_PLAN_CHECK_LOOKUPPK 1
#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
+/* these queries are executed against the FK (referencing) table. */
+#define RI_PLAN_ONDELETE_CASCADE 3
+#define RI_PLAN_ONUPDATE_CASCADE 4
+/* the same plan can be used for both ON DELETE and ON UPDATE triggers. */
+#define RI_PLAN_ONTRIGGER_RESTRICT 5
+#define RI_PLAN_ONDELETE_SETNULL 6
+#define RI_PLAN_ONUPDATE_SETNULL 7
+#define RI_PLAN_ONDELETE_SETDEFAULT 8
+#define RI_PLAN_ONUPDATE_SETDEFAULT 9
#define MAX_QUOTED_NAME_LEN (NAMEDATALEN*2+3)
#define MAX_QUOTED_REL_NAME_LEN (MAX_QUOTED_NAME_LEN*2)
@@ -109,7 +112,11 @@ typedef struct RI_ConstraintInfo
Oid pk_relid; /* referenced relation */
Oid fk_relid; /* referencing relation */
char confupdtype; /* foreign key's ON UPDATE action */
+ int nupdsetcols; /* number of columns referenced in ON UPDATE SET clause */
+ int16 confupdsetcols[RI_MAX_NUMKEYS]; /* attnums of cols to set on update */
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 +187,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 +667,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_ONTRIGGER_RESTRICT);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
@@ -767,7 +774,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_ONDELETE_CASCADE);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
@@ -876,7 +883,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_ONUPDATE_CASCADE);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
@@ -970,7 +977,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 +992,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 +1007,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 +1022,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 +1032,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 +1040,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 +1059,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_ONUPDATE_SETNULL
+ : RI_PLAN_ONUPDATE_SETDEFAULT;
+ break;
+ case RI_TRIGTYPE_DELETE:
+ queryno = is_set_null
+ ? RI_PLAN_ONDELETE_SETNULL
+ : RI_PLAN_ONDELETE_SETDEFAULT;
+ 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 +1088,30 @@ 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->nupdsetcols;
+ set_cols = riinfo->confupdsetcols;
+ break;
+ case RI_TRIGTYPE_DELETE:
+ num_cols_to_set = riinfo->ndelsetcols;
+ set_cols = riinfo->confdelsetcols;
+ break;
+ default:
+ elog(ERROR, "invalid tgkind passed to ri_set");
+ }
+
+ /*
+ * If confupdsetcols or confdelsetcols is non-empty, then we only
+ * update the columns specified in that array.
+ */
+ if (num_cols_to_set == 0) {
+ num_cols_to_set = riinfo->nkeys;
+ set_cols = riinfo->fk_attnums;
+ }
/* ----------
* The query string built is
@@ -1080,39 +1122,46 @@ 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++)
{
- Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
- Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
- Oid pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
- Oid fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
+ Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
+ Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
+ Oid pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
+ Oid fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
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 +2147,11 @@ ri_LoadConstraintInfo(Oid constraintOid)
riinfo->pk_attnums,
riinfo->pf_eq_oprs,
riinfo->pp_eq_oprs,
- riinfo->ff_eq_oprs);
+ riinfo->ff_eq_oprs,
+ &riinfo->nupdsetcols,
+ riinfo->confupdsetcols,
+ &riinfo->ndelsetcols,
+ riinfo->confdelsetcols);
ReleaseSysCache(tup);
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 1bb25738a5..12ef534964 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2235,6 +2235,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
appendStringInfoString(&buf, string);
/* Add ON UPDATE and ON DELETE clauses, if needed */
+
+ val = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_confupdsetcols, &isnull);
+ if (isnull)
+ elog(ERROR, "null confupdsetcols for foreign key constraint %u",
+ constraintId);
+
switch (conForm->confupdtype)
{
case FKCONSTR_ACTION_NOACTION:
@@ -2261,6 +2268,20 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
if (string)
appendStringInfo(&buf, " ON UPDATE %s", string);
+ /* Add columns specified to SET NULL or SET DEFAULT if provided. */
+ if (ARR_NDIM(DatumGetArrayTypeP(val)) > 0)
+ {
+ appendStringInfo(&buf, " (");
+ decompile_column_index_array(val, conForm->conrelid, &buf);
+ appendStringInfo(&buf, ")");
+ }
+
+ val = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_confdelsetcols, &isnull);
+ if (isnull)
+ elog(ERROR, "null confdelsetcols for foreign key constraint %u",
+ constraintId);
+
switch (conForm->confdeltype)
{
case FKCONSTR_ACTION_NOACTION:
@@ -2287,6 +2308,14 @@ 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. */
+ if (ARR_NDIM(DatumGetArrayTypeP(val)) > 0)
+ {
+ 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 b54c911766..09519969fc 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4619,7 +4619,7 @@ RelationGetFKeyList(Relation relation)
info->conkey,
info->confkey,
info->conpfeqop,
- NULL, NULL);
+ NULL, NULL, NULL, NULL, NULL, NULL);
/* Add FK's node to the result list */
result = lappend(result, info);
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index e75baa8e1e..e2d782c653 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -138,6 +138,18 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
*/
Oid conffeqop[1] BKI_LOOKUP(pg_operator);
+ /*
+ * If a foreign key with a ON UPDATE SET NULL/DEFAULT action, the subset
+ * of conkey to updated. If empty, all columns should be updated.
+ */
+ Oid confupdsetcols[1];
+
+ /*
+ * If a foreign key with a ON DELETE SET NULL/DEFAULT action, the subset
+ * of conkey to updated. If empty, all columns should be updated.
+ */
+ Oid confdelsetcols[1];
+
/*
* If an exclusion constraint, the OIDs of the exclusion operators for
* each column of the constraint
@@ -219,7 +231,11 @@ extern Oid CreateConstraintEntry(const char *constraintName,
const Oid *ffEqOp,
int foreignNKeys,
char foreignUpdateType,
+ const int16 *fkUpdateSetCols,
+ int numFkUpdateSetCols,
char foreignDeleteType,
+ const int16 *fkDeleteSetCols,
+ int numFkDeleteSetCols,
char foreignMatchType,
const Oid *exclOp,
Node *conExpr,
@@ -254,7 +270,9 @@ 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_upd_set_cols, AttrNumber *fk_upd_set_cols,
+ 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 3138877553..90d6e43b0d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2299,7 +2299,10 @@ typedef struct Constraint
List *pk_attrs; /* Corresponding attrs in PK table */
char fk_matchtype; /* FULL, PARTIAL, SIMPLE */
char fk_upd_action; /* ON UPDATE action */
+ List *fk_upd_set_cols; /* ON UPDATE SET NULL/DEFAULT (col1, col2) */
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..a311388d0a 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -755,6 +755,56 @@ SELECT * from FKTABLE;
| | | 1
(7 rows)
+DROP TABLE FKTABLE;
+DROP TABLE PKTABLE;
+-- Test for ON UPDATE/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 UPDATE 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 (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 UPDATE SET NULL (foo));
+ERROR: column "foo" referenced in ON UPDATE SET action must be part of foreign key
+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 DEFAULT 0, id int,
+ fk_id_upd_set_null int,
+ fk_id_upd_set_default int DEFAULT 0,
+ fk_id_del_set_null int,
+ fk_id_del_set_default int,
+ FOREIGN KEY (tid, fk_id_upd_set_null) REFERENCES PKTABLE ON UPDATE SET NULL (fk_id_upd_set_null),
+ FOREIGN KEY (tid, fk_id_upd_set_default) REFERENCES PKTABLE ON UPDATE SET DEFAULT (fk_id_upd_set_default),
+ FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES PKTABLE ON DELETE SET NULL (tid),
+ FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (tid)
+);
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass::oid ORDER BY oid;
+ pg_get_constraintdef
+--------------------------------------------------------------------------------------------------------------------
+ FOREIGN KEY (tid, fk_id_upd_set_null) REFERENCES pktable(tid, id) ON UPDATE SET NULL (fk_id_upd_set_null)
+ FOREIGN KEY (tid, fk_id_upd_set_default) REFERENCES pktable(tid, id) ON UPDATE SET DEFAULT (fk_id_upd_set_default)
+ FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES pktable(tid, id) ON DELETE SET NULL (tid)
+ FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES pktable(tid, id) ON DELETE SET DEFAULT (tid)
+(4 rows)
+
+INSERT INTO PKTABLE VALUES (1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (0, 4);
+INSERT INTO FKTABLE VALUES
+ (1, 1, 1, NULL, NULL, NULL),
+ (1, 2, NULL, 2, NULL, NULL),
+ (1, 3, NULL, NULL, 3, NULL),
+ (1, 4, NULL, NULL, NULL, 4);
+UPDATE PKTABLE SET id = 5 WHERE id = 1;
+UPDATE PKTABLE SET id = 6 WHERE id = 2;
+DELETE FROM PKTABLE WHERE tid = 1 AND (id = 3 OR id = 4);
+SELECT * FROM FKTABLE ORDER BY id;
+ tid | id | fk_id_upd_set_null | fk_id_upd_set_default | fk_id_del_set_null | fk_id_del_set_default
+-----+----+--------------------+-----------------------+--------------------+-----------------------
+ 1 | 1 | | | |
+ 1 | 2 | | 0 | |
+ | 3 | | | 3 |
+ 0 | 4 | | | | 4
+(4 rows)
+
DROP TABLE FKTABLE;
DROP TABLE PKTABLE;
CREATE TABLE PKTABLE (ptest1 int PRIMARY KEY);
@@ -1734,6 +1784,44 @@ SELECT * FROM fk_partitioned_fk WHERE b = 142857;
2501 | 142857
(1 row)
+-- ON UPDATE/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) ON UPDATE SET NULL (b);
+BEGIN;
+INSERT INTO fk_partitioned_fk VALUES (1500, 2503);
+UPDATE fk_notpartitioned_pk SET b = 2504 WHERE a = 1500;
+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
+------+--------
+ 1500 |
+ 2502 |
+ | 142857
+(3 rows)
+
+ROLLBACK;
+-- ON UPDATE/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) ON UPDATE SET DEFAULT (b);
+BEGIN;
+DELETE FROM fk_partitioned_fk;
+DELETE FROM fk_notpartitioned_pk;
+INSERT INTO fk_notpartitioned_pk VALUES (500, 100000), (2501, 100000), (1500, 2503), (1500, 142857);
+INSERT INTO fk_partitioned_fk VALUES (500, 100000), (1500, 2503);
+UPDATE fk_notpartitioned_pk SET b = 2504 WHERE b = 2503;
+DELETE FROM fk_notpartitioned_pk WHERE a = 500;
+SELECT * FROM fk_partitioned_fk ORDER BY a;
+ a | b
+------+--------
+ 1500 | 142857
+ 2501 | 100000
+(2 rows)
+
+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..5b3fed72e3 100644
--- a/src/test/regress/sql/foreign_key.sql
+++ b/src/test/regress/sql/foreign_key.sql
@@ -463,6 +463,42 @@ SELECT * from FKTABLE;
DROP TABLE FKTABLE;
DROP TABLE PKTABLE;
+-- Test for ON UPDATE/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 UPDATE SET NULL (bar));
+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 UPDATE SET NULL (foo));
+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 DEFAULT 0, id int,
+ fk_id_upd_set_null int,
+ fk_id_upd_set_default int DEFAULT 0,
+ fk_id_del_set_null int,
+ fk_id_del_set_default int,
+ FOREIGN KEY (tid, fk_id_upd_set_null) REFERENCES PKTABLE ON UPDATE SET NULL (fk_id_upd_set_null),
+ FOREIGN KEY (tid, fk_id_upd_set_default) REFERENCES PKTABLE ON UPDATE SET DEFAULT (fk_id_upd_set_default),
+ FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES PKTABLE ON DELETE SET NULL (tid),
+ FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (tid)
+);
+
+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), (1, 3), (1, 4), (0, 4);
+INSERT INTO FKTABLE VALUES
+ (1, 1, 1, NULL, NULL, NULL),
+ (1, 2, NULL, 2, NULL, NULL),
+ (1, 3, NULL, NULL, 3, NULL),
+ (1, 4, NULL, NULL, NULL, 4);
+
+UPDATE PKTABLE SET id = 5 WHERE id = 1;
+UPDATE PKTABLE SET id = 6 WHERE id = 2;
+DELETE FROM PKTABLE WHERE tid = 1 AND (id = 3 OR id = 4);
+
+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 +1320,33 @@ 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 UPDATE/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) ON UPDATE SET NULL (b);
+BEGIN;
+INSERT INTO fk_partitioned_fk VALUES (1500, 2503);
+UPDATE fk_notpartitioned_pk SET b = 2504 WHERE a = 1500;
+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 UPDATE/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) ON UPDATE SET DEFAULT (b);
+BEGIN;
+DELETE FROM fk_partitioned_fk;
+DELETE FROM fk_notpartitioned_pk;
+INSERT INTO fk_notpartitioned_pk VALUES (500, 100000), (2501, 100000), (1500, 2503), (1500, 142857);
+INSERT INTO fk_partitioned_fk VALUES (500, 100000), (1500, 2503);
+UPDATE fk_notpartitioned_pk SET b = 2504 WHERE b = 2503;
+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)