[PATCH] Partial foreign key updates in referential integrity triggers
Hey, hackers!
I've created a patch to better support referential integrity constraints when
using composite primary and foreign keys. This patch allows creating a foreign
key using the syntax:
FOREIGN KEY (tenant_id, fk_id) REFERENCES fktable ON DELETE SET NULL (fk_id)
which means that only the fk_id column will be set to NULL when the referenced
row is deleted, rather than both the tenant_id and fk_id columns.
In multi-tenant applications, it is common to denormalize a "tenant_id" column
across every table, and use composite primary keys of the form (tenant_id, id)
and composite foreign keys of the form (tenant_id, fk_id), reusing the
tenant_id column in both the primary and foreign key.
This is often done initially for performance reasons, but has the added benefit
of making it impossible for data from one tenant to reference data from another
tenant, also making this a good decision from a security perspective.
Unfortunately, one downside of using composite foreign keys in such a matter
is that commonly used referential actions, such as ON DELETE SET NULL, no
longer work, because Postgres tries to set all of the referencing columns to
NULL, including the columns that overlap with the primary key:
CREATE TABLE tenants (id serial PRIMARY KEY);
CREATE TABLE users (
tenant_id int REFERENCES tenants ON DELETE CASCADE,
id serial,
PRIMARY KEY (tenant_id, id),
);
CREATE TABLE posts (
tenant_id int REFERENCES tenants ON DELETE CASCADE,
id serial,
author_id int,
PRIMARY KEY (tenant_id, id),
FOREIGN KEY (tenant_id, author_id) REFERENCES users ON DELETE SET NULL
);
INSERT INTO tenants VALUES (1);
INSERT INTO users VALUES (1, 101);
INSERT INTO posts VALUES (1, 201, 101);
DELETE FROM users WHERE id = 101;
ERROR: null value in column "tenant_id" violates not-null constraint
DETAIL: Failing row contains (null, 201, null).
This can be resolved by manually creating triggers on the referenced table, but
this is clunky and adds a significant amount of extra work when adding (or
removing!) foreign keys. Users shouldn't have to compromise on maintenance
overhead when using composite foreign keys.
I have implemented a simple extension to the syntax for foreign keys that
makes it just as easy to support referential integrity constraints for
composite foreign keys as it is for single column foreign keys. The SET NULL
and SET DEFAULT referential actions can now be optionally followed by a column
list:
key_action:
| NO ACTION
| RESTRICT
| CASCADE
| SET NULL [ (column_name [, ...] ) ]
| SET DEFAULT [ (column_name [, ...] ) ]
When a column list is provided, only the specified columns, which must be a
subset of the referencing columns, will be updated in the associated trigger.
Note that use of SET NULL (col1, ...) on a composite foreign key with MATCH
FULL specified will still raise an error. In such a scenario we could raise an
error when the user tries to define the foreign key, but we don't raise a
similar error when a user tries to use SET NULL on a non-nullable column, so I
don't think this is critical. (I haven't added this check in my patch.) While
this feature would mostly be used with the default MATCH SIMPLE, I could
imagine using SET DEFAULT (col, ...) even for MATCH FULL foreign keys.
To store this additional data, I've added two columns to the pg_constraint
catalog entry:
confupdsetcols int2[]
confdelsetcols int2[]
These columns store the attribute numbers of the columns to update in the
ON UPDATE and ON DELETE triggers respectively. If the arrays are empty, then
all of the referencing columns should be updated.
I previously proposed this feature about a year ago [1]/messages/by-id/CAF+2_SGRXQOtumethpuXhsyU+4AYzfKA5fhHCjCjH+jQ04WWjA@mail.gmail.com, but I don't feel like
the arguments against it were very strong. Wanting to get more familiar with the
Postgres codebase I decided to implement the feature over this holiday break,
and I've gotten everything working and put together a complete patch including
tests and updates to documentation. Hopefully if people find it useful it can
make its way into the next commitfest!
Visual diff:
https://github.com/postgres/postgres/compare/master...PaulJuliusMartinez:on-upd-del-set-cols
Here's a rough outline of the changes:
src/backend/parser/gram.y | 122
src/include/nodes/parsenodes.h | 3
src/backend/nodes/copyfuncs.c | 2
src/backend/nodes/equalfuncs.c | 2
src/backend/nodes/outfuncs.c | 47
- Modify grammar to add opt_column_list after SET NULL and SET DEFAULT
- Add fk_{upd,del}_set_cols fields to Constraint struct
- Add proper node support, as well as outfuncs support for AlterTableStmt,
which I used while debugging
src/include/catalog/pg_constraint.h | 20
src/backend/catalog/pg_constraint.c | 80
src/include/catalog/catversion.h | 2
- Add confupdsetcols and confdelsetcols to pg_constraint catalog entry
src/backend/commands/tablecmds.c | 142
- Pass along data from parsed Constraint node to CreateConstraintEntry
- Handle propagating constraints for partitioned tables
src/backend/utils/adt/ri_triggers.c | 109
- Update construction of trigger query to only update specified columns
- Update caching mechanism since ON UPDATE and ON DELETE may modify different
sets of columns
src/backend/utils/adt/ruleutils.c | 29
- Update pg_get_constraintdef to handle new syntax
- This automatically updates psql \d output as well as pg_dump
src/backend/catalog/heap.c | 4
src/backend/catalog/index.c | 4
src/backend/commands/trigger.c | 4
src/backend/commands/typecmds.c | 4
src/backend/utils/cache/relcache.c | 2
- Update misc. call sites for updated functions
src/test/regress/sql/foreign_key.sql | 63
src/test/regress/expected/foreign_key.out | 88
- Regression tests with checks for error cases and partitioned tables
doc/src/sgml/catalogs.sgml | 24
doc/src/sgml/ref/create_table.sgml | 15
- Updated documentation for CREATE TABLE and pg_constraint catalog definition
20 files changed, 700 insertions(+), 66 deletions(-)
Thanks,
Paul
[1]: /messages/by-id/CAF+2_SGRXQOtumethpuXhsyU+4AYzfKA5fhHCjCjH+jQ04WWjA@mail.gmail.com
Attachments:
referential-actions-set-cols-v1.patchapplication/octet-stream; name=referential-actions-set-cols-v1.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3a2266526c..648d0b38ad 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2675,6 +2675,30 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>confupdsetcols</structfield> <type>int2[]</type>
+ (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+ </para>
+ <para>
+ If a foreign key with a <literal>SET NULL</literal> or <literal>SET
+ DEFAULT</literal> update action, which columns should be updated.
+ If empty, all of the referencing columns should be updated.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>confdelsetcols</structfield> <type>int2[]</type>
+ (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+ </para>
+ <para>
+ If a foreign key with a <literal>SET NULL</literal> or <literal>SET
+ DEFAULT</literal> delete action, which columns should be updated.
+ If empty, all of the referencing columns should be updated.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>conexclop</structfield> <type>oid[]</type>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 569f4c9da7..fbb943449e 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -105,6 +105,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
<phrase><replaceable class="parameter">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
{ <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+
+<phrase><replaceable class="parameter">referential_action</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
+
+{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] | SET DEFAULT [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] }
+
</synopsis>
</refsynopsisdiv>
@@ -1128,19 +1133,21 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</varlistentry>
<varlistentry>
- <term><literal>SET NULL</literal></term>
+ <term><literal>SET NULL [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
<listitem>
<para>
- Set the referencing column(s) to null.
+ Set all of the referencing columns, or a specified subset of the
+ referencing columns, to null.
</para>
</listitem>
</varlistentry>
<varlistentry>
- <term><literal>SET DEFAULT</literal></term>
+ <term><literal>SET DEFAULT [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
<listitem>
<para>
- 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.)
</para>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 21f2240ade..ee7e05ec00 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2456,7 +2456,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 cffbc0ac38..a359a2bbc0 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2051,7 +2051,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 0081558c48..634c7c3061 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 993da56d43..17dac5e028 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -449,11 +449,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);
@@ -8384,9 +8392,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;
@@ -8482,11 +8494,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
@@ -8761,6 +8789,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkupdsetcols,
+ fkupdsetcols,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok);
/* Now handle the referencing side. */
@@ -8773,6 +8805,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkupdsetcols,
+ fkupdsetcols,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok,
lockmode);
@@ -8784,6 +8820,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
@@ -8805,6 +8871,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 delATE SET NULL/DELETE
+ * (...) clause
+ * fkdelsetcols is the attnum array of the columns in the ON delATE 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).
@@ -8814,7 +8888,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;
@@ -8888,7 +8965,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 */
@@ -8970,6 +9051,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) */
@@ -9010,6 +9093,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 delATE SET NULL/DELETE
+ * (...) clause
+ * fkdelsetcols is the attnum array of the columns in the ON delATE 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.
@@ -9019,6 +9110,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));
@@ -9156,7 +9249,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,
@@ -9189,6 +9286,10 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkupdsetcols,
+ fkupdsetcols,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok,
lockmode);
@@ -9294,6 +9395,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);
@@ -9326,7 +9431,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];
@@ -9373,6 +9482,12 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
conpfeqop,
conppeqop,
conffeqop,
+ // TODO (paulmtz): Extract fk_{upd,del}_set_col values
+ // and use them here.
+ 0,
+ NULL,
+ 0,
+ NULL,
true);
table_close(fkRel, NoLock);
@@ -9443,6 +9558,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;
@@ -9474,7 +9593,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];
@@ -9558,7 +9679,11 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
conffeqop,
numfks,
fkconstraint->fk_upd_action,
+ confupdsetcols,
+ numfkupdsetcols,
fkconstraint->fk_del_action,
+ confdelsetcols,
+ numfkupdsetcols,
fkconstraint->fk_matchtype,
NULL,
NULL,
@@ -9594,6 +9719,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);
@@ -10099,7 +10228,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,
@@ -10126,7 +10255,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 12229364f1..d8bc1954c2 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -813,7 +813,11 @@ CreateTrigger(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 76218fb47e..ad27464e00 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3555,7 +3555,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 ba3ccc712c..3f63e49e89 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2979,7 +2979,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 a2ef853dc2..779b798dce 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2643,7 +2643,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 8392be6d44..9f68f51bfd 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2787,6 +2787,42 @@ _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_NODE_FIELD(object);
+ 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)
{
@@ -3604,7 +3640,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);
@@ -4226,6 +4264,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 31c95443a5..be53b8d401 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -134,6 +134,19 @@ typedef struct SelectLimit
LimitOption limitOption;
} SelectLimit;
+/* 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
@@ -250,6 +263,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
struct SelectLimit *selectlimit;
+ struct KeyActions *keyactions;
+ struct KeyAction *keyaction;
}
%type <node> stmt schema_stmt
@@ -548,7 +563,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> TableLikeOptionList TableLikeOption
%type <list> ColQualList
%type <node> ColConstraint ColConstraintElem ConstraintAttr
-%type <ival> key_actions key_delete key_match key_update key_action
+%type <ival> key_match
+%type <keyaction> key_delete key_update key_action
+%type <keyactions> key_actions
%type <ival> ConstraintAttributeSpec ConstraintAttributeElem
%type <str> ExistingIndex
@@ -3622,8 +3639,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;
@@ -3832,8 +3851,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,
@@ -3911,23 +3932,50 @@ ExclusionWhereClause:
| /*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; }
@@ -3937,11 +3985,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 6e3a41062f..ac8ce4e498 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -76,8 +76,11 @@
#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_ONUPDATE_SETNULL_DOUPDATE 6
+#define RI_PLAN_ONDELETE_SETNULL_DOUPDATE 7
+#define RI_PLAN_ONUPDATE_SETDEFAULT_DOUPDATE 8
+#define RI_PLAN_ONDELETE_SETDEFAULT_DOUPDATE 9
#define MAX_QUOTED_NAME_LEN (NAMEDATALEN*2+3)
#define MAX_QUOTED_REL_NAME_LEN (MAX_QUOTED_NAME_LEN*2)
@@ -106,7 +109,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 refereced 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 refereced 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 */
@@ -177,7 +184,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,
@@ -962,7 +969,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);
}
/*
@@ -977,7 +984,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);
}
/*
@@ -992,7 +999,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);
}
/*
@@ -1007,7 +1014,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);
}
/*
@@ -1017,7 +1024,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;
@@ -1025,6 +1032,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);
@@ -1043,18 +1051,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_DOUPDATE
+ : RI_PLAN_ONUPDATE_SETDEFAULT_DOUPDATE;
+ break;
+ case RI_TRIGTYPE_DELETE:
+ queryno = is_set_null
+ ? RI_PLAN_ONDELETE_SETNULL_DOUPDATE
+ : RI_PLAN_ONDELETE_SETDEFAULT_DOUPDATE;
+ 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];
@@ -1062,6 +1080,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
@@ -1072,39 +1114,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,
@@ -2066,7 +2115,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 db803b4388..c9ff625cb3 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2059,6 +2059,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:
@@ -2085,6 +2092,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:
@@ -2111,6 +2132,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 afc3451a54..219a1d76ce 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4456,7 +4456,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/catversion.h b/src/include/catalog/catversion.h
index a6e39b3139..2b8b7d5fb0 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202012293
+#define CATALOG_VERSION_NO 202101011
#endif
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index f3c3df390f..51651a1364 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -133,6 +133,18 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
*/
Oid conffeqop[1];
+ /*
+ * 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
@@ -215,7 +227,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,
@@ -250,7 +266,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 dc2bb40926..a37dc28f1a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2204,7 +2204,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 07bd5b6434..3966e35999 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 c5c9011afc..1c3eeaf8db 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)
On 1/5/21 4:40 PM, Paul Martinez wrote:
I've created a patch to better support referential integrity constraints when
using composite primary and foreign keys. This patch allows creating a foreign
key using the syntax:
<snip>
I previously proposed this feature about a year ago [1], but I don't feel like
the arguments against it were very strong.
Perhaps not, but Tom certainly didn't seem in favor of this feature, at
the least.
Since the patch has not attracted any review/comment I propose to move
it to the next CF since it doesn't seem a likely candidate for PG14.
I'll do that on March 23 unless there are arguments to the contrary.
Regards,
--
-David
david@pgmasters.net
On 3/18/21 9:52 AM, David Steele wrote:
On 1/5/21 4:40 PM, Paul Martinez wrote:
I've created a patch to better support referential integrity
constraints when
using composite primary and foreign keys. This patch allows creating a
foreign
key using the syntax:<snip>
I previously proposed this feature about a year ago [1], but I don't
feel like
the arguments against it were very strong.Perhaps not, but Tom certainly didn't seem in favor of this feature, at
the least.Since the patch has not attracted any review/comment I propose to move
it to the next CF since it doesn't seem a likely candidate for PG14.I'll do that on March 23 unless there are arguments to the contrary.
This entry has been moved to the 2021-07 CF with status Needs Review.
Regards,
--
-David
david@pgmasters.net
On 05.01.21 22:40, Paul Martinez wrote:
I've created a patch to better support referential integrity constraints when
using composite primary and foreign keys. This patch allows creating a foreign
key using the syntax:FOREIGN KEY (tenant_id, fk_id) REFERENCES fktable ON DELETE SET NULL (fk_id)
which means that only the fk_id column will be set to NULL when the referenced
row is deleted, rather than both the tenant_id and fk_id columns.
I think this is an interesting feature with a legitimate use case.
I'm wondering a bit about what the ON UPDATE side of this is supposed to
mean. Consider your example:
CREATE TABLE tenants (id serial PRIMARY KEY);
CREATE TABLE users (
tenant_id int REFERENCES tenants ON DELETE CASCADE,
id serial,
PRIMARY KEY (tenant_id, id),
);
CREATE TABLE posts (
tenant_id int REFERENCES tenants ON DELETE CASCADE,
id serial,
author_id int,
PRIMARY KEY (tenant_id, id),
FOREIGN KEY (tenant_id, author_id) REFERENCES users ON DELETE SET NULL
);INSERT INTO tenants VALUES (1);
INSERT INTO users VALUES (1, 101);
INSERT INTO posts VALUES (1, 201, 101);
DELETE FROM users WHERE id = 101;
ERROR: null value in column "tenant_id" violates not-null constraint
DETAIL: Failing row contains (null, 201, null).
Consider what should happen when you update users.id. Per SQL standard,
for MATCH SIMPLE an ON UPDATE SET NULL should only set to null the
referencing column that corresponds to the referenced column actually
updated, not all of them. PostgreSQL doesn't do this, but if it did,
then this would work just fine.
Your feature requires specifying a fixed column or columns to update, so
it cannot react differently to what column actually updated. In fact,
you might want different referential actions depending on what columns
are updated, like what you can do with general triggers.
So, unless I'm missing an angle here, I would suggest leaving out the ON
UPDATE variant of this feature.
On Wed, Jul 14, 2021 at 6:51 PM Peter Eisentraut <
peter.eisentraut@enterprisedb.com> wrote:
On 05.01.21 22:40, Paul Martinez wrote:
I've created a patch to better support referential integrity constraints
when
using composite primary and foreign keys. This patch allows creating a
foreign
key using the syntax:
FOREIGN KEY (tenant_id, fk_id) REFERENCES fktable ON DELETE SET NULL
(fk_id)
which means that only the fk_id column will be set to NULL when the
referenced
row is deleted, rather than both the tenant_id and fk_id columns.
I think this is an interesting feature with a legitimate use case.
I'm wondering a bit about what the ON UPDATE side of this is supposed to
mean. Consider your example:CREATE TABLE tenants (id serial PRIMARY KEY);
CREATE TABLE users (
tenant_id int REFERENCES tenants ON DELETE CASCADE,
id serial,
PRIMARY KEY (tenant_id, id),
);
CREATE TABLE posts (
tenant_id int REFERENCES tenants ON DELETE CASCADE,
id serial,
author_id int,
PRIMARY KEY (tenant_id, id),
FOREIGN KEY (tenant_id, author_id) REFERENCES users ON DELETE SETNULL
);
INSERT INTO tenants VALUES (1);
INSERT INTO users VALUES (1, 101);
INSERT INTO posts VALUES (1, 201, 101);
DELETE FROM users WHERE id = 101;
ERROR: null value in column "tenant_id" violates not-null constraint
DETAIL: Failing row contains (null, 201, null).Consider what should happen when you update users.id. Per SQL standard,
for MATCH SIMPLE an ON UPDATE SET NULL should only set to null the
referencing column that corresponds to the referenced column actually
updated, not all of them. PostgreSQL doesn't do this, but if it did,
then this would work just fine.Your feature requires specifying a fixed column or columns to update, so
it cannot react differently to what column actually updated. In fact,
you might want different referential actions depending on what columns
are updated, like what you can do with general triggers.So, unless I'm missing an angle here, I would suggest leaving out the ON
UPDATE variant of this feature.
Patch does not apply on head, I am marking the status "Waiting on author"
http://cfbot.cputube.org/patch_33_2932.log
--
Ibrar Ahmed
On Wed, Jul 14, 2021 at 6:51 AM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:
I think this is an interesting feature with a legitimate use case.
Great! I'm glad to hear that.
Consider what should happen when you update users.id. Per SQL standard,
for MATCH SIMPLE an ON UPDATE SET NULL should only set to null the
referencing column that corresponds to the referenced column actually
updated, not all of them. PostgreSQL doesn't do this, but if it did,
then this would work just fine....
So, unless I'm missing an angle here, I would suggest leaving out the ON
UPDATE variant of this feature.
I understand what you're saying here, but are you sure that is the
behavior specified in the SQL standard?
I can't find a copy of a more recent standard, but at least in SQL 1999 [1]http://web.cecs.pdx.edu/~len/sql1999.pdf,
it has this to say (page 459 of the linked PDF, page 433 of the standard):
8) If a non-null value of a referenced column in the referenced table is
updated to a value that is distinct from the current value of that
column, then for every member F of the subtable family of the
referencing table:Case:
a) If SIMPLE is specified or implicit, or if FULL is specified, thenCase:
ii) If the <update rule> specifies SET NULL, thenCase:
1) If SIMPLE is specified or implicit, then:A) <snipped stuff about triggers>
B) For every F, in every matching row in F, each referencing
column in F that corresponds with a referenced column is set to
the null value.
This phrasing doesn't seem to make any reference to which columns were
actually updated.
The definitions a few pages earlier do explicitly define the set of
updated columns ("Let UMC be the set of referencing columns that
correspond with updated referenced columns"), but that defined set is
only referenced in the behavior when PARTIAL is specified, implying that
the set of updated columns is _not_ relevant in the SIMPLE case.
If my understanding of this is correct, then Postgres _isn't_ out of spec,
and this extension still works fine for the ON UPDATE triggers. (But, wow,
this spec is not easy to read, so I could definitely be wrong.)
I'm not sure how MATCH PARTIAL fits into this; I know it's
unimplemented, but I don't know what its use cases are, and I don't
think I understand how ON DELETE / ON UPDATE should work with MATCH
PARTIAL, let alone how they would work combined with this extension.
This lack of clarity may be a good-enough reason to leave out the ON
UPDATE case.
On Wed, Jul 14, 2021 at 12:36 PM Ibrar Ahmed <ibrar.ahmad@gmail.com> wrote:
Patch does not apply on head, I am marking the status "Waiting on author"
http://cfbot.cputube.org/patch_33_2932.log
I've rebased my original patch and it should work now. This is
referential-actions-set-cols-v2.patch.
I've also created a second patch that leaves out the ON UPDATE behavior, if
we think that's the safer route. This is
referential-actions-on-delete-only-set-cols-v1.patch.
[1]: http://web.cecs.pdx.edu/~len/sql1999.pdf
Show quoted text
On Wed, Jul 14, 2021 at 12:36 PM Ibrar Ahmed <ibrar.ahmad@gmail.com> wrote:
On Wed, Jul 14, 2021 at 6:51 PM Peter Eisentraut <peter.eisentraut@enterprisedb.com> wrote:
On 05.01.21 22:40, Paul Martinez wrote:
I've created a patch to better support referential integrity constraints when
using composite primary and foreign keys. This patch allows creating a foreign
key using the syntax:FOREIGN KEY (tenant_id, fk_id) REFERENCES fktable ON DELETE SET NULL (fk_id)
which means that only the fk_id column will be set to NULL when the referenced
row is deleted, rather than both the tenant_id and fk_id columns.I think this is an interesting feature with a legitimate use case.
I'm wondering a bit about what the ON UPDATE side of this is supposed to
mean. Consider your example:CREATE TABLE tenants (id serial PRIMARY KEY);
CREATE TABLE users (
tenant_id int REFERENCES tenants ON DELETE CASCADE,
id serial,
PRIMARY KEY (tenant_id, id),
);
CREATE TABLE posts (
tenant_id int REFERENCES tenants ON DELETE CASCADE,
id serial,
author_id int,
PRIMARY KEY (tenant_id, id),
FOREIGN KEY (tenant_id, author_id) REFERENCES users ON DELETE SET NULL
);INSERT INTO tenants VALUES (1);
INSERT INTO users VALUES (1, 101);
INSERT INTO posts VALUES (1, 201, 101);
DELETE FROM users WHERE id = 101;
ERROR: null value in column "tenant_id" violates not-null constraint
DETAIL: Failing row contains (null, 201, null).Consider what should happen when you update users.id. Per SQL standard,
for MATCH SIMPLE an ON UPDATE SET NULL should only set to null the
referencing column that corresponds to the referenced column actually
updated, not all of them. PostgreSQL doesn't do this, but if it did,
then this would work just fine.Your feature requires specifying a fixed column or columns to update, so
it cannot react differently to what column actually updated. In fact,
you might want different referential actions depending on what columns
are updated, like what you can do with general triggers.So, unless I'm missing an angle here, I would suggest leaving out the ON
UPDATE variant of this feature.Patch does not apply on head, I am marking the status "Waiting on author"
http://cfbot.cputube.org/patch_33_2932.log--
Ibrar Ahmed
Attachments:
referential-actions-on-delete-only-set-cols-v1.patchapplication/octet-stream; name=referential-actions-on-delete-only-set-cols-v1.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..e66b54257e 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2703,6 +2703,18 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>confdelsetcols</structfield> <type>int2[]</type>
+ (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+ </para>
+ <para>
+ If a foreign key with a <literal>SET NULL</literal> or <literal>SET
+ DEFAULT</literal> delete action, which columns should be updated.
+ If empty, all of the referencing columns should be updated.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>conexclop</structfield> <type>oid[]</type>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 15aed2f251..ca6ffe48bb 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -105,6 +105,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
<phrase><replaceable class="parameter">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
{ <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+
+<phrase><replaceable class="parameter">referential_action</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
+
+{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] | SET DEFAULT [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] }
+
</synopsis>
</refsynopsisdiv>
@@ -1165,19 +1170,23 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</varlistentry>
<varlistentry>
- <term><literal>SET NULL</literal></term>
+ <term><literal>SET NULL [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
<listitem>
<para>
- 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 <literal>ON DELETE</literal> triggers.
</para>
</listitem>
</varlistentry>
<varlistentry>
- <term><literal>SET DEFAULT</literal></term>
+ <term><literal>SET DEFAULT [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
<listitem>
<para>
- 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 <literal>ON DELETE</literal> triggers.
(There must be a row in the referenced table matching the default
values, if they are not null, or the operation will fail.)
</para>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 83746d3fd9..fd2d8d3bce 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2481,6 +2481,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 26bfa74ce7..51e72a851e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1959,6 +1959,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..00af9930f4 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,10 @@ CreateConstraintEntry(const char *constraintName,
fkdatums[i] = ObjectIdGetDatum(ffEqOp[i]);
conffeqopArray = construct_array(fkdatums, foreignNKeys,
OIDOID, sizeof(Oid), true, TYPALIGN_INT);
+ for (i = 0; i < numFkDeleteSetCols; i++)
+ fkdatums[i] = Int16GetDatum(fkDeleteSetCols[i]);
+ confdelsetcolsArray = construct_array(fkdatums, numFkDeleteSetCols,
+ INT2OID, 2, true, TYPALIGN_SHORT);
}
else
{
@@ -143,6 +150,7 @@ CreateConstraintEntry(const char *constraintName,
conpfeqopArray = NULL;
conppeqopArray = NULL;
conffeqopArray = NULL;
+ confdelsetcolsArray = NULL;
}
if (exclOp != NULL)
@@ -211,6 +219,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 +1170,14 @@ 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_del_set_cols, AttrNumber *fk_del_set_cols)
{
Oid constrId;
Datum adatum;
@@ -1260,6 +1274,29 @@ DeconstructFkConstraintRow(HeapTuple tuple, int *numfks,
pfree(arr); /* free de-toasted copy, if any */
}
+ 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 fcd778c62a..3b77ac215e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -483,11 +483,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, int16 *fkattnums,
+ int numfksetcols, 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);
@@ -8939,9 +8944,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;
@@ -9037,11 +9044,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
@@ -9316,6 +9331,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok);
/* Now handle the referencing side. */
@@ -9328,6 +9345,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok,
lockmode);
@@ -9339,6 +9358,35 @@ 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, int16 *fkattnums,
+ int numfksetcols, int16 *fksetcolsattnums,
+ List *fksetcols)
+{
+ 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 DELETE SET action must be part of foreign key", col)));
+ }
+ }
+}
+
/*
* addFkRecurseReferenced
* subroutine for ATAddForeignKeyConstraint; recurses on the referenced
@@ -9360,6 +9408,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).
@@ -9369,7 +9421,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;
@@ -9444,6 +9498,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 */
@@ -9525,6 +9581,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) */
@@ -9565,6 +9622,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.
@@ -9574,6 +9635,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));
@@ -9712,6 +9774,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
numfks,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
+ fkdelsetcols,
+ numfkdelsetcols,
fkconstraint->fk_matchtype,
NULL,
NULL,
@@ -9744,6 +9808,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok,
lockmode);
@@ -9849,6 +9915,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);
@@ -9881,7 +9949,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];
@@ -9928,6 +9998,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
conpfeqop,
conppeqop,
conffeqop,
+ numfkdelsetcols,
+ confdelsetcols,
true);
table_close(fkRel, NoLock);
@@ -9998,6 +10070,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;
@@ -10029,7 +10103,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];
@@ -10114,6 +10189,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
numfks,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
+ confdelsetcols,
+ numfkdelsetcols,
fkconstraint->fk_matchtype,
NULL,
NULL,
@@ -10149,6 +10226,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
conpfeqop,
conppeqop,
conffeqop,
+ numfkdelsetcols,
+ confdelsetcols,
false, /* no old check exists */
AccessExclusiveLock);
table_close(pkrel, NoLock);
@@ -10770,7 +10849,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,
@@ -10797,7 +10876,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 93eeff950b..1b38197711 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 29020c908e..c4124686a0 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 8a1762000c..702f4a70f9 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2692,6 +2692,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 87561cbb6f..31eeee8a42 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)
{
@@ -3731,6 +3766,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);
@@ -4370,6 +4406,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 39a2849eba..6a6927d862 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
@@ -260,6 +273,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 <node> stmt toplevel_stmt schema_stmt routine_body_stmt
@@ -564,7 +579,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> column_compression opt_column_compression
%type <list> ColQualList
%type <node> ColConstraint ColConstraintElem ConstraintAttr
-%type <ival> key_actions key_delete key_match key_update key_action
+%type <ival> key_match
+%type <keyaction> key_delete key_update key_action
+%type <keyactions> key_actions
%type <ival> ConstraintAttributeSpec ConstraintAttributeElem
%type <str> ExistingIndex
@@ -3684,8 +3701,14 @@ 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;
+ if (($5)->updateAction->cols != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SET NULL/DEFAULT <column_list> only supported for ON DELETE triggers"),
+ parser_errposition(@5)));
+ 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;
@@ -3895,8 +3918,14 @@ 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;
+ if (($10)->updateAction->cols != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SET NULL/DEFAULT <column_list> only supported for ON DELETE triggers"),
+ parser_errposition(@10)));
+ 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,
@@ -3974,23 +4003,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; }
@@ -4000,11 +4056,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..5e615c58bb 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -76,8 +76,11 @@
#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_ONUPDATE_SETNULL_DOUPDATE 6
+#define RI_PLAN_ONDELETE_SETNULL_DOUPDATE 7
+#define RI_PLAN_ONUPDATE_SETDEFAULT_DOUPDATE 8
+#define RI_PLAN_ONDELETE_SETDEFAULT_DOUPDATE 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,
@@ -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_ONUPDATE_SETNULL_DOUPDATE
+ : RI_PLAN_ONUPDATE_SETDEFAULT_DOUPDATE;
+ break;
+ case RI_TRIGTYPE_DELETE:
+ queryno = is_set_null
+ ? RI_PLAN_ONDELETE_SETNULL_DOUPDATE
+ : RI_PLAN_ONDELETE_SETDEFAULT_DOUPDATE;
+ 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 is non-empty, 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,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,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 4df8cc5abf..1ba16ded4d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2233,6 +2233,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
appendStringInfoString(&buf, string);
/* Add ON UPDATE and ON DELETE clauses, if needed */
+
switch (conForm->confupdtype)
{
case FKCONSTR_ACTION_NOACTION:
@@ -2259,6 +2260,12 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
if (string)
appendStringInfo(&buf, " ON UPDATE %s", string);
+ 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:
@@ -2285,6 +2292,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 13d9994af3..b0881b54b4 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4516,7 +4516,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 876583adaa..832ba7b332 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202108031
+#define CATALOG_VERSION_NO 202108040
#endif
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index e75baa8e1e..4e256d4d89 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 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
@@ -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 e28248af32..7979590fa1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2288,6 +2288,8 @@ 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..a2680dbc9d 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: SET NULL/DEFAULT <column_list> only supported for ON DELETE triggers
+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)
referential-actions-set-cols-v2.patchapplication/octet-stream; name=referential-actions-set-cols-v2.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..f3e8645cf1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2703,6 +2703,30 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>confupdsetcols</structfield> <type>int2[]</type>
+ (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+ </para>
+ <para>
+ If a foreign key with a <literal>SET NULL</literal> or <literal>SET
+ DEFAULT</literal> update action, which columns should be updated.
+ If empty, all of the referencing columns should be updated.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>confdelsetcols</structfield> <type>int2[]</type>
+ (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+ </para>
+ <para>
+ If a foreign key with a <literal>SET NULL</literal> or <literal>SET
+ DEFAULT</literal> delete action, which columns should be updated.
+ If empty, all of the referencing columns should be updated.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>conexclop</structfield> <type>oid[]</type>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 15aed2f251..8d61ddc5b3 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -105,6 +105,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
<phrase><replaceable class="parameter">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
{ <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+
+<phrase><replaceable class="parameter">referential_action</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
+
+{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] | SET DEFAULT [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] }
+
</synopsis>
</refsynopsisdiv>
@@ -1165,19 +1170,21 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</varlistentry>
<varlistentry>
- <term><literal>SET NULL</literal></term>
+ <term><literal>SET NULL [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
<listitem>
<para>
- Set the referencing column(s) to null.
+ Set all of the referencing columns, or a specified subset of the
+ referencing columns, to null.
</para>
</listitem>
</varlistentry>
<varlistentry>
- <term><literal>SET DEFAULT</literal></term>
+ <term><literal>SET DEFAULT [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
<listitem>
<para>
- 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.)
</para>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 83746d3fd9..bb5413a62b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2480,7 +2480,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 fcd778c62a..115428a637 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -483,11 +483,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);
@@ -8939,9 +8947,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;
@@ -9037,11 +9049,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
@@ -9316,6 +9344,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkupdsetcols,
+ fkupdsetcols,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok);
/* Now handle the referencing side. */
@@ -9328,6 +9360,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkupdsetcols,
+ fkupdsetcols,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok,
lockmode);
@@ -9339,6 +9375,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
@@ -9360,6 +9426,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).
@@ -9369,7 +9443,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;
@@ -9443,7 +9520,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 */
@@ -9525,6 +9606,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) */
@@ -9565,6 +9648,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.
@@ -9574,6 +9665,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));
@@ -9711,7 +9804,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,
@@ -9744,6 +9841,10 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkupdsetcols,
+ fkupdsetcols,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok,
lockmode);
@@ -9849,6 +9950,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);
@@ -9881,7 +9986,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];
@@ -9928,6 +10037,12 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
conpfeqop,
conppeqop,
conffeqop,
+ // TODO (paulmtz): Extract fk_{upd,del}_set_col values
+ // and use them here.
+ 0,
+ NULL,
+ 0,
+ NULL,
true);
table_close(fkRel, NoLock);
@@ -9998,6 +10113,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;
@@ -10029,7 +10148,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];
@@ -10113,7 +10234,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,
@@ -10149,6 +10274,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);
@@ -10770,7 +10899,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,
@@ -10797,7 +10926,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 93eeff950b..8b35895c8a 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 29020c908e..4ddc110f7a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3082,7 +3082,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 8a1762000c..8ec602351a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2691,7 +2691,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 87561cbb6f..f694b586b5 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);
@@ -4370,6 +4407,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 39a2849eba..06f432040b 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
@@ -260,6 +273,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 <node> stmt toplevel_stmt schema_stmt routine_body_stmt
@@ -564,7 +579,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> column_compression opt_column_compression
%type <list> ColQualList
%type <node> ColConstraint ColConstraintElem ConstraintAttr
-%type <ival> key_actions key_delete key_match key_update key_action
+%type <ival> key_match
+%type <keyaction> key_delete key_update key_action
+%type <keyactions> key_actions
%type <ival> ConstraintAttributeSpec ConstraintAttributeElem
%type <str> ExistingIndex
@@ -3684,8 +3701,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;
@@ -3895,8 +3914,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,
@@ -3974,23 +3995,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; }
@@ -4000,11 +4048,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..d57a575565 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -76,8 +76,11 @@
#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_ONUPDATE_SETNULL_DOUPDATE 6
+#define RI_PLAN_ONDELETE_SETNULL_DOUPDATE 7
+#define RI_PLAN_ONUPDATE_SETDEFAULT_DOUPDATE 8
+#define RI_PLAN_ONDELETE_SETDEFAULT_DOUPDATE 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,
@@ -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_DOUPDATE
+ : RI_PLAN_ONUPDATE_SETDEFAULT_DOUPDATE;
+ break;
+ case RI_TRIGTYPE_DELETE:
+ queryno = is_set_null
+ ? RI_PLAN_ONDELETE_SETNULL_DOUPDATE
+ : RI_PLAN_ONDELETE_SETDEFAULT_DOUPDATE;
+ 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 4df8cc5abf..53337bebfd 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2233,6 +2233,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:
@@ -2259,6 +2266,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:
@@ -2285,6 +2306,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 13d9994af3..d9e76a7c0e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4516,7 +4516,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/catversion.h b/src/include/catalog/catversion.h
index 876583adaa..696873d39c 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202108031
+#define CATALOG_VERSION_NO 202108041
#endif
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 e28248af32..cb00ca1252 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2287,7 +2287,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)
On 4 Aug 2021, at 23:49, Paul Martinez <hellopfm@gmail.com> wrote:
I've rebased my original patch and it should work now.
This patch no longer applies, can you please submit a rebased version? It
currently fails on catversion.h, to keep that from happening repeatedly you can
IMO skip that from the patch submission.
--
Daniel Gustafsson https://vmware.com/
On Wed, Sep 1, 2021 at 4:11 AM Daniel Gustafsson <daniel@yesql.se> wrote:
This patch no longer applies, can you please submit a rebased version? It
currently fails on catversion.h, to keep that from happening repeatedly you can
IMO skip that from the patch submission.
Ah, understood. Will do that in the future. Attached are rebased patches, not
including catversion.h changes, for both the ON UPDATE/DELETE case, and the
ON DELETE only case.
- Paul
Attachments:
referential-actions-set-cols-v3.patchapplication/octet-stream; name=referential-actions-set-cols-v3.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..f3e8645cf1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2703,6 +2703,30 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>confupdsetcols</structfield> <type>int2[]</type>
+ (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+ </para>
+ <para>
+ If a foreign key with a <literal>SET NULL</literal> or <literal>SET
+ DEFAULT</literal> update action, which columns should be updated.
+ If empty, all of the referencing columns should be updated.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>confdelsetcols</structfield> <type>int2[]</type>
+ (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+ </para>
+ <para>
+ If a foreign key with a <literal>SET NULL</literal> or <literal>SET
+ DEFAULT</literal> delete action, which columns should be updated.
+ If empty, all of the referencing columns should be updated.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>conexclop</structfield> <type>oid[]</type>
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 <replaceable class="parameter">numeric_literal</replaceable>, REM
<phrase><replaceable class="parameter">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
{ <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+
+<phrase><replaceable class="parameter">referential_action</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
+
+{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] | SET DEFAULT [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] }
+
</synopsis>
</refsynopsisdiv>
@@ -1165,19 +1170,21 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</varlistentry>
<varlistentry>
- <term><literal>SET NULL</literal></term>
+ <term><literal>SET NULL [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
<listitem>
<para>
- Set the referencing column(s) to null.
+ Set all of the referencing columns, or a specified subset of the
+ referencing columns, to null.
</para>
</listitem>
</varlistentry>
<varlistentry>
- <term><literal>SET DEFAULT</literal></term>
+ <term><literal>SET DEFAULT [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
<listitem>
<para>
- 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.)
</para>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 83746d3fd9..bb5413a62b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2480,7 +2480,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 dbee6ae199..9adc3cac3f 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);
@@ -8927,9 +8935,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;
@@ -9025,11 +9037,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
@@ -9304,6 +9332,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkupdsetcols,
+ fkupdsetcols,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok);
/* Now handle the referencing side. */
@@ -9316,6 +9348,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkupdsetcols,
+ fkupdsetcols,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok,
lockmode);
@@ -9327,6 +9363,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
@@ -9348,6 +9414,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).
@@ -9357,7 +9431,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;
@@ -9431,7 +9508,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 */
@@ -9513,6 +9594,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) */
@@ -9553,6 +9636,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.
@@ -9562,6 +9653,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));
@@ -9699,7 +9792,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,
@@ -9732,6 +9829,10 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkupdsetcols,
+ fkupdsetcols,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok,
lockmode);
@@ -9837,6 +9938,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);
@@ -9869,7 +9974,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];
@@ -9916,6 +10025,10 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
conpfeqop,
conppeqop,
conffeqop,
+ numfkupdsetcols,
+ confupdsetcols,
+ numfkdelsetcols,
+ confdelsetcols,
true);
table_close(fkRel, NoLock);
@@ -9986,6 +10099,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;
@@ -10017,7 +10134,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];
@@ -10101,7 +10220,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,
@@ -10137,6 +10260,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);
@@ -10758,7 +10885,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,
@@ -10785,7 +10912,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 6bdb1a1660..a543adb441 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3545,7 +3545,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 38251c2b8e..74a3cfb352 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3070,7 +3070,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 8a1762000c..8ec602351a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2691,7 +2691,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 87561cbb6f..f694b586b5 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);
@@ -4370,6 +4407,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 39a2849eba..06f432040b 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
@@ -260,6 +273,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 <node> stmt toplevel_stmt schema_stmt routine_body_stmt
@@ -564,7 +579,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> column_compression opt_column_compression
%type <list> ColQualList
%type <node> ColConstraint ColConstraintElem ConstraintAttr
-%type <ival> key_actions key_delete key_match key_update key_action
+%type <ival> key_match
+%type <keyaction> key_delete key_update key_action
+%type <keyactions> key_actions
%type <ival> ConstraintAttributeSpec ConstraintAttributeElem
%type <str> ExistingIndex
@@ -3684,8 +3701,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;
@@ -3895,8 +3914,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,
@@ -3974,23 +3995,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; }
@@ -4000,11 +4048,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..d57a575565 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -76,8 +76,11 @@
#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_ONUPDATE_SETNULL_DOUPDATE 6
+#define RI_PLAN_ONDELETE_SETNULL_DOUPDATE 7
+#define RI_PLAN_ONUPDATE_SETDEFAULT_DOUPDATE 8
+#define RI_PLAN_ONDELETE_SETDEFAULT_DOUPDATE 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,
@@ -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_DOUPDATE
+ : RI_PLAN_ONUPDATE_SETDEFAULT_DOUPDATE;
+ break;
+ case RI_TRIGTYPE_DELETE:
+ queryno = is_set_null
+ ? RI_PLAN_ONDELETE_SETNULL_DOUPDATE
+ : RI_PLAN_ONDELETE_SETDEFAULT_DOUPDATE;
+ 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 8ff4e5dc07..eff27a20f4 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2233,6 +2233,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:
@@ -2259,6 +2266,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:
@@ -2285,6 +2306,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 13d9994af3..d9e76a7c0e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4516,7 +4516,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 7af13dee43..6ce5ba4ec9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2287,7 +2287,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)
referential-actions-on-delete-only-set-cols-v2.patchapplication/octet-stream; name=referential-actions-on-delete-only-set-cols-v2.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..e66b54257e 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2703,6 +2703,18 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>confdelsetcols</structfield> <type>int2[]</type>
+ (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+ </para>
+ <para>
+ If a foreign key with a <literal>SET NULL</literal> or <literal>SET
+ DEFAULT</literal> delete action, which columns should be updated.
+ If empty, all of the referencing columns should be updated.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>conexclop</structfield> <type>oid[]</type>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..2211fc71f4 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -105,6 +105,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
<phrase><replaceable class="parameter">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
{ <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+
+<phrase><replaceable class="parameter">referential_action</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
+
+{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] | SET DEFAULT [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] }
+
</synopsis>
</refsynopsisdiv>
@@ -1165,19 +1170,23 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</varlistentry>
<varlistentry>
- <term><literal>SET NULL</literal></term>
+ <term><literal>SET NULL [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
<listitem>
<para>
- 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 <literal>ON DELETE</literal> triggers.
</para>
</listitem>
</varlistentry>
<varlistentry>
- <term><literal>SET DEFAULT</literal></term>
+ <term><literal>SET DEFAULT [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
<listitem>
<para>
- 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 <literal>ON DELETE</literal> triggers.
(There must be a row in the referenced table matching the default
values, if they are not null, or the operation will fail.)
</para>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 83746d3fd9..fd2d8d3bce 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2481,6 +2481,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 26bfa74ce7..51e72a851e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1959,6 +1959,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..00af9930f4 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,10 @@ CreateConstraintEntry(const char *constraintName,
fkdatums[i] = ObjectIdGetDatum(ffEqOp[i]);
conffeqopArray = construct_array(fkdatums, foreignNKeys,
OIDOID, sizeof(Oid), true, TYPALIGN_INT);
+ for (i = 0; i < numFkDeleteSetCols; i++)
+ fkdatums[i] = Int16GetDatum(fkDeleteSetCols[i]);
+ confdelsetcolsArray = construct_array(fkdatums, numFkDeleteSetCols,
+ INT2OID, 2, true, TYPALIGN_SHORT);
}
else
{
@@ -143,6 +150,7 @@ CreateConstraintEntry(const char *constraintName,
conpfeqopArray = NULL;
conppeqopArray = NULL;
conffeqopArray = NULL;
+ confdelsetcolsArray = NULL;
}
if (exclOp != NULL)
@@ -211,6 +219,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 +1170,14 @@ 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_del_set_cols, AttrNumber *fk_del_set_cols)
{
Oid constrId;
Datum adatum;
@@ -1260,6 +1274,29 @@ DeconstructFkConstraintRow(HeapTuple tuple, int *numfks,
pfree(arr); /* free de-toasted copy, if any */
}
+ 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 dbee6ae199..3e5649432e 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, int16 *fkattnums,
+ int numfksetcols, 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);
@@ -8927,9 +8932,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;
@@ -9025,11 +9032,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
@@ -9304,6 +9319,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok);
/* Now handle the referencing side. */
@@ -9316,6 +9333,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok,
lockmode);
@@ -9327,6 +9346,35 @@ 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, int16 *fkattnums,
+ int numfksetcols, int16 *fksetcolsattnums,
+ List *fksetcols)
+{
+ 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 DELETE SET action must be part of foreign key", col)));
+ }
+ }
+}
+
/*
* addFkRecurseReferenced
* subroutine for ATAddForeignKeyConstraint; recurses on the referenced
@@ -9348,6 +9396,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).
@@ -9357,7 +9409,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;
@@ -9432,6 +9486,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 */
@@ -9513,6 +9569,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) */
@@ -9553,6 +9610,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.
@@ -9562,6 +9623,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));
@@ -9700,6 +9762,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
numfks,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
+ fkdelsetcols,
+ numfkdelsetcols,
fkconstraint->fk_matchtype,
NULL,
NULL,
@@ -9732,6 +9796,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok,
lockmode);
@@ -9837,6 +9903,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);
@@ -9869,7 +9937,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];
@@ -9916,6 +9986,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
conpfeqop,
conppeqop,
conffeqop,
+ numfkdelsetcols,
+ confdelsetcols,
true);
table_close(fkRel, NoLock);
@@ -9986,6 +10058,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;
@@ -10017,7 +10091,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];
@@ -10102,6 +10177,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
numfks,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
+ confdelsetcols,
+ numfkdelsetcols,
fkconstraint->fk_matchtype,
NULL,
NULL,
@@ -10137,6 +10214,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
conpfeqop,
conppeqop,
conffeqop,
+ numfkdelsetcols,
+ confdelsetcols,
false, /* no old check exists */
AccessExclusiveLock);
table_close(pkrel, NoLock);
@@ -10758,7 +10837,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,
@@ -10785,7 +10864,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 6bdb1a1660..dca4f29326 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3546,6 +3546,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 38251c2b8e..35eff0f70b 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3071,6 +3071,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 8a1762000c..702f4a70f9 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2692,6 +2692,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 87561cbb6f..31eeee8a42 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)
{
@@ -3731,6 +3766,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);
@@ -4370,6 +4406,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 39a2849eba..6a6927d862 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
@@ -260,6 +273,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 <node> stmt toplevel_stmt schema_stmt routine_body_stmt
@@ -564,7 +579,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> column_compression opt_column_compression
%type <list> ColQualList
%type <node> ColConstraint ColConstraintElem ConstraintAttr
-%type <ival> key_actions key_delete key_match key_update key_action
+%type <ival> key_match
+%type <keyaction> key_delete key_update key_action
+%type <keyactions> key_actions
%type <ival> ConstraintAttributeSpec ConstraintAttributeElem
%type <str> ExistingIndex
@@ -3684,8 +3701,14 @@ 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;
+ if (($5)->updateAction->cols != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SET NULL/DEFAULT <column_list> only supported for ON DELETE triggers"),
+ parser_errposition(@5)));
+ 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;
@@ -3895,8 +3918,14 @@ 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;
+ if (($10)->updateAction->cols != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SET NULL/DEFAULT <column_list> only supported for ON DELETE triggers"),
+ parser_errposition(@10)));
+ 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,
@@ -3974,23 +4003,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; }
@@ -4000,11 +4056,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..5e615c58bb 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -76,8 +76,11 @@
#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_ONUPDATE_SETNULL_DOUPDATE 6
+#define RI_PLAN_ONDELETE_SETNULL_DOUPDATE 7
+#define RI_PLAN_ONUPDATE_SETDEFAULT_DOUPDATE 8
+#define RI_PLAN_ONDELETE_SETDEFAULT_DOUPDATE 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,
@@ -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_ONUPDATE_SETNULL_DOUPDATE
+ : RI_PLAN_ONUPDATE_SETDEFAULT_DOUPDATE;
+ break;
+ case RI_TRIGTYPE_DELETE:
+ queryno = is_set_null
+ ? RI_PLAN_ONDELETE_SETNULL_DOUPDATE
+ : RI_PLAN_ONDELETE_SETDEFAULT_DOUPDATE;
+ 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 is non-empty, 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,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,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 8ff4e5dc07..44551f6990 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2233,6 +2233,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
appendStringInfoString(&buf, string);
/* Add ON UPDATE and ON DELETE clauses, if needed */
+
switch (conForm->confupdtype)
{
case FKCONSTR_ACTION_NOACTION:
@@ -2259,6 +2260,12 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
if (string)
appendStringInfo(&buf, " ON UPDATE %s", string);
+ 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:
@@ -2285,6 +2292,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 13d9994af3..b0881b54b4 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4516,7 +4516,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/pg_constraint.h b/src/include/catalog/pg_constraint.h
index e75baa8e1e..4e256d4d89 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 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
@@ -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 7af13dee43..e1ce2d7334 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2288,6 +2288,8 @@ 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..a2680dbc9d 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: SET NULL/DEFAULT <column_list> only supported for ON DELETE triggers
+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)
On Thu, Sep 2, 2021 at 12:11 PM Paul Martinez <hellopfm@gmail.com> wrote:
On Wed, Sep 1, 2021 at 4:11 AM Daniel Gustafsson <daniel@yesql.se> wrote:
This patch no longer applies, can you please submit a rebased version?
It
currently fails on catversion.h, to keep that from happening repeatedly
you can
IMO skip that from the patch submission.
Ah, understood. Will do that in the future. Attached are rebased patches,
not
including catversion.h changes, for both the ON UPDATE/DELETE case, and the
ON DELETE only case.- Paul
Hi,
+ case RI_TRIGTYPE_DELETE:
+ queryno = is_set_null
+ ? RI_PLAN_ONDELETE_SETNULL_DOUPDATE
+ : RI_PLAN_ONDELETE_SETDEFAULT_DOUPDATE;
Should the new symbols be renamed ?
RI_PLAN_ONDELETE_SETNULL_DOUPDATE -> RI_PLAN_ONDELETE_SETNULL_DODELETE
RI_PLAN_ONDELETE_SETDEFAULT_DOUPDATE -> RI_PLAN_ONDELETE_SETDEFAULT_DODELETE
Cheers
On Thu, Sep 2, 2021 at 1:55 PM Zhihong Yu <zyu@yugabyte.com> wrote:
Hi, + case RI_TRIGTYPE_DELETE: + queryno = is_set_null + ? RI_PLAN_ONDELETE_SETNULL_DOUPDATE + : RI_PLAN_ONDELETE_SETDEFAULT_DOUPDATE;Should the new symbols be renamed ?
RI_PLAN_ONDELETE_SETNULL_DOUPDATE -> RI_PLAN_ONDELETE_SETNULL_DODELETE
RI_PLAN_ONDELETE_SETDEFAULT_DOUPDATE -> RI_PLAN_ONDELETE_SETDEFAULT_DODELETE
These constants are named correctly -- they follow the format:
RI_PLAN_<trigger>_<action>_<what_saved_plan_does>
These symbols refer to plans that are used for ON DELETE SET NULL
and ON DELETE SET DEFAULT triggers, which update rows in the referencing
table ("_DOUPDATE"). These triggers do not perform any deletions.
But these names are definitely confusing, and I did have to spend some time
confirming that the names were correct. I decided to rename these, as well as
the other plan keys, so they all use the same more explicit format:
RI_PLAN_<trigger>_<action>
RI_PLAN_CASCADE_DEL_DODELETE => RI_PLAN_ONDELETE_CASCADE
RI_PLAN_CASCADE_UPD_DOUPDATE => RI_PLAN_ONUPDATE_CASCADE
RI_PLAN_RESTRICT_CHECKREF => RI_PLAN_ONTRIGGER_RESTRICT
RI_PLAN_SETNULL_DOUPDATE => RI_PLAN_ONDELETE_SETNULL
and RI_PLAN_ONUPDATE_SETNULL
RI_PLAN_SETDEFAULT_DOUPDATE => RI_PLAN_ONDELETE_SETDEFAULT
and RI_PLAN_ONUPDATE_SETDEFAULT
The same plan can be used for both ON DELETE RESTRICT and ON UPDATE RESTRICT,
so we just use ONTRIGGER there. Previously, the same plan could also be
used for both ON DELETE SET NULL and ON UPDATE SET NULL, or both
ON DELETE SET DEFAULT and ON UPDATE SET DEFAULT. This is no longer the case,
so we need to add separate keys for each case. As an example, a constraint on
a table foo could specify:
FOREIGN KEY (a, b) REFERENCES bar (a, b)
ON UPDATE SET NULL
ON DELETE SET NULL (a)
In this case for the update trigger we want to do:
UPDATE foo SET a = NULL, B = NULL WHERE ...
but for the delete trigger we want to do:
UPDATE foo SET a = NULL WHERE ...
so the plans cannot be shared.
(Note that we still need separate plans even if we only support specifying
a column subset for the ON DELETE trigger. As in the above example, the
ON UPDATE trigger will always set all the columns, while the ON DELETE trigger
could only set a subset.)
- Paul
Attachments:
referential-actions-set-cols-v4.patchapplication/octet-stream; name=referential-actions-set-cols-v4.patchDownload
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$<replaceable><iteration count></replaceable>:<replaceable>&l
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>confupdsetcols</structfield> <type>int2[]</type>
+ (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+ </para>
+ <para>
+ If a foreign key with a <literal>SET NULL</literal> or <literal>SET
+ DEFAULT</literal> update action, which columns should be updated.
+ If empty, all of the referencing columns should be updated.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>confdelsetcols</structfield> <type>int2[]</type>
+ (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+ </para>
+ <para>
+ If a foreign key with a <literal>SET NULL</literal> or <literal>SET
+ DEFAULT</literal> delete action, which columns should be updated.
+ If empty, all of the referencing columns should be updated.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>conexclop</structfield> <type>oid[]</type>
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 <replaceable class="parameter">numeric_literal</replaceable>, REM
<phrase><replaceable class="parameter">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
{ <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+
+<phrase><replaceable class="parameter">referential_action</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
+
+{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] | SET DEFAULT [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] }
+
</synopsis>
</refsynopsisdiv>
@@ -1165,19 +1170,21 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</varlistentry>
<varlistentry>
- <term><literal>SET NULL</literal></term>
+ <term><literal>SET NULL [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
<listitem>
<para>
- Set the referencing column(s) to null.
+ Set all of the referencing columns, or a specified subset of the
+ referencing columns, to null.
</para>
</listitem>
</varlistentry>
<varlistentry>
- <term><literal>SET DEFAULT</literal></term>
+ <term><literal>SET DEFAULT [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
<listitem>
<para>
- 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.)
</para>
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 <node> stmt toplevel_stmt schema_stmt routine_body_stmt
@@ -563,7 +578,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> column_compression opt_column_compression
%type <list> ColQualList
%type <node> ColConstraint ColConstraintElem ConstraintAttr
-%type <ival> key_actions key_delete key_match key_update key_action
+%type <ival> key_match
+%type <keyaction> key_delete key_update key_action
+%type <keyactions> key_actions
%type <ival> ConstraintAttributeSpec ConstraintAttributeElem
%type <str> 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)
referential-actions-on-delete-only-set-cols-v3.patchapplication/octet-stream; name=referential-actions-on-delete-only-set-cols-v3.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fd6910ddbe..32516d306e 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2703,6 +2703,18 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>confdelsetcols</structfield> <type>int2[]</type>
+ (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+ </para>
+ <para>
+ If a foreign key with a <literal>SET NULL</literal> or <literal>SET
+ DEFAULT</literal> delete action, which columns should be updated.
+ If empty, all of the referencing columns should be updated.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>conexclop</structfield> <type>oid[]</type>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..2211fc71f4 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -105,6 +105,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
<phrase><replaceable class="parameter">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
{ <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+
+<phrase><replaceable class="parameter">referential_action</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
+
+{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] | SET DEFAULT [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] }
+
</synopsis>
</refsynopsisdiv>
@@ -1165,19 +1170,23 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</varlistentry>
<varlistentry>
- <term><literal>SET NULL</literal></term>
+ <term><literal>SET NULL [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
<listitem>
<para>
- 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 <literal>ON DELETE</literal> triggers.
</para>
</listitem>
</varlistentry>
<varlistentry>
- <term><literal>SET DEFAULT</literal></term>
+ <term><literal>SET DEFAULT [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
<listitem>
<para>
- 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 <literal>ON DELETE</literal> triggers.
(There must be a row in the referenced table matching the default
values, if they are not null, or the operation will fail.)
</para>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 81cc39fb70..67983e916b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2484,6 +2484,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 26bfa74ce7..51e72a851e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1959,6 +1959,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..00af9930f4 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,10 @@ CreateConstraintEntry(const char *constraintName,
fkdatums[i] = ObjectIdGetDatum(ffEqOp[i]);
conffeqopArray = construct_array(fkdatums, foreignNKeys,
OIDOID, sizeof(Oid), true, TYPALIGN_INT);
+ for (i = 0; i < numFkDeleteSetCols; i++)
+ fkdatums[i] = Int16GetDatum(fkDeleteSetCols[i]);
+ confdelsetcolsArray = construct_array(fkdatums, numFkDeleteSetCols,
+ INT2OID, 2, true, TYPALIGN_SHORT);
}
else
{
@@ -143,6 +150,7 @@ CreateConstraintEntry(const char *constraintName,
conpfeqopArray = NULL;
conppeqopArray = NULL;
conffeqopArray = NULL;
+ confdelsetcolsArray = NULL;
}
if (exclOp != NULL)
@@ -211,6 +219,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 +1170,14 @@ 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_del_set_cols, AttrNumber *fk_del_set_cols)
{
Oid constrId;
Datum adatum;
@@ -1260,6 +1274,29 @@ DeconstructFkConstraintRow(HeapTuple tuple, int *numfks,
pfree(arr); /* free de-toasted copy, if any */
}
+ 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..bf5e0e9c2d 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, int16 *fkattnums,
+ int numfksetcols, 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);
@@ -8961,9 +8966,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;
@@ -9059,11 +9066,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
@@ -9338,6 +9353,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok);
/* Now handle the referencing side. */
@@ -9350,6 +9367,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok,
lockmode);
@@ -9361,6 +9380,35 @@ 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, int16 *fkattnums,
+ int numfksetcols, int16 *fksetcolsattnums,
+ List *fksetcols)
+{
+ 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 DELETE SET action must be part of foreign key", col)));
+ }
+ }
+}
+
/*
* addFkRecurseReferenced
* subroutine for ATAddForeignKeyConstraint; recurses on the referenced
@@ -9382,6 +9430,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).
@@ -9391,7 +9443,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;
@@ -9466,6 +9520,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 */
@@ -9547,6 +9603,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) */
@@ -9587,6 +9644,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.
@@ -9596,6 +9657,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));
@@ -9734,6 +9796,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
numfks,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
+ fkdelsetcols,
+ numfkdelsetcols,
fkconstraint->fk_matchtype,
NULL,
NULL,
@@ -9766,6 +9830,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok,
lockmode);
@@ -9871,6 +9937,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);
@@ -9903,7 +9971,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];
@@ -9950,6 +10020,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
conpfeqop,
conppeqop,
conffeqop,
+ numfkdelsetcols,
+ confdelsetcols,
true);
table_close(fkRel, NoLock);
@@ -10020,6 +10092,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;
@@ -10051,7 +10125,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];
@@ -10136,6 +10211,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
numfks,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
+ confdelsetcols,
+ numfkdelsetcols,
fkconstraint->fk_matchtype,
NULL,
NULL,
@@ -10171,6 +10248,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
conpfeqop,
conppeqop,
conffeqop,
+ numfkdelsetcols,
+ confdelsetcols,
false, /* no old check exists */
AccessExclusiveLock);
table_close(pkrel, NoLock);
@@ -10792,7 +10871,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 +10898,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 70e9e54d3e..5116386b1f 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3080,6 +3080,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 19eff20102..06f4e8ec42 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2713,6 +2713,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 2e5ed77e18..992cec437a 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)
{
@@ -3731,6 +3766,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);
@@ -4371,6 +4407,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..fd8c84b770 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 <node> stmt toplevel_stmt schema_stmt routine_body_stmt
@@ -563,7 +578,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> column_compression opt_column_compression
%type <list> ColQualList
%type <node> ColConstraint ColConstraintElem ConstraintAttr
-%type <ival> key_actions key_delete key_match key_update key_action
+%type <ival> key_match
+%type <keyaction> key_delete key_update key_action
+%type <keyactions> key_actions
%type <ival> ConstraintAttributeSpec ConstraintAttributeElem
%type <str> ExistingIndex
@@ -3683,8 +3700,14 @@ 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;
+ if (($5)->updateAction->cols != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SET NULL/DEFAULT <column_list> only supported for ON DELETE triggers"),
+ parser_errposition(@5)));
+ 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 +3917,14 @@ 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;
+ if (($10)->updateAction->cols != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SET NULL/DEFAULT <column_list> only supported for ON DELETE triggers"),
+ parser_errposition(@10)));
+ 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 +4002,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 +4055,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..a088622b8b 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)
@@ -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_ONTRIGGER_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_ONDELETE_CASCADE);
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_ONUPDATE_CASCADE);
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_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 +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 is non-empty, 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,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,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 1bb25738a5..d5d5320c10 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2235,6 +2235,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
appendStringInfoString(&buf, string);
/* Add ON UPDATE and ON DELETE clauses, if needed */
+
switch (conForm->confupdtype)
{
case FKCONSTR_ACTION_NOACTION:
@@ -2261,6 +2262,12 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
if (string)
appendStringInfo(&buf, " ON UPDATE %s", string);
+ 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 +2294,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..b5520acfb7 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);
/* 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..4e256d4d89 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 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
@@ -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 3138877553..7169f96a49 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2300,6 +2300,8 @@ 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..a2680dbc9d 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: SET NULL/DEFAULT <column_list> only supported for ON DELETE triggers
+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)
I have reviewed your patch
referential-actions-on-delete-only-set-cols-v3.patch. Attached are two
patches that go on top of yours that contain my recommended changes.
Basically, this all looks pretty good to me. My changes are mostly
stylistic.
Some notes of substance:
- The omission of the referential actions details from the CREATE TABLE
reference page surprised me. I have committed that separately (without
the column support, of course). So you should rebase your patch on top
of that. Note that ALTER TABLE would now also need to be updated by
your patch.
- I recommend setting pg_constraint.confdelsetcols to null for the
default behavior of setting all columns, instead of an empty array the
way you have done it. I have noted the places in the code that need to
be changed for that.
- The outfuncs.c support shouldn't be included in the final patch.
There is nothing wrong it, but I don't think it should be part of this
patch to add piecemeal support like that. I have included a few changes
there anyway for completeness.
- In gram.y, I moved the error check around to avoid duplication.
- In ri_triggers.c, I follow your renaming of the constants, but
RI_PLAN_ONTRIGGER_RESTRICT seems a little weird. Maybe do _ONBOTH_, or
else reverse the order, like RI_PLAN_SETNULL_ONDELETE, which would then
allow RI_PLAN_RESTRICT.
Please look through this and provide an updated patch, and then it
should be good to go.
Attachments:
0001-Fix-whitespace.patchtext/plain; charset=UTF-8; name=0001-Fix-whitespace.patchDownload
From cacfc7737bc62e28b8ada7bd6230034050e76946 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Thu, 11 Nov 2021 08:06:27 +0100
Subject: [PATCH 1/2] Fix whitespace
---
src/backend/commands/tablecmds.c | 4 +-
src/backend/parser/gram.y | 86 ++++++++++++++++----------------
2 files changed, 45 insertions(+), 45 deletions(-)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38b49694b3..3ef5d6d285 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9403,8 +9403,8 @@ void validateFkOnDeleteSetColumns(
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)));
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("column \"%s\" referenced in ON DELETE SET action must be part of foreign key", col)));
}
}
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b39c15cfeb..7ee0414e0b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4011,39 +4011,39 @@ OptWhereClause:
key_actions:
key_update
- {
+ {
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->updateAction = $1;
+ n->deleteAction = (KeyAction *) palloc(sizeof(KeyAction));
+ n->deleteAction->action = FKCONSTR_ACTION_NOACTION;
+ n->deleteAction->cols = NIL;
$$ = n;
- }
+ }
| key_delete
- {
+ {
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->updateAction = (KeyAction *) palloc(sizeof(KeyAction));
+ n->updateAction->action = FKCONSTR_ACTION_NOACTION;
+ n->updateAction->cols = NIL;
+ n->deleteAction = $1;
$$ = n;
- }
+ }
| key_update key_delete
- {
+ {
KeyActions *n = (KeyActions *) palloc(sizeof(KeyActions));
- n->updateAction = $1;
- n->deleteAction = $2;
+ n->updateAction = $1;
+ n->deleteAction = $2;
$$ = n;
- }
+ }
| key_delete key_update
- {
+ {
KeyActions *n = (KeyActions *) palloc(sizeof(KeyActions));
- n->updateAction = $2;
- n->deleteAction = $1;
+ n->updateAction = $2;
+ n->deleteAction = $1;
$$ = n;
- }
- | /*EMPTY*/
- {
+ }
+ | /*EMPTY*/
+ {
KeyActions *n = (KeyActions *) palloc(sizeof(KeyActions));
n->updateAction = (KeyAction *) palloc(sizeof(KeyAction));
n->updateAction->action = FKCONSTR_ACTION_NOACTION;
@@ -4052,7 +4052,7 @@ key_actions:
n->deleteAction->action = FKCONSTR_ACTION_NOACTION;
n->deleteAction->cols = NIL;
$$ = n;
- }
+ }
;
key_update: ON UPDATE key_action { $$ = $3; }
@@ -4065,38 +4065,38 @@ key_action:
NO ACTION
{
KeyAction *n = (KeyAction *) palloc(sizeof(KeyAction));
- n->action = FKCONSTR_ACTION_NOACTION;
- n->cols = NIL;
- $$ = n;
- }
+ 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;
- }
+ 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;
- }
+ 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;
- }
+ 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;
- }
+ n->action = FKCONSTR_ACTION_SETDEFAULT;
+ n->cols = $3;
+ $$ = n;
+ }
;
OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
--
2.33.1
0002-Review-changes.patchtext/plain; charset=UTF-8; name=0002-Review-changes.patchDownload
From 98a3fc16f49dea24d34d1af257c5017281bfe27a Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Thu, 11 Nov 2021 11:07:02 +0100
Subject: [PATCH 2/2] Review changes
---
doc/src/sgml/ref/create_table.sgml | 6 +--
src/backend/catalog/pg_constraint.c | 10 +++--
src/backend/commands/tablecmds.c | 29 +++++++-----
src/backend/nodes/outfuncs.c | 12 +++--
src/backend/parser/gram.y | 54 ++++++++++++-----------
src/backend/utils/adt/ri_triggers.c | 32 ++++++++------
src/backend/utils/adt/ruleutils.c | 13 +++---
src/include/catalog/pg_constraint.h | 2 +-
src/include/nodes/parsenodes.h | 1 -
src/test/regress/expected/foreign_key.out | 2 +-
10 files changed, 90 insertions(+), 71 deletions(-)
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index c6a9072ca0..eee837d86e 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -106,7 +106,7 @@
{ <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
-<phrase><replaceable class="parameter">referential_action</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
+<phrase><replaceable class="parameter">referential_action</replaceable> in a <literal>FOREIGN KEY</literal>/<literal>REFERENCES</literal> constraint is:</phrase>
{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] | SET DEFAULT [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] }
@@ -1175,7 +1175,7 @@ <title>Parameters</title>
<para>
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 <literal>ON DELETE</literal> triggers.
+ specified for <literal>ON DELETE</literal> actions.
</para>
</listitem>
</varlistentry>
@@ -1186,7 +1186,7 @@ <title>Parameters</title>
<para>
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 <literal>ON DELETE</literal> triggers.
+ can only be specified for <literal>ON DELETE</literal> actions.
(There must be a row in the referenced table matching the default
values, if they are not null, or the operation will fail.)
</para>
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 00af9930f4..35496984b9 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -139,6 +139,7 @@ CreateConstraintEntry(const char *constraintName,
fkdatums[i] = ObjectIdGetDatum(ffEqOp[i]);
conffeqopArray = construct_array(fkdatums, foreignNKeys,
OIDOID, sizeof(Oid), true, TYPALIGN_INT);
+ // FIXME: use null instead of empty array for standard behavior
for (i = 0; i < numFkDeleteSetCols; i++)
fkdatums[i] = Int16GetDatum(fkDeleteSetCols[i]);
confdelsetcolsArray = construct_array(fkdatums, numFkDeleteSetCols,
@@ -1170,8 +1171,9 @@ 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; fields other than
- * numfks, conkey and confkey 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,
@@ -1277,10 +1279,12 @@ DeconstructFkConstraintRow(HeapTuple tuple, int *numfks,
if (fk_del_set_cols)
{
int num_delete_cols = 0;
+
+ // FIXME: use null instead of empty array for standard behavior
adatum = SysCacheGetAttr(CONSTROID, tuple,
Anum_pg_constraint_confdelsetcols, &isNull);
if (isNull)
- elog(ERROR, "null confdelsetcols for foreign-key constraint %u", constrId);
+ elog(ERROR, "null confdelsetcols for constraint %u", constrId);
arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
if (ARR_NDIM(arr) != 0)
{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3ef5d6d285..d158fef975 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -484,8 +484,8 @@ static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstra
Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
int numfkdelsetcols, int16 *fkdelsetcols,
bool old_check_ok);
-static void validateFkOnDeleteSetColumns(int numfks, int16 *fkattnums,
- int numfksetcols, int16 *fksetcolsattnums,
+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,
@@ -9385,22 +9385,27 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
* Verifies that columns used in ON DELETE SET NULL/DEFAULT (...)
* column lists are valid.
*/
-void validateFkOnDeleteSetColumns(
- int numfks, int16 *fkattnums,
- int numfksetcols, int16 *fksetcolsattnums,
- List *fksetcols)
+void
+validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
+ int numfksetcols, const int16 *fksetcolsattnums,
+ List *fksetcols)
{
- for (int i = 0; i < numfksetcols; i++) {
- int setcol_attnum = fksetcolsattnums[i];
+ 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) {
+
+ for (int j = 0; j < numfks; j++)
+ {
+ if (fkattnums[j] == setcol_attnum)
+ {
seen = true;
break;
}
}
- if (!seen) {
+ if (!seen)
+ {
char *col = strVal(list_nth(fksetcols, i));
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
@@ -10871,7 +10876,7 @@ ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName,
/*
* transformColumnNameList - transform list of column names
*
- * Lookup each name and return its attnum and, if needed, type OID
+ * Lookup each name and return its attnum and, optionally, type OID
*/
static int
transformColumnNameList(Oid relId, List *colList,
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 7225434494..58bebbaa2d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2871,10 +2871,11 @@ _outPLAssignStmt(StringInfo str, const PLAssignStmt *node)
WRITE_LOCATION_FIELD(location);
}
+#if 0
static void
_outAlterTableStmt(StringInfo str, const AlterTableStmt *node)
{
- WRITE_NODE_TYPE("ALTERTABLE");
+ WRITE_NODE_TYPE("ALTERTABLESTMT");
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(cmds);
@@ -2885,7 +2886,7 @@ _outAlterTableStmt(StringInfo str, const AlterTableStmt *node)
static void
_outAlterTableCmd(StringInfo str, const AlterTableCmd *node)
{
- WRITE_NODE_TYPE("ALTERTABLE_CMD");
+ WRITE_NODE_TYPE("ALTERTABLECMD");
WRITE_ENUM_FIELD(subtype, AlterTableType);
WRITE_STRING_FIELD(name);
@@ -2899,12 +2900,13 @@ _outAlterTableCmd(StringInfo str, const AlterTableCmd *node)
static void
_outRoleSpec(StringInfo str, const RoleSpec *node)
{
- WRITE_NODE_TYPE("ROLE_SPEC");
+ WRITE_NODE_TYPE("ROLESPEC");
WRITE_ENUM_FIELD(roletype, RoleSpecType);
WRITE_STRING_FIELD(rolename);
- WRITE_INT_FIELD(location);
+ WRITE_LOCATION_FIELD(location);
}
+#endif
static void
_outFuncCall(StringInfo str, const FuncCall *node)
@@ -4408,6 +4410,7 @@ outNode(StringInfo str, const void *obj)
case T_PLAssignStmt:
_outPLAssignStmt(str, obj);
break;
+#if 0
case T_AlterTableStmt:
_outAlterTableStmt(str, obj);
break;
@@ -4417,6 +4420,7 @@ outNode(StringInfo str, const void *obj)
case T_RoleSpec:
_outRoleSpec(str, obj);
break;
+#endif
case T_ColumnDef:
_outColumnDef(str, obj);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7ee0414e0b..2a319eecda 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3708,11 +3708,6 @@ ColConstraintElem:
n->pk_attrs = $3;
n->fk_matchtype = $4;
n->fk_upd_action = ($5)->updateAction->action;
- if (($5)->updateAction->cols != NIL)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SET NULL/DEFAULT <column_list> only supported for ON DELETE triggers"),
- parser_errposition(@5)));
n->fk_del_action = ($5)->deleteAction->action;
n->fk_del_set_cols = ($5)->deleteAction->cols;
n->skip_validation = false;
@@ -3925,11 +3920,6 @@ ConstraintElem:
n->pk_attrs = $8;
n->fk_matchtype = $9;
n->fk_upd_action = ($10)->updateAction->action;
- if (($10)->updateAction->cols != NIL)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SET NULL/DEFAULT <column_list> only supported for ON DELETE triggers"),
- parser_errposition(@10)));
n->fk_del_action = ($10)->deleteAction->action;
n->fk_del_set_cols = ($10)->deleteAction->cols;
processCASbits($11, @11, "FOREIGN KEY",
@@ -4012,17 +4002,17 @@ OptWhereClause:
key_actions:
key_update
{
- KeyActions *n = (KeyActions *) palloc(sizeof(KeyActions));
+ KeyActions *n = palloc(sizeof(KeyActions));
n->updateAction = $1;
- n->deleteAction = (KeyAction *) palloc(sizeof(KeyAction));
+ n->deleteAction = palloc(sizeof(KeyAction));
n->deleteAction->action = FKCONSTR_ACTION_NOACTION;
n->deleteAction->cols = NIL;
$$ = n;
}
| key_delete
{
- KeyActions *n = (KeyActions *) palloc(sizeof(KeyActions));
- n->updateAction = (KeyAction *) palloc(sizeof(KeyAction));
+ KeyActions *n = palloc(sizeof(KeyActions));
+ n->updateAction = palloc(sizeof(KeyAction));
n->updateAction->action = FKCONSTR_ACTION_NOACTION;
n->updateAction->cols = NIL;
n->deleteAction = $1;
@@ -4030,69 +4020,81 @@ key_actions:
}
| key_update key_delete
{
- KeyActions *n = (KeyActions *) palloc(sizeof(KeyActions));
+ KeyActions *n = palloc(sizeof(KeyActions));
n->updateAction = $1;
n->deleteAction = $2;
$$ = n;
}
| key_delete key_update
{
- KeyActions *n = (KeyActions *) palloc(sizeof(KeyActions));
+ KeyActions *n = palloc(sizeof(KeyActions));
n->updateAction = $2;
n->deleteAction = $1;
$$ = n;
}
| /*EMPTY*/
{
- KeyActions *n = (KeyActions *) palloc(sizeof(KeyActions));
- n->updateAction = (KeyAction *) palloc(sizeof(KeyAction));
+ KeyActions *n = palloc(sizeof(KeyActions));
+ n->updateAction = palloc(sizeof(KeyAction));
n->updateAction->action = FKCONSTR_ACTION_NOACTION;
n->updateAction->cols = NIL;
- n->deleteAction = (KeyAction *) palloc(sizeof(KeyAction));
+ 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
{
- KeyAction *n = (KeyAction *) palloc(sizeof(KeyAction));
+ KeyAction *n = palloc(sizeof(KeyAction));
n->action = FKCONSTR_ACTION_NOACTION;
n->cols = NIL;
$$ = n;
}
| RESTRICT
{
- KeyAction *n = (KeyAction *) palloc(sizeof(KeyAction));
+ KeyAction *n = palloc(sizeof(KeyAction));
n->action = FKCONSTR_ACTION_RESTRICT;
n->cols = NIL;
$$ = n;
}
| CASCADE
{
- KeyAction *n = (KeyAction *) palloc(sizeof(KeyAction));
+ KeyAction *n = palloc(sizeof(KeyAction));
n->action = FKCONSTR_ACTION_CASCADE;
n->cols = NIL;
$$ = n;
}
| SET NULL_P opt_column_list
{
- KeyAction *n = (KeyAction *) palloc(sizeof(KeyAction));
+ KeyAction *n = palloc(sizeof(KeyAction));
n->action = FKCONSTR_ACTION_SETNULL;
n->cols = $3;
$$ = n;
}
| SET DEFAULT opt_column_list
{
- KeyAction *n = (KeyAction *) palloc(sizeof(KeyAction));
+ KeyAction *n = palloc(sizeof(KeyAction));
n->action = FKCONSTR_ACTION_SETDEFAULT;
n->cols = $3;
$$ = n;
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index a088622b8b..dc53ce6cad 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -72,15 +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_ONDELETE_CASCADE 3
-#define RI_PLAN_ONUPDATE_CASCADE 4
+/* 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 RI_PLAN_ONTRIGGER_RESTRICT 5 // XXX confusing name
+#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)
@@ -1128,7 +1128,9 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
appendStringInfo(&querybuf, "UPDATE %s%s SET",
fk_only, fkrelname);
- // Add assignment clauses
+ /*
+ * Add assignment clauses
+ */
querysep = "";
for (int i = 0; i < num_cols_to_set; i++)
{
@@ -1140,14 +1142,16 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
querysep = ",";
}
- // Add WHERE clause
+ /*
+ * 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]));
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d5d5320c10..062a5b7051 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2262,12 +2262,6 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
if (string)
appendStringInfo(&buf, " ON UPDATE %s", string);
- 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:
@@ -2295,6 +2289,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
appendStringInfo(&buf, " ON DELETE %s", string);
/* Add columns specified to SET NULL or SET DEFAULT if provided. */
+ // FIXME: use null instead of empty array for standard behavior
+ val = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_confdelsetcols, &isnull);
+ if (isnull)
+ elog(ERROR, "null confdelsetcols for constraint %u",
+ constraintId);
+
if (ARR_NDIM(DatumGetArrayTypeP(val)) > 0)
{
appendStringInfo(&buf, " (");
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 4e256d4d89..dd0d975468 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -142,7 +142,7 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
* If a foreign key with an ON DELETE SET NULL/DEFAULT action, the subset
* of conkey to updated. If empty, all columns should be updated.
*/
- Oid confdelsetcols[1];
+ int16 confdelsetcols[1];
/*
* If an exclusion constraint, the OIDs of the exclusion operators for
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b682e9abb1..4c5a8a39bf 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2302,7 +2302,6 @@ typedef struct Constraint
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 a2680dbc9d..4c5274983d 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -764,7 +764,7 @@ 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: SET NULL/DEFAULT <column_list> only supported for ON DELETE triggers
+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 (
--
2.33.1
On Thu, Nov 11, 2021 at 4:34 AM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:
I have reviewed your patch
referential-actions-on-delete-only-set-cols-v3.patch. Attached are two
patches that go on top of yours that contain my recommended changes.Basically, this all looks pretty good to me. My changes are mostly
stylistic.
Thank you! I really, really appreciate the thorough review and the
comments and corrections.
Some notes of substance:
- The omission of the referential actions details from the CREATE TABLE
reference page surprised me. I have committed that separately (without
the column support, of course). So you should rebase your patch on top
of that. Note that ALTER TABLE would now also need to be updated by
your patch.
Done.
- I recommend setting pg_constraint.confdelsetcols to null for the
default behavior of setting all columns, instead of an empty array the
way you have done it. I have noted the places in the code that need to
be changed for that.
Done.
- The outfuncs.c support shouldn't be included in the final patch.
There is nothing wrong it, but I don't think it should be part of this
patch to add piecemeal support like that. I have included a few changes
there anyway for completeness.
Got it. I've reverted the changes in that file.
- In ri_triggers.c, I follow your renaming of the constants, but
RI_PLAN_ONTRIGGER_RESTRICT seems a little weird. Maybe do _ONBOTH_, or
else reverse the order, like RI_PLAN_SETNULL_ONDELETE, which would then
allow RI_PLAN_RESTRICT.
I've reversed the order, so it's now RI_PLAN_<action>_<trigger>, and
renamed the RESTRICT one to just RI_PLAN_RESTRICT.
I've attached an updated patch, including your changes and the additional
changes mentioned above.
- Paul
Attachments:
referential-actions-on-delete-only-set-cols-v4.patchapplication/octet-stream; name=referential-actions-on-delete-only-set-cols-v4.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c1d11be73f..db1297a226 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2708,6 +2708,18 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>confdelsetcols</structfield> <type>int2[]</type>
+ (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+ </para>
+ <para>
+ If a foreign key with a <literal>SET NULL</literal> or <literal>SET
+ DEFAULT</literal> delete action, which columns should be updated.
+ If null, all of the referencing columns should be updated.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>conexclop</structfield> <type>oid[]</type>
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 <replaceable class="parameter">numeric_literal</replaceable>, REM
<phrase><replaceable class="parameter">referential_action</replaceable> in a <literal>FOREIGN KEY</literal>/<literal>REFERENCES</literal> constraint is:</phrase>
-{ NO ACTION | RESTRICT | CASCADE | SET NULL | SET DEFAULT }
+{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] | SET DEFAULT [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] }
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 57d51a676a..0a526c9253 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -108,7 +108,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
<phrase><replaceable class="parameter">referential_action</replaceable> in a <literal>FOREIGN KEY</literal>/<literal>REFERENCES</literal> constraint is:</phrase>
-{ NO ACTION | RESTRICT | CASCADE | SET NULL | SET DEFAULT }
+{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] | SET DEFAULT [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] }
</synopsis>
</refsynopsisdiv>
@@ -1169,19 +1169,23 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</varlistentry>
<varlistentry>
- <term><literal>SET NULL</literal></term>
+ <term><literal>SET NULL [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
<listitem>
<para>
- 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 <literal>ON DELETE</literal> actions.
</para>
</listitem>
</varlistentry>
<varlistentry>
- <term><literal>SET DEFAULT</literal></term>
+ <term><literal>SET DEFAULT [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
<listitem>
<para>
- 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 <literal>ON DELETE</literal> actions.
(There must be a row in the referenced table matching the default
values, if they are not null, or the operation will fail.)
</para>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 81cc39fb70..67983e916b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2484,6 +2484,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 c255806e38..95a0739ef4 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..0ede73ad94 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)
+ {
+ int num_delete_cols = 0;
+
+ adatum = SysCacheGetAttr(CONSTROID, tuple,
+ Anum_pg_constraint_confdelsetcols, &isNull);
+ if (isNull)
+ {
+ *num_fk_del_set_cols = 0;
+ }
+ else
+ {
+ 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 785b282e69..7118e93a91 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);
@@ -8961,9 +8966,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;
@@ -9059,11 +9066,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
@@ -9338,6 +9353,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok);
/* Now handle the referencing side. */
@@ -9350,6 +9367,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok,
lockmode);
@@ -9361,6 +9380,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
@@ -9382,6 +9435,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).
@@ -9391,7 +9448,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;
@@ -9466,6 +9525,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 */
@@ -9547,6 +9608,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) */
@@ -9587,6 +9649,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.
@@ -9596,6 +9662,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));
@@ -9734,6 +9801,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
numfks,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
+ fkdelsetcols,
+ numfkdelsetcols,
fkconstraint->fk_matchtype,
NULL,
NULL,
@@ -9766,6 +9835,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok,
lockmode);
@@ -9871,6 +9942,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);
@@ -9903,7 +9976,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];
@@ -9950,6 +10025,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
conpfeqop,
conppeqop,
conffeqop,
+ numfkdelsetcols,
+ confdelsetcols,
true);
table_close(fkRel, NoLock);
@@ -10020,6 +10097,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;
@@ -10051,7 +10130,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];
@@ -10136,6 +10216,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
numfks,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
+ confdelsetcols,
+ numfkdelsetcols,
fkconstraint->fk_matchtype,
NULL,
NULL,
@@ -10171,6 +10253,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
conpfeqop,
conppeqop,
conffeqop,
+ numfkdelsetcols,
+ confdelsetcols,
false, /* no old check exists */
AccessExclusiveLock);
table_close(pkrel, NoLock);
@@ -10792,7 +10876,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,
@@ -10819,7 +10903,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 ad1ea2ff2f..f3bf7f2d57 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3081,6 +3081,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/parser/gram.y b/src/backend/parser/gram.y
index a6d0cefa6b..2a319eecda 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 <node> stmt toplevel_stmt schema_stmt routine_body_stmt
@@ -570,7 +585,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> column_compression opt_column_compression
%type <list> ColQualList
%type <node> ColConstraint ColConstraintElem ConstraintAttr
-%type <ival> key_actions key_delete key_match key_update key_action
+%type <ival> key_match
+%type <keyaction> key_delete key_update key_action
+%type <keyactions> key_actions
%type <ival> ConstraintAttributeSpec ConstraintAttributeElem
%type <str> 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..ef61acffe8 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
+/* 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 9fa9e671a1..8cf9d44dad 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);
/* 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..c19cf2cdad 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 should be 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)
On Mon, Nov 22, 2021 at 8:04 PM Paul Martinez <hellopfm@gmail.com> wrote:
On Thu, Nov 11, 2021 at 4:34 AM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:I have reviewed your patch
referential-actions-on-delete-only-set-cols-v3.patch. Attached are two
patches that go on top of yours that contain my recommended changes.Basically, this all looks pretty good to me. My changes are mostly
stylistic.Thank you! I really, really appreciate the thorough review and the
comments and corrections.Some notes of substance:
- The omission of the referential actions details from the CREATE TABLE
reference page surprised me. I have committed that separately (without
the column support, of course). So you should rebase your patch on top
of that. Note that ALTER TABLE would now also need to be updated by
your patch.Done.
- I recommend setting pg_constraint.confdelsetcols to null for the
default behavior of setting all columns, instead of an empty array the
way you have done it. I have noted the places in the code that need to
be changed for that.Done.
- The outfuncs.c support shouldn't be included in the final patch.
There is nothing wrong it, but I don't think it should be part of this
patch to add piecemeal support like that. I have included a few changes
there anyway for completeness.Got it. I've reverted the changes in that file.
- In ri_triggers.c, I follow your renaming of the constants, but
RI_PLAN_ONTRIGGER_RESTRICT seems a little weird. Maybe do _ONBOTH_, or
else reverse the order, like RI_PLAN_SETNULL_ONDELETE, which would then
allow RI_PLAN_RESTRICT.I've reversed the order, so it's now RI_PLAN_<action>_<trigger>, and
renamed the RESTRICT one to just RI_PLAN_RESTRICT.I've attached an updated patch, including your changes and the additional
changes mentioned above.- Paul
Hi,
+ If a foreign key with a <literal>SET NULL</literal> or <literal>SET
+ DEFAULT</literal> delete action, which columns should be updated.
which columns should be updated -> the columns that should be updated
+ if (fk_del_set_cols)
+ {
+ int num_delete_cols = 0;
Since num_delete_cols is only used in the else block, I think it can be
moved inside else block.
Or you can store the value inside *num_fk_del_set_cols directly and avoid
num_delete_cols.
Cheers
On Mon, Nov 22, 2021 at 10:21 PM Zhihong Yu <zyu@yugabyte.com> wrote:
Hi, + If a foreign key with a <literal>SET NULL</literal> or <literal>SET + DEFAULT</literal> delete action, which columns should be updated.which columns should be updated -> the columns that should be updated
Done.
+ if (fk_del_set_cols) + { + int num_delete_cols = 0;Since num_delete_cols is only used in the else block, I think it can be moved inside else block.
Or you can store the value inside *num_fk_del_set_cols directly and avoid num_delete_cols.
I've moved it inside the else block (and removed the initialization).
Updated patch attached. Thanks for taking a look so quickly!
- Paul
Attachments:
referential-actions-on-delete-only-set-cols-v5.patchapplication/octet-stream; name=referential-actions-on-delete-only-set-cols-v5.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c1d11be73f..27c2378815 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2708,6 +2708,18 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>confdelsetcols</structfield> <type>int2[]</type>
+ (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+ </para>
+ <para>
+ If a foreign key with a <literal>SET NULL</literal> or <literal>SET
+ DEFAULT</literal> delete action, the columns that should be updated.
+ If null, all of the referencing columns should be updated.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>conexclop</structfield> <type>oid[]</type>
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 <replaceable class="parameter">numeric_literal</replaceable>, REM
<phrase><replaceable class="parameter">referential_action</replaceable> in a <literal>FOREIGN KEY</literal>/<literal>REFERENCES</literal> constraint is:</phrase>
-{ NO ACTION | RESTRICT | CASCADE | SET NULL | SET DEFAULT }
+{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] | SET DEFAULT [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] }
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 57d51a676a..0a526c9253 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -108,7 +108,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
<phrase><replaceable class="parameter">referential_action</replaceable> in a <literal>FOREIGN KEY</literal>/<literal>REFERENCES</literal> constraint is:</phrase>
-{ NO ACTION | RESTRICT | CASCADE | SET NULL | SET DEFAULT }
+{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] | SET DEFAULT [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] }
</synopsis>
</refsynopsisdiv>
@@ -1169,19 +1169,23 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</varlistentry>
<varlistentry>
- <term><literal>SET NULL</literal></term>
+ <term><literal>SET NULL [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
<listitem>
<para>
- 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 <literal>ON DELETE</literal> actions.
</para>
</listitem>
</varlistentry>
<varlistentry>
- <term><literal>SET DEFAULT</literal></term>
+ <term><literal>SET DEFAULT [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
<listitem>
<para>
- 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 <literal>ON DELETE</literal> actions.
(There must be a row in the referenced table matching the default
values, if they are not null, or the operation will fail.)
</para>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 81cc39fb70..67983e916b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2484,6 +2484,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 c255806e38..95a0739ef4 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 785b282e69..7118e93a91 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);
@@ -8961,9 +8966,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;
@@ -9059,11 +9066,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
@@ -9338,6 +9353,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok);
/* Now handle the referencing side. */
@@ -9350,6 +9367,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok,
lockmode);
@@ -9361,6 +9380,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
@@ -9382,6 +9435,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).
@@ -9391,7 +9448,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;
@@ -9466,6 +9525,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 */
@@ -9547,6 +9608,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) */
@@ -9587,6 +9649,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.
@@ -9596,6 +9662,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));
@@ -9734,6 +9801,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
numfks,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
+ fkdelsetcols,
+ numfkdelsetcols,
fkconstraint->fk_matchtype,
NULL,
NULL,
@@ -9766,6 +9835,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
+ numfkdelsetcols,
+ fkdelsetcols,
old_check_ok,
lockmode);
@@ -9871,6 +9942,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);
@@ -9903,7 +9976,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];
@@ -9950,6 +10025,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
conpfeqop,
conppeqop,
conffeqop,
+ numfkdelsetcols,
+ confdelsetcols,
true);
table_close(fkRel, NoLock);
@@ -10020,6 +10097,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;
@@ -10051,7 +10130,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];
@@ -10136,6 +10216,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
numfks,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
+ confdelsetcols,
+ numfkdelsetcols,
fkconstraint->fk_matchtype,
NULL,
NULL,
@@ -10171,6 +10253,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
conpfeqop,
conppeqop,
conffeqop,
+ numfkdelsetcols,
+ confdelsetcols,
false, /* no old check exists */
AccessExclusiveLock);
table_close(pkrel, NoLock);
@@ -10792,7 +10876,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,
@@ -10819,7 +10903,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 ad1ea2ff2f..f3bf7f2d57 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3081,6 +3081,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/parser/gram.y b/src/backend/parser/gram.y
index a6d0cefa6b..2a319eecda 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 <node> stmt toplevel_stmt schema_stmt routine_body_stmt
@@ -570,7 +585,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> column_compression opt_column_compression
%type <list> ColQualList
%type <node> ColConstraint ColConstraintElem ConstraintAttr
-%type <ival> key_actions key_delete key_match key_update key_action
+%type <ival> key_match
+%type <keyaction> key_delete key_update key_action
+%type <keyactions> key_actions
%type <ival> ConstraintAttributeSpec ConstraintAttributeElem
%type <str> 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..8afdb50ba7 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
+/* 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 9fa9e671a1..8cf9d44dad 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);
/* 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..c19cf2cdad 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 should be 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)
On 05.01.21 22:40, Paul Martinez wrote:
CREATE TABLE tenants (id serial PRIMARY KEY);
CREATE TABLE users (
tenant_id int REFERENCES tenants ON DELETE CASCADE,
id serial,
PRIMARY KEY (tenant_id, id),
);
CREATE TABLE posts (
tenant_id int REFERENCES tenants ON DELETE CASCADE,
id serial,
author_id int,
PRIMARY KEY (tenant_id, id),
FOREIGN KEY (tenant_id, author_id) REFERENCES users ON DELETE SET NULL
);INSERT INTO tenants VALUES (1);
INSERT INTO users VALUES (1, 101);
INSERT INTO posts VALUES (1, 201, 101);
DELETE FROM users WHERE id = 101;
ERROR: null value in column "tenant_id" violates not-null constraint
DETAIL: Failing row contains (null, 201, null).
I was looking through this example to see if it could be adapted for the
documentation.
The way the users table is defined, it appears that "id" is actually
unique and the primary key ought to be just (id). The DELETE command
you show also just uses the id column to find the user, which would be
bad if the user id is not unique across tenants. If the id were unique,
then the foreign key from posts to users would just use the user id
column and the whole problem of the ON DELETE SET NULL action would go
away. If the primary key of users is indeed supposed to be (tenant_id,
id), then maybe the definition of the users.id column should not use
serial, and the DELETE command should also look at the tenant_id column.
(The same question applies to posts.id.)
Also, you initially wrote that this is a denormalized schema. I think
if we keep the keys the way you show, then this isn't denormalized. But
if we considered users.id globally unique, then there would be
normalization concerns.
What do you think?
On 23.11.21 05:44, Paul Martinez wrote:
Updated patch attached. Thanks for taking a look so quickly!
This patch looks pretty much okay to me. As I wrote in another message
in this thread, I'm having some doubts about the proper use case. So
I'm going to push this commit fest entry to the next one, so we can
continue that discussion.
On Wed, 1 Dec 2021 at 11:33, Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:
On 23.11.21 05:44, Paul Martinez wrote:
Updated patch attached. Thanks for taking a look so quickly!
This patch looks pretty much okay to me. As I wrote in another message
in this thread, I'm having some doubts about the proper use case. So
I'm going to push this commit fest entry to the next one, so we can
continue that discussion.
The use case of the original mail "foreign keys are guaranteed to not
be cross-tenant" seems like a good enough use case to me?
The alternative to the discriminator column approach to seperating
tenant data even when following referential integrety checks would be
maintaining a copy of the table for each tenant, but this won't work
as well due to (amongst others) syscache bloat, prepared statements
being significantly less effective, and DDL operations now growing
linearly with the amount of tenants in the system.
Kind regards,
Matthias van de Meent
On Wed, Nov 24, 2021 at 10:59 AM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:
I was looking through this example to see if it could be adapted for the
documentation.The way the users table is defined, it appears that "id" is actually
unique and the primary key ought to be just (id). The DELETE command
you show also just uses the id column to find the user, which would be
bad if the user id is not unique across tenants. If the id were unique,
then the foreign key from posts to users would just use the user id
column and the whole problem of the ON DELETE SET NULL action would go
away. If the primary key of users is indeed supposed to be (tenant_id,
id), then maybe the definition of the users.id column should not use
serial, and the DELETE command should also look at the tenant_id column.
(The same question applies to posts.id.)Also, you initially wrote that this is a denormalized schema. I think
if we keep the keys the way you show, then this isn't denormalized. But
if we considered users.id globally unique, then there would be
normalization concerns.What do you think?
Regarding that specific example, in a production scenario, yes, the
DELETE command should reference both columns. And if used for
documentation both columns should be referenced for clarity/correctness.
I don't think the exact semantics regarding the uniqueness of the id
column are critical. Configuring separate auto-incrementing ids per
tenant would be fairly complex; practically speaking, a single
database with multi-tenant data will use serial to get auto-incrementing
ids (or else use UUIDs to prevent conflicts). The possibility of
conflicting ids likely won't arise until moving to a distributed
environment, at which point queries should only be routed towards a
single shard (where uniqueness will still hold), either by some higher
level application-level context, or by including the tenant_id as part
of the query.
I think there are three separate motivating use cases for using
(tenant_id, id) as primary keys everywhere in a multi-tenant database:
1) I initially encountered this problem while migrating a database to use
Citus, which requires that primary keys (and any other uniqueness
constraints) include the shard key, which forces the primary key to be
(tenant_id, id). I'm not sure what constraints other sharding solutions
enforce, but I don't feel like this feature is over-fitting to Citus'
specific implementation -- it seems like a pretty
reasonable/generalizable solution when sharding data: prefix all your
indexes with the shard key.
2) As I mentioned in my response to Tom in my original proposal thread,
and as Matthias alluded to, using composite primary keys grants
significantly stronger referential integrity by preventing cross-tenant
references. I think this represents a significant leap in the robustness
and security of a schema, to the point where you could consider it a
design flaw to _not_ use composite keys.
/messages/by-id/CAF+2_SFFCjWMpxo0cj3yaqMavcb3Byd0bSG+0UPs7RVb8EF99g@mail.gmail.com
3) For performance reasons, indexes on foreign keys will often be
prefixed by the tenant_id to speed up index scans. (I think
algorithmically doing an index lookup on (fk_id) vs. (tenant_id, fk_id)
has the same complexity, but repeated index scans, such as when doing a
join, should in practice be more efficient when including a tenant_id,
because most queries will only reference a single tenant so the looked
up values are more likely to be on the same pages.) If a foreign key
only references the id column, then ON DELETE CASCADE triggers will only
use the id column in their DELETE query. Thus, to ensure that deletes
are still fast, you will need to create an index on (fk_id) in addition
to the (tenant_id, fk_id) index, which would cause _significant_
database bloat. (In practice, the presence of both indexes will also
confuse the query planner and now BOTH indexes will take up precious
space in the database's working memory, so it really creates all sorts
of problems.) Using a composite foreign key will ensure that ON DELETE
CASCADE trigger query will use both columns.
- Paul
On 02.12.21 01:17, Paul Martinez wrote:
Regarding that specific example, in a production scenario, yes, the
DELETE command should reference both columns. And if used for
documentation both columns should be referenced for clarity/correctness.
Ok, thanks. I have updated your example accordingly and included it in
the patch, which I have now committed.