Allow specifying column list for foreign key ON DELETE SET actions

Extend the foreign key ON DELETE actions SET NULL and SET DEFAULT by
allowing the specification of a column list, like

    CREATE TABLE posts (
        ...
        FOREIGN KEY (tenant_id, author_id) REFERENCES users ON DELETE SET NULL (author_id)
    );

If a column list is specified, only those columns are set to
null/default, instead of all the columns in the foreign-key
constraint.

This is useful for multitenant or sharded schemas, where the tenant or
shard ID is included in the primary key of all tables but shouldn't be
set to null.

Author: Paul Martinez <paulmtz@google.com>
Discussion: https://www.postgresql.org/message-id/flat/CACqFVBZQyMYJV=njbSMxf+rbDHpx=W=B7AEaMKn8dWn9OZJY7w@mail.gmail.com
This commit is contained in:
Peter Eisentraut 2021-12-08 11:09:44 +01:00
parent e464cb7af3
commit d6f96ed94e
22 changed files with 557 additions and 72 deletions

View File

@ -2708,6 +2708,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</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 will be updated.
If null, all of the referencing columns will be updated.
</para></entry>
</row>
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>conexclop</structfield> <type>oid[]</type>

View File

@ -1083,10 +1083,41 @@ CREATE TABLE order_items (
manager to null or a default might be useful.
</para>
<para>
The actions <literal>SET NULL</literal> and <literal>SET DEFAULT</literal>
can take a column list to specify which columns to set. Normally, all
columns of the foreign-key constraint are set; setting only a subset is
useful in some special cases. Consider the following example:
<programlisting>
CREATE TABLE tenants (
tenant_id integer PRIMARY KEY
);
CREATE TABLE users (
tenant_id integer REFERENCES tenants ON DELETE CASCADE,
user_id integer NOT NULL,
PRIMARY KEY (tenant_id, user_id)
);
CREATE TABLE posts (
tenant_id integer REFERENCES tenants ON DELETE CASCADE,
post_id integer NOT NULL,
author_id integer,
PRIMARY KEY (tenant_id, post_id),
FOREIGN KEY (tenant_id, author_id) REFERENCES users ON DELETE SET NULL <emphasis>(author_id)</emphasis>
);
</programlisting>
Without the specification of the column, the foreign key would also set
the column <literal>tenant_id</literal> to null, but that column is still
required as part of the primary key.
</para>
<para>
Analogous to <literal>ON DELETE</literal> there is also
<literal>ON UPDATE</literal> which is invoked when a referenced
column is changed (updated). The possible actions are the same.
column is changed (updated). The possible actions are the same,
except that column lists cannot be specified for <literal>SET
NULL</literal> and <literal>SET DEFAULT</literal>.
In this case, <literal>CASCADE</literal> means that the updated values of the
referenced column(s) should be copied into the referencing row(s).
</para>

View File

@ -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>

View File

@ -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>
@ -2223,6 +2227,16 @@ CREATE TABLE cities_partdef
</para>
</refsect2>
<refsect2>
<title>Foreign-Key Constraint Actions</title>
<para>
The ability to specify column lists in the foreign-key actions
<literal>SET DEFAULT</literal> and <literal>SET NULL</literal> is a
<productname>PostgreSQL</productname> extension.
</para>
</refsect2>
<refsect2>
<title><literal>NULL</literal> <quote>Constraint</quote></title>

View File

@ -2441,6 +2441,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
0,
' ',
' ',
NULL,
0,
' ',
NULL, /* not an exclusion constraint */
expr, /* Tree form of check constraint */

View File

@ -1969,6 +1969,8 @@ index_constraint_create(Relation heapRelation,
0,
' ',
' ',
NULL,
0,
' ',
indexInfo->ii_ExclusionOps,
NULL, /* no check constraint */

View File

@ -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;
}

View File

@ -482,11 +482,16 @@ static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstra
Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
int numfks, int16 *pkattnum, int16 *fkattnum,
Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
int numfkdelsetcols, int16 *fkdelsetcols,
bool old_check_ok);
static void validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
int numfksetcols, const int16 *fksetcolsattnums,
List *fksetcols);
static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
int numfks, int16 *pkattnum, int16 *fkattnum,
Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
int numfkdelsetcols, int16 *fkdelsetcols,
bool old_check_ok, LOCKMODE lockmode);
static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
Relation partitionRel);
@ -8973,9 +8978,11 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
Oid pfeqoperators[INDEX_MAX_KEYS];
Oid ppeqoperators[INDEX_MAX_KEYS];
Oid ffeqoperators[INDEX_MAX_KEYS];
int16 fkdelsetcols[INDEX_MAX_KEYS];
int i;
int numfks,
numpks;
numpks,
numfkdelsetcols;
Oid indexOid;
bool old_check_ok;
ObjectAddress address;
@ -9071,11 +9078,19 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
MemSet(pfeqoperators, 0, sizeof(pfeqoperators));
MemSet(ppeqoperators, 0, sizeof(ppeqoperators));
MemSet(ffeqoperators, 0, sizeof(ffeqoperators));
MemSet(fkdelsetcols, 0, sizeof(fkdelsetcols));
numfks = transformColumnNameList(RelationGetRelid(rel),
fkconstraint->fk_attrs,
fkattnum, fktypoid);
numfkdelsetcols = transformColumnNameList(RelationGetRelid(rel),
fkconstraint->fk_del_set_cols,
fkdelsetcols, NULL);
validateFkOnDeleteSetColumns(numfks, fkattnum,
numfkdelsetcols, fkdelsetcols,
fkconstraint->fk_del_set_cols);
/*
* If the attribute list for the referenced table was omitted, lookup the
* definition of the primary key and use it. Otherwise, validate the
@ -9350,6 +9365,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
numfkdelsetcols,
fkdelsetcols,
old_check_ok);
/* Now handle the referencing side. */
@ -9362,6 +9379,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
numfkdelsetcols,
fkdelsetcols,
old_check_ok,
lockmode);
@ -9373,6 +9392,40 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
return address;
}
/*
* validateFkActionSetColumns
* Verifies that columns used in ON DELETE SET NULL/DEFAULT (...)
* column lists are valid.
*/
void
validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
int numfksetcols, const int16 *fksetcolsattnums,
List *fksetcols)
{
for (int i = 0; i < numfksetcols; i++)
{
int16 setcol_attnum = fksetcolsattnums[i];
bool seen = false;
for (int j = 0; j < numfks; j++)
{
if (fkattnums[j] == setcol_attnum)
{
seen = true;
break;
}
}
if (!seen)
{
char *col = strVal(list_nth(fksetcols, i));
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("column \"%s\" referenced in ON DELETE SET action must be part of foreign key", col)));
}
}
}
/*
* addFkRecurseReferenced
* subroutine for ATAddForeignKeyConstraint; recurses on the referenced
@ -9394,6 +9447,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
* numfks is the number of columns in the foreign key
* pkattnum is the attnum array of referenced attributes.
* fkattnum is the attnum array of referencing attributes.
* numfkdelsetcols is the number of columns in the ON DELETE SET NULL/DELETE
* (...) clause
* fkdelsetcols is the attnum array of the columns in the ON DELETE SET
* NULL/DELETE clause
* pf/pp/ffeqoperators are OID array of operators between columns.
* old_check_ok signals that this constraint replaces an existing one that
* was already validated (thus this one doesn't need validation).
@ -9403,7 +9460,9 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
Relation pkrel, Oid indexOid, Oid parentConstr,
int numfks,
int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok)
Oid *ppeqoperators, Oid *ffeqoperators,
int numfkdelsetcols, int16 *fkdelsetcols,
bool old_check_ok)
{
ObjectAddress address;
Oid constrOid;
@ -9478,6 +9537,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
numfks,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
fkdelsetcols,
numfkdelsetcols,
fkconstraint->fk_matchtype,
NULL, /* no exclusion constraint */
NULL, /* no check constraint */
@ -9559,6 +9620,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
partIndexId, constrOid, numfks,
mapped_pkattnum, fkattnum,
pfeqoperators, ppeqoperators, ffeqoperators,
numfkdelsetcols, fkdelsetcols,
old_check_ok);
/* Done -- clean up (but keep the lock) */
@ -9599,6 +9661,10 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
* pkattnum is the attnum array of referenced attributes.
* fkattnum is the attnum array of referencing attributes.
* pf/pp/ffeqoperators are OID array of operators between columns.
* numfkdelsetcols is the number of columns in the ON DELETE SET NULL/DELETE
* (...) clause
* fkdelsetcols is the attnum array of the columns in the ON DELETE SET
* NULL/DELETE clause
* old_check_ok signals that this constraint replaces an existing one that
* was already validated (thus this one doesn't need validation).
* lockmode is the lockmode to acquire on partitions when recursing.
@ -9608,6 +9674,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
Relation pkrel, Oid indexOid, Oid parentConstr,
int numfks, int16 *pkattnum, int16 *fkattnum,
Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
int numfkdelsetcols, int16 *fkdelsetcols,
bool old_check_ok, LOCKMODE lockmode)
{
AssertArg(OidIsValid(parentConstr));
@ -9746,6 +9813,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
numfks,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
fkdelsetcols,
numfkdelsetcols,
fkconstraint->fk_matchtype,
NULL,
NULL,
@ -9778,6 +9847,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
pfeqoperators,
ppeqoperators,
ffeqoperators,
numfkdelsetcols,
fkdelsetcols,
old_check_ok,
lockmode);
@ -9883,6 +9954,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
Oid conpfeqop[INDEX_MAX_KEYS];
Oid conppeqop[INDEX_MAX_KEYS];
Oid conffeqop[INDEX_MAX_KEYS];
int numfkdelsetcols;
AttrNumber confdelsetcols[INDEX_MAX_KEYS];
Constraint *fkconstraint;
tuple = SearchSysCache1(CONSTROID, constrOid);
@ -9915,7 +9988,9 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
confkey,
conpfeqop,
conppeqop,
conffeqop);
conffeqop,
&numfkdelsetcols,
confdelsetcols);
for (int i = 0; i < numfks; i++)
mapped_confkey[i] = attmap->attnums[confkey[i] - 1];
@ -9962,6 +10037,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
conpfeqop,
conppeqop,
conffeqop,
numfkdelsetcols,
confdelsetcols,
true);
table_close(fkRel, NoLock);
@ -10032,6 +10109,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
Oid conpfeqop[INDEX_MAX_KEYS];
Oid conppeqop[INDEX_MAX_KEYS];
Oid conffeqop[INDEX_MAX_KEYS];
int numfkdelsetcols;
AttrNumber confdelsetcols[INDEX_MAX_KEYS];
Constraint *fkconstraint;
bool attached;
Oid indexOid;
@ -10063,7 +10142,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
ShareRowExclusiveLock, NULL);
DeconstructFkConstraintRow(tuple, &numfks, conkey, confkey,
conpfeqop, conppeqop, conffeqop);
conpfeqop, conppeqop, conffeqop,
&numfkdelsetcols, confdelsetcols);
for (int i = 0; i < numfks; i++)
mapped_conkey[i] = attmap->attnums[conkey[i] - 1];
@ -10148,6 +10228,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
numfks,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
confdelsetcols,
numfkdelsetcols,
fkconstraint->fk_matchtype,
NULL,
NULL,
@ -10183,6 +10265,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
conpfeqop,
conppeqop,
conffeqop,
numfkdelsetcols,
confdelsetcols,
false, /* no old check exists */
AccessExclusiveLock);
table_close(pkrel, NoLock);
@ -10804,7 +10888,7 @@ ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName,
/*
* transformColumnNameList - transform list of column names
*
* Lookup each name and return its attnum and type OID
* Lookup each name and return its attnum and, optionally, type OID
*/
static int
transformColumnNameList(Oid relId, List *colList,
@ -10831,7 +10915,8 @@ transformColumnNameList(Oid relId, List *colList,
errmsg("cannot have more than %d keys in a foreign key",
INDEX_MAX_KEYS)));
attnums[attnum] = ((Form_pg_attribute) GETSTRUCT(atttuple))->attnum;
atttypids[attnum] = ((Form_pg_attribute) GETSTRUCT(atttuple))->atttypid;
if (atttypids != NULL)
atttypids[attnum] = ((Form_pg_attribute) GETSTRUCT(atttuple))->atttypid;
ReleaseSysCache(atttuple);
attnum++;
}

View File

@ -829,6 +829,8 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
0,
' ',
' ',
NULL,
0,
' ',
NULL, /* no exclusion */
NULL, /* no check constraint */

View File

@ -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 */

View File

@ -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);

View File

@ -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);

View File

@ -3735,6 +3735,7 @@ _outConstraint(StringInfo str, const Constraint *node)
WRITE_CHAR_FIELD(fk_matchtype);
WRITE_CHAR_FIELD(fk_upd_action);
WRITE_CHAR_FIELD(fk_del_action);
WRITE_NODE_FIELD(fk_del_set_cols);
WRITE_NODE_FIELD(old_conpfeqop);
WRITE_OID_FIELD(old_pktable_oid);
WRITE_BOOL_FIELD(skip_validation);

View File

@ -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; }

View File

@ -73,11 +73,14 @@
#define RI_PLAN_CHECK_LOOKUPPK_FROM_PK 2
#define RI_PLAN_LAST_ON_PK RI_PLAN_CHECK_LOOKUPPK_FROM_PK
/* these queries are executed against the FK (referencing) table: */
#define RI_PLAN_CASCADE_DEL_DODELETE 3
#define RI_PLAN_CASCADE_UPD_DOUPDATE 4
#define RI_PLAN_RESTRICT_CHECKREF 5
#define RI_PLAN_SETNULL_DOUPDATE 6
#define RI_PLAN_SETDEFAULT_DOUPDATE 7
#define RI_PLAN_CASCADE_ONDELETE 3
#define RI_PLAN_CASCADE_ONUPDATE 4
/* For RESTRICT, the same plan can be used for both ON DELETE and ON UPDATE triggers. */
#define RI_PLAN_RESTRICT 5
#define RI_PLAN_SETNULL_ONDELETE 6
#define RI_PLAN_SETNULL_ONUPDATE 7
#define RI_PLAN_SETDEFAULT_ONDELETE 8
#define RI_PLAN_SETDEFAULT_ONUPDATE 9
#define MAX_QUOTED_NAME_LEN (NAMEDATALEN*2+3)
#define MAX_QUOTED_REL_NAME_LEN (MAX_QUOTED_NAME_LEN*2)
@ -110,6 +113,8 @@ typedef struct RI_ConstraintInfo
Oid fk_relid; /* referencing relation */
char confupdtype; /* foreign key's ON UPDATE action */
char confdeltype; /* foreign key's ON DELETE action */
int ndelsetcols; /* number of columns referenced in ON DELETE SET clause */
int16 confdelsetcols[RI_MAX_NUMKEYS]; /* attnums of cols to set on delete */
char confmatchtype; /* foreign key's match type */
int nkeys; /* number of key columns */
int16 pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */
@ -180,7 +185,7 @@ static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
TupleTableSlot *oldslot,
const RI_ConstraintInfo *riinfo);
static Datum ri_restrict(TriggerData *trigdata, bool is_no_action);
static Datum ri_set(TriggerData *trigdata, bool is_set_null);
static Datum ri_set(TriggerData *trigdata, bool is_set_null, int tgkind);
static void quoteOneName(char *buffer, const char *name);
static void quoteRelationName(char *buffer, Relation rel);
static void ri_GenerateQual(StringInfo buf,
@ -660,7 +665,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
* Fetch or prepare a saved plan for the restrict lookup (it's the same
* query for delete and update cases)
*/
ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_RESTRICT_CHECKREF);
ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_RESTRICT);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
@ -767,7 +772,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
elog(ERROR, "SPI_connect failed");
/* Fetch or prepare a saved plan for the cascaded delete */
ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_DEL_DODELETE);
ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_ONDELETE);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
@ -876,7 +881,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
elog(ERROR, "SPI_connect failed");
/* Fetch or prepare a saved plan for the cascaded update */
ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_UPD_DOUPDATE);
ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_ONUPDATE);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
@ -970,7 +975,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
ri_CheckTrigger(fcinfo, "RI_FKey_setnull_del", RI_TRIGTYPE_DELETE);
/* Share code with UPDATE case */
return ri_set((TriggerData *) fcinfo->context, true);
return ri_set((TriggerData *) fcinfo->context, true, RI_TRIGTYPE_DELETE);
}
/*
@ -985,7 +990,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
ri_CheckTrigger(fcinfo, "RI_FKey_setnull_upd", RI_TRIGTYPE_UPDATE);
/* Share code with DELETE case */
return ri_set((TriggerData *) fcinfo->context, true);
return ri_set((TriggerData *) fcinfo->context, true, RI_TRIGTYPE_UPDATE);
}
/*
@ -1000,7 +1005,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_del", RI_TRIGTYPE_DELETE);
/* Share code with UPDATE case */
return ri_set((TriggerData *) fcinfo->context, false);
return ri_set((TriggerData *) fcinfo->context, false, RI_TRIGTYPE_DELETE);
}
/*
@ -1015,7 +1020,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_upd", RI_TRIGTYPE_UPDATE);
/* Share code with DELETE case */
return ri_set((TriggerData *) fcinfo->context, false);
return ri_set((TriggerData *) fcinfo->context, false, RI_TRIGTYPE_UPDATE);
}
/*
@ -1025,7 +1030,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
* NULL, and ON UPDATE SET DEFAULT.
*/
static Datum
ri_set(TriggerData *trigdata, bool is_set_null)
ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
{
const RI_ConstraintInfo *riinfo;
Relation fk_rel;
@ -1033,6 +1038,7 @@ ri_set(TriggerData *trigdata, bool is_set_null)
TupleTableSlot *oldslot;
RI_QueryKey qkey;
SPIPlanPtr qplan;
int32 queryno;
riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
trigdata->tg_relation, true);
@ -1051,18 +1057,28 @@ ri_set(TriggerData *trigdata, bool is_set_null)
elog(ERROR, "SPI_connect failed");
/*
* Fetch or prepare a saved plan for the set null/default operation (it's
* the same query for delete and update cases)
* Fetch or prepare a saved plan for the trigger.
*/
ri_BuildQueryKey(&qkey, riinfo,
(is_set_null
? RI_PLAN_SETNULL_DOUPDATE
: RI_PLAN_SETDEFAULT_DOUPDATE));
switch (tgkind) {
case RI_TRIGTYPE_UPDATE:
queryno = is_set_null
? RI_PLAN_SETNULL_ONUPDATE
: RI_PLAN_SETDEFAULT_ONUPDATE;
break;
case RI_TRIGTYPE_DELETE:
queryno = is_set_null
? RI_PLAN_SETNULL_ONDELETE
: RI_PLAN_SETDEFAULT_ONDELETE;
break;
default:
elog(ERROR, "invalid tgkind passed to ri_set");
}
ri_BuildQueryKey(&qkey, riinfo, queryno);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
StringInfoData querybuf;
StringInfoData qualbuf;
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
char paramname[16];
@ -1070,6 +1086,32 @@ ri_set(TriggerData *trigdata, bool is_set_null)
const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS];
const char *fk_only;
int num_cols_to_set;
const int16 *set_cols;
switch (tgkind) {
case RI_TRIGTYPE_UPDATE:
num_cols_to_set = riinfo->nkeys;
set_cols = riinfo->fk_attnums;
break;
case RI_TRIGTYPE_DELETE:
/*
* If confdelsetcols are present, then we only update
* the columns specified in that array, otherwise we
* update all the referencing columns.
*/
if (riinfo->ndelsetcols != 0) {
num_cols_to_set = riinfo->ndelsetcols;
set_cols = riinfo->confdelsetcols;
}
else {
num_cols_to_set = riinfo->nkeys;
set_cols = riinfo->fk_attnums;
}
break;
default:
elog(ERROR, "invalid tgkind passed to ri_set");
}
/* ----------
* The query string built is
@ -1080,13 +1122,29 @@ ri_set(TriggerData *trigdata, bool is_set_null)
* ----------
*/
initStringInfo(&querybuf);
initStringInfo(&qualbuf);
fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
"" : "ONLY ";
quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "UPDATE %s%s SET",
fk_only, fkrelname);
/*
* Add assignment clauses
*/
querysep = "";
for (int i = 0; i < num_cols_to_set; i++)
{
quoteOneName(attname, RIAttName(fk_rel, set_cols[i]));
appendStringInfo(&querybuf,
"%s %s = %s",
querysep, attname,
is_set_null ? "NULL" : "DEFAULT");
querysep = ",";
}
/*
* Add WHERE clause
*/
qualsep = "WHERE";
for (int i = 0; i < riinfo->nkeys; i++)
{
@ -1097,22 +1155,17 @@ ri_set(TriggerData *trigdata, bool is_set_null)
quoteOneName(attname,
RIAttName(fk_rel, riinfo->fk_attnums[i]));
appendStringInfo(&querybuf,
"%s %s = %s",
querysep, attname,
is_set_null ? "NULL" : "DEFAULT");
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&qualbuf, qualsep,
ri_GenerateQual(&querybuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
attname, fk_type);
if (pk_coll != fk_coll && !get_collation_isdeterministic(pk_coll))
ri_GenerateQualCollation(&querybuf, pk_coll);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
}
appendBinaryStringInfo(&querybuf, qualbuf.data, qualbuf.len);
/* Prepare and save the plan */
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
@ -2098,7 +2151,9 @@ ri_LoadConstraintInfo(Oid constraintOid)
riinfo->pk_attnums,
riinfo->pf_eq_oprs,
riinfo->pp_eq_oprs,
riinfo->ff_eq_oprs);
riinfo->ff_eq_oprs,
&riinfo->ndelsetcols,
riinfo->confdelsetcols);
ReleaseSysCache(tup);

View File

@ -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:

View File

@ -4592,7 +4592,7 @@ RelationGetFKeyList(Relation relation)
info->conkey,
info->confkey,
info->conpfeqop,
NULL, NULL);
NULL, NULL, NULL, NULL);
/* Add FK's node to the result list */
result = lappend(result, info);

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 202111301
#define CATALOG_VERSION_NO 202112081
#endif

View File

@ -138,6 +138,12 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
*/
Oid conffeqop[1] BKI_LOOKUP(pg_operator);
/*
* If a foreign key with an ON DELETE SET NULL/DEFAULT action, the subset
* of conkey to updated. If null, all columns are updated.
*/
int16 confdelsetcols[1];
/*
* If an exclusion constraint, the OIDs of the exclusion operators for
* each column of the constraint
@ -220,6 +226,8 @@ extern Oid CreateConstraintEntry(const char *constraintName,
int foreignNKeys,
char foreignUpdateType,
char foreignDeleteType,
const int16 *fkDeleteSetCols,
int numFkDeleteSetCols,
char foreignMatchType,
const Oid *exclOp,
Node *conExpr,
@ -254,7 +262,8 @@ extern Bitmapset *get_primary_key_attnos(Oid relid, bool deferrableOk,
Oid *constraintOid);
extern void DeconstructFkConstraintRow(HeapTuple tuple, int *numfks,
AttrNumber *conkey, AttrNumber *confkey,
Oid *pf_eq_oprs, Oid *pp_eq_oprs, Oid *ff_eq_oprs);
Oid *pf_eq_oprs, Oid *pp_eq_oprs, Oid *ff_eq_oprs,
int *num_fk_del_set_cols, AttrNumber *fk_del_set_cols);
extern bool check_functional_grouping(Oid relid,
Index varno, Index varlevelsup,

View File

@ -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 */

View File

@ -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)

View File

@ -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)