Commit fe3db740 authored by Tom Lane's avatar Tom Lane

Share RI trigger code between NO ACTION and RESTRICT cases.

These triggers are identical except for whether ri_Check_Pk_Match is to be
called, so factor out the common code to save a couple hundred lines.

Also, eliminate null-column checks in ri_Check_Pk_Match, since they're
duplicate with the calling functions and require unnecessary complication
in its API statement.

Simplify the way code is shared between RI_FKey_check_ins and
RI_FKey_check_upd, too.
parent 48756be9
......@@ -74,14 +74,12 @@
/* 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_NOACTION_DEL_CHECKREF 5
#define RI_PLAN_NOACTION_UPD_CHECKREF 6
#define RI_PLAN_RESTRICT_DEL_CHECKREF 7
#define RI_PLAN_RESTRICT_UPD_CHECKREF 8
#define RI_PLAN_SETNULL_DEL_DOUPDATE 9
#define RI_PLAN_SETNULL_UPD_DOUPDATE 10
#define RI_PLAN_SETDEFAULT_DEL_DOUPDATE 11
#define RI_PLAN_SETDEFAULT_UPD_DOUPDATE 12
#define RI_PLAN_RESTRICT_DEL_CHECKREF 5
#define RI_PLAN_RESTRICT_UPD_CHECKREF 6
#define RI_PLAN_SETNULL_DEL_DOUPDATE 7
#define RI_PLAN_SETNULL_UPD_DOUPDATE 8
#define RI_PLAN_SETDEFAULT_DEL_DOUPDATE 9
#define RI_PLAN_SETDEFAULT_UPD_DOUPDATE 10
#define MAX_QUOTED_NAME_LEN (NAMEDATALEN*2+3)
#define MAX_QUOTED_REL_NAME_LEN (MAX_QUOTED_NAME_LEN*2)
......@@ -92,8 +90,7 @@
#define RI_TRIGTYPE_INSERT 1
#define RI_TRIGTYPE_UPDATE 2
#define RI_TRIGTYPE_INUP 3
#define RI_TRIGTYPE_DELETE 4
#define RI_TRIGTYPE_DELETE 3
/* ----------
......@@ -185,6 +182,11 @@ static HTAB *ri_compare_cache = NULL;
* Local function prototypes
* ----------
*/
static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
HeapTuple old_row,
const RI_ConstraintInfo *riinfo);
static Datum ri_restrict_del(TriggerData *trigdata, bool is_no_action);
static Datum ri_restrict_upd(TriggerData *trigdata, bool is_no_action);
static void quoteOneName(char *buffer, const char *name);
static void quoteRelationName(char *buffer, Relation rel);
static void ri_GenerateQual(StringInfo buf,
......@@ -203,9 +205,6 @@ static bool ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
const RI_ConstraintInfo *riinfo, bool rel_is_pk);
static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
Datum oldvalue, Datum newvalue);
static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
HeapTuple old_row,
const RI_ConstraintInfo *riinfo);
static void ri_InitHashTables(void);
static SPIPlanPtr ri_FetchPreparedPlan(RI_QueryKey *key);
......@@ -240,9 +239,8 @@ static void ri_ReportViolation(const RI_ConstraintInfo *riinfo,
* ----------
*/
static Datum
RI_FKey_check(PG_FUNCTION_ARGS)
RI_FKey_check(TriggerData *trigdata)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
RI_ConstraintInfo riinfo;
Relation fk_rel;
Relation pk_rel;
......@@ -252,11 +250,6 @@ RI_FKey_check(PG_FUNCTION_ARGS)
SPIPlanPtr qplan;
int i;
/*
* Check that this is a valid trigger call on the right time and event.
*/
ri_CheckTrigger(fcinfo, "RI_FKey_check", RI_TRIGTYPE_INUP);
/*
* Get arguments.
*/
......@@ -510,7 +503,15 @@ RI_FKey_check(PG_FUNCTION_ARGS)
Datum
RI_FKey_check_ins(PG_FUNCTION_ARGS)
{
return RI_FKey_check(fcinfo);
/*
* Check that this is a valid trigger call on the right time and event.
*/
ri_CheckTrigger(fcinfo, "RI_FKey_check_ins", RI_TRIGTYPE_INSERT);
/*
* Share code with UPDATE case.
*/
return RI_FKey_check((TriggerData *) fcinfo->context);
}
......@@ -523,16 +524,27 @@ RI_FKey_check_ins(PG_FUNCTION_ARGS)
Datum
RI_FKey_check_upd(PG_FUNCTION_ARGS)
{
return RI_FKey_check(fcinfo);
/*
* Check that this is a valid trigger call on the right time and event.
*/
ri_CheckTrigger(fcinfo, "RI_FKey_check_upd", RI_TRIGTYPE_UPDATE);
/*
* Share code with INSERT case.
*/
return RI_FKey_check((TriggerData *) fcinfo->context);
}
/* ----------
* ri_Check_Pk_Match
*
* Check for matching value of old pk row in current state for
* noaction triggers. Returns false if no row was found and a fk row
* could potentially be referencing this row, true otherwise.
* Check to see if another PK row has been created that provides the same
* key values as the "old_row" that's been modified or deleted in our trigger
* event. Returns true if a match is found in the PK table.
*
* We assume the caller checked that the old_row contains no NULL key values,
* since otherwise a match is impossible.
* ----------
*/
static bool
......@@ -545,65 +557,15 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
int i;
bool result;
switch (ri_NullCheck(old_row, riinfo, true))
{
case RI_KEYS_ALL_NULL:
/*
* No check - nothing could have been referencing this row anyway.
*/
return true;
case RI_KEYS_SOME_NULL:
/*
* This is the only case that differs between the three kinds of
* MATCH.
*/
switch (riinfo->confmatchtype)
{
case FKCONSTR_MATCH_FULL:
case FKCONSTR_MATCH_SIMPLE:
/*
* MATCH SIMPLE/FULL - if ANY column is null, nothing
* could have been referencing this row.
*/
return true;
case FKCONSTR_MATCH_PARTIAL:
/*
* MATCH PARTIAL - all non-null columns must match. (not
* implemented, can be done by modifying the query below
* to only include non-null columns, or by writing a
* special version here)
*/
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
break;
default:
elog(ERROR, "unrecognized confmatchtype: %d",
riinfo->confmatchtype);
break;
}
case RI_KEYS_NONE_NULL:
/*
* Have a full qualified key - continue below for all three kinds
* of MATCH.
*/
break;
}
/* Only called for non-null rows */
Assert(ri_NullCheck(old_row, riinfo, true) == RI_KEYS_NONE_NULL);
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed");
/*
* Fetch or prepare a saved plan for the real check
* Fetch or prepare a saved plan for checking PK table with values coming
* from a PK row
*/
ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK_FROM_PK);
......@@ -675,196 +637,59 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
Datum
RI_FKey_noaction_del(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
RI_ConstraintInfo riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple old_row;
RI_QueryKey qkey;
SPIPlanPtr qplan;
int i;
/*
* Check that this is a valid trigger call on the right time and event.
*/
ri_CheckTrigger(fcinfo, "RI_FKey_noaction_del", RI_TRIGTYPE_DELETE);
/*
* Get arguments.
* Share code with RESTRICT case.
*/
ri_FetchConstraintInfo(&riinfo,
trigdata->tg_trigger, trigdata->tg_relation, true);
return ri_restrict_del((TriggerData *) fcinfo->context, true);
}
/* ----------
* RI_FKey_restrict_del -
*
* Restrict delete from PK table to rows unreferenced by foreign key.
*
* The SQL standard intends that this referential action occur exactly when
* the delete is performed, rather than after. This appears to be
* the only difference between "NO ACTION" and "RESTRICT". In Postgres
* we still implement this as an AFTER trigger, but it's non-deferrable.
* ----------
*/
Datum
RI_FKey_restrict_del(PG_FUNCTION_ARGS)
{
/*
* Nothing to do if no column names to compare given
* Check that this is a valid trigger call on the right time and event.
*/
if (riinfo.nkeys == 0)
return PointerGetDatum(NULL);
ri_CheckTrigger(fcinfo, "RI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
/*
* Get the relation descriptors of the FK and PK tables and the old tuple.
*
* fk_rel is opened in RowShareLock mode since that's what our eventual
* SELECT FOR SHARE will get on it.
* Share code with NO ACTION case.
*/
fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
pk_rel = trigdata->tg_relation;
old_row = trigdata->tg_trigtuple;
if (ri_Check_Pk_Match(pk_rel, fk_rel, old_row, &riinfo))
{
/*
* There's either another row, or no row could match this one. In
* either case, we don't need to do the check.
*/
heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
}
switch (riinfo.confmatchtype)
{
/* ----------
* SQL:2008 15.17 <Execution of referential actions>
* General rules 9) a) iv):
* MATCH SIMPLE/FULL
* ... ON DELETE NO ACTION
* ----------
*/
case FKCONSTR_MATCH_SIMPLE:
case FKCONSTR_MATCH_FULL:
switch (ri_NullCheck(old_row, &riinfo, true))
{
case RI_KEYS_ALL_NULL:
case RI_KEYS_SOME_NULL:
/*
* No check needed - there cannot be any reference to old
* key if it contains a NULL
*/
heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
case RI_KEYS_NONE_NULL:
/*
* Have a full qualified key - continue below
*/
break;
}
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed");
/*
* Fetch or prepare a saved plan for the restrict delete lookup
*/
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_NOACTION_DEL_CHECKREF);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
StringInfoData querybuf;
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
char paramname[16];
const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* The query string built is
* SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
* corresponding PK attributes.
* ----------
*/
initStringInfo(&querybuf);
quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
fkrelname);
querysep = "WHERE";
for (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]);
quoteOneName(attname,
RIAttName(fk_rel, riinfo.fk_attnums[i]));
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo.pf_eq_oprs[i],
attname, fk_type);
querysep = "AND";
queryoids[i] = pk_type;
}
appendStringInfo(&querybuf, " FOR SHARE OF x");
/* Prepare and save the plan */
qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
/*
* We have a plan now. Run it to check for existing references.
*/
ri_PerformCheck(&riinfo, &qkey, qplan,
fk_rel, pk_rel,
old_row, NULL,
true, /* must detect new rows */
SPI_OK_SELECT);
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
/*
* Handle MATCH PARTIAL restrict delete.
*/
case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
return PointerGetDatum(NULL);
default:
elog(ERROR, "unrecognized confmatchtype: %d",
riinfo.confmatchtype);
break;
}
/* Never reached */
return PointerGetDatum(NULL);
return ri_restrict_del((TriggerData *) fcinfo->context, false);
}
/* ----------
* RI_FKey_noaction_upd -
* ri_restrict_del -
*
* Give an error and roll back the current transaction if the
* update has resulted in a violation of the given referential
* integrity constraint.
* Common code for ON DELETE RESTRICT and ON DELETE NO ACTION.
* ----------
*/
Datum
RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
static Datum
ri_restrict_del(TriggerData *trigdata, bool is_no_action)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
RI_ConstraintInfo riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple new_row;
HeapTuple old_row;
RI_QueryKey qkey;
SPIPlanPtr qplan;
int i;
/*
* Check that this is a valid trigger call on the right time and event.
*/
ri_CheckTrigger(fcinfo, "RI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
/*
* Get arguments.
*/
......@@ -878,24 +703,22 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
return PointerGetDatum(NULL);
/*
* Get the relation descriptors of the FK and PK tables and the new and
* old tuple.
* Get the relation descriptors of the FK and PK tables and the old tuple.
*
* fk_rel is opened in RowShareLock mode since that's what our eventual
* SELECT FOR SHARE will get on it.
*/
fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
switch (riinfo.confmatchtype)
{
/* ----------
* SQL:2008 15.17 <Execution of referential actions>
* General rules 10) a) iv):
* General rules 9) a) iv):
* MATCH SIMPLE/FULL
* ... ON UPDATE NO ACTION
* ... ON DELETE RESTRICT
* ----------
*/
case FKCONSTR_MATCH_SIMPLE:
......@@ -921,31 +744,25 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
}
/*
* No need to check anything if old and new keys are equal
* If another PK row now exists providing the old key values,
* we should not do anything. However, this check should only be
* made in the NO ACTION case; in RESTRICT cases we don't wish to
* allow another row to be substituted.
*/
if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
if (is_no_action &&
ri_Check_Pk_Match(pk_rel, fk_rel, old_row, &riinfo))
{
heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
}
if (ri_Check_Pk_Match(pk_rel, fk_rel, old_row, &riinfo))
{
/*
* There's either another row, or no row could match this one.
* In either case, we don't need to do the check.
*/
heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
}
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed");
/*
* Fetch or prepare a saved plan for the noaction update lookup
* Fetch or prepare a saved plan for the restrict delete lookup
*/
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_NOACTION_UPD_CHECKREF);
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_RESTRICT_DEL_CHECKREF);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
......@@ -990,186 +807,24 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
&qkey, fk_rel, pk_rel, true);
}
/*
* We have a plan now. Run it to check for existing references.
*/
ri_PerformCheck(&riinfo, &qkey, qplan,
fk_rel, pk_rel,
old_row, NULL,
true, /* must detect new rows */
SPI_OK_SELECT);
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
/*
* Handle MATCH PARTIAL noaction update.
*/
case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
return PointerGetDatum(NULL);
default:
elog(ERROR, "unrecognized confmatchtype: %d",
riinfo.confmatchtype);
break;
}
/* Never reached */
return PointerGetDatum(NULL);
}
/* ----------
* RI_FKey_cascade_del -
*
* Cascaded delete foreign key references at delete event on PK table.
* ----------
*/
Datum
RI_FKey_cascade_del(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
RI_ConstraintInfo riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple old_row;
RI_QueryKey qkey;
SPIPlanPtr qplan;
int i;
/*
* Check that this is a valid trigger call on the right time and event.
*/
ri_CheckTrigger(fcinfo, "RI_FKey_cascade_del", RI_TRIGTYPE_DELETE);
/*
* Get arguments.
*/
ri_FetchConstraintInfo(&riinfo,
trigdata->tg_trigger, trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
if (riinfo.nkeys == 0)
return PointerGetDatum(NULL);
/*
* Get the relation descriptors of the FK and PK tables and the old tuple.
*
* fk_rel is opened in RowExclusiveLock mode since that's what our
* eventual DELETE will get on it.
*/
fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
old_row = trigdata->tg_trigtuple;
switch (riinfo.confmatchtype)
{
/* ----------
* SQL:2008 15.17 <Execution of referential actions>
* General rules 9) a) i):
* MATCH SIMPLE/FULL
* ... ON DELETE CASCADE
* ----------
*/
case FKCONSTR_MATCH_SIMPLE:
case FKCONSTR_MATCH_FULL:
switch (ri_NullCheck(old_row, &riinfo, true))
{
case RI_KEYS_ALL_NULL:
case RI_KEYS_SOME_NULL:
/*
* No check needed - there cannot be any reference to old
* key if it contains a NULL
*/
heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
case RI_KEYS_NONE_NULL:
/*
* Have a full qualified key - continue below
*/
break;
}
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed");
/*
* Fetch or prepare a saved plan for the cascaded delete
*/
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_CASCADE_DEL_DODELETE);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
StringInfoData querybuf;
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
char paramname[16];
const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* The query string built is
* DELETE FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
* corresponding PK attributes.
* ----------
*/
initStringInfo(&querybuf);
quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "DELETE FROM ONLY %s", fkrelname);
querysep = "WHERE";
for (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]);
quoteOneName(attname,
RIAttName(fk_rel, riinfo.fk_attnums[i]));
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo.pf_eq_oprs[i],
attname, fk_type);
querysep = "AND";
queryoids[i] = pk_type;
}
/* Prepare and save the plan */
qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
/*
* We have a plan now. Build up the arguments from the key values
* in the deleted PK tuple and delete the referencing rows
/*
* We have a plan now. Run it to check for existing references.
*/
ri_PerformCheck(&riinfo, &qkey, qplan,
fk_rel, pk_rel,
old_row, NULL,
true, /* must detect new rows */
SPI_OK_DELETE);
SPI_OK_SELECT);
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
heap_close(fk_rel, RowExclusiveLock);
heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
/*
* Handle MATCH PARTIAL cascaded delete.
* Handle MATCH PARTIAL restrict delete.
*/
case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
......@@ -1189,15 +844,61 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
/* ----------
* RI_FKey_cascade_upd -
* RI_FKey_noaction_upd -
*
* Cascaded update foreign key references at update event on PK table.
* Give an error and roll back the current transaction if the
* update has resulted in a violation of the given referential
* integrity constraint.
* ----------
*/
Datum
RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
{
/*
* Check that this is a valid trigger call on the right time and event.
*/
ri_CheckTrigger(fcinfo, "RI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
/*
* Share code with RESTRICT case.
*/
return ri_restrict_upd((TriggerData *) fcinfo->context, true);
}
/* ----------
* RI_FKey_restrict_upd -
*
* Restrict update of PK to rows unreferenced by foreign key.
*
* The SQL standard intends that this referential action occur exactly when
* the update is performed, rather than after. This appears to be
* the only difference between "NO ACTION" and "RESTRICT". In Postgres
* we still implement this as an AFTER trigger, but it's non-deferrable.
* ----------
*/
Datum
RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
{
/*
* Check that this is a valid trigger call on the right time and event.
*/
ri_CheckTrigger(fcinfo, "RI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
/*
* Share code with NO ACTION case.
*/
return ri_restrict_upd((TriggerData *) fcinfo->context, false);
}
/* ----------
* ri_restrict_upd -
*
* Common code for ON UPDATE RESTRICT and ON UPDATE NO ACTION.
* ----------
*/
static Datum
ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
RI_ConstraintInfo riinfo;
Relation fk_rel;
Relation pk_rel;
......@@ -1206,12 +907,6 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
RI_QueryKey qkey;
SPIPlanPtr qplan;
int i;
int j;
/*
* Check that this is a valid trigger call on the right time and event.
*/
ri_CheckTrigger(fcinfo, "RI_FKey_cascade_upd", RI_TRIGTYPE_UPDATE);
/*
* Get arguments.
......@@ -1229,10 +924,10 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
* Get the relation descriptors of the FK and PK tables and the new and
* old tuple.
*
* fk_rel is opened in RowExclusiveLock mode since that's what our
* eventual UPDATE will get on it.
* fk_rel is opened in RowShareLock mode since that's what our eventual
* SELECT FOR SHARE will get on it.
*/
fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
......@@ -1241,9 +936,9 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
{
/* ----------
* SQL:2008 15.17 <Execution of referential actions>
* General rules 10) a) i):
* General rules 10) a) iv):
* MATCH SIMPLE/FULL
* ... ON UPDATE CASCADE
* ... ON UPDATE RESTRICT
* ----------
*/
case FKCONSTR_MATCH_SIMPLE:
......@@ -1257,7 +952,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
* No check needed - there cannot be any reference to old
* key if it contains a NULL
*/
heap_close(fk_rel, RowExclusiveLock);
heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
case RI_KEYS_NONE_NULL:
......@@ -1269,11 +964,24 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
}
/*
* No need to do anything if old and new keys are equal
* No need to check anything if old and new keys are equal
*/
if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
{
heap_close(fk_rel, RowExclusiveLock);
heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
}
/*
* If another PK row now exists providing the old key values,
* we should not do anything. However, this check should only be
* made in the NO ACTION case; in RESTRICT cases we don't wish to
* allow another row to be substituted.
*/
if (is_no_action &&
ri_Check_Pk_Match(pk_rel, fk_rel, old_row, &riinfo))
{
heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
}
......@@ -1281,82 +989,71 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
elog(ERROR, "SPI_connect failed");
/*
* Fetch or prepare a saved plan for the cascaded update
* Fetch or prepare a saved plan for the restrict update lookup
*/
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_CASCADE_UPD_DOUPDATE);
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_RESTRICT_UPD_CHECKREF);
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];
const char *querysep;
const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS * 2];
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* The query string built is
* UPDATE ONLY <fktable> SET fkatt1 = $1 [, ...]
* WHERE $n = fkatt1 [AND ...]
* SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
* corresponding PK attributes. Note that we are assuming
* there is an assignment cast from the PK to the FK type;
* else the parser will fail.
* corresponding PK attributes.
* ----------
*/
initStringInfo(&querybuf);
initStringInfo(&qualbuf);
quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
querysep = "";
qualsep = "WHERE";
for (i = 0, j = riinfo.nkeys; i < riinfo.nkeys; i++, j++)
appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
fkrelname);
querysep = "WHERE";
for (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]);
quoteOneName(attname,
RIAttName(fk_rel, riinfo.fk_attnums[i]));
appendStringInfo(&querybuf,
"%s %s = $%d",
querysep, attname, i + 1);
sprintf(paramname, "$%d", j + 1);
ri_GenerateQual(&qualbuf, qualsep,
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo.pf_eq_oprs[i],
attname, fk_type);
querysep = ",";
qualsep = "AND";
querysep = "AND";
queryoids[i] = pk_type;
queryoids[j] = pk_type;
}
appendStringInfoString(&querybuf, qualbuf.data);
appendStringInfo(&querybuf, " FOR SHARE OF x");
/* Prepare and save the plan */
qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys * 2, queryoids,
qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
/*
* We have a plan now. Run it to update the existing references.
* We have a plan now. Run it to check for existing references.
*/
ri_PerformCheck(&riinfo, &qkey, qplan,
fk_rel, pk_rel,
old_row, new_row,
old_row, NULL,
true, /* must detect new rows */
SPI_OK_UPDATE);
SPI_OK_SELECT);
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
heap_close(fk_rel, RowExclusiveLock);
heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
/*
* Handle MATCH PARTIAL cascade update.
* Handle MATCH PARTIAL restrict update.
*/
case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
......@@ -1376,18 +1073,13 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
/* ----------
* RI_FKey_restrict_del -
*
* Restrict delete from PK table to rows unreferenced by foreign key.
* RI_FKey_cascade_del -
*
* The SQL standard intends that this referential action occur BEFORE
* the delete is performed, rather than after. This appears to be
* the only difference between "NO ACTION" and "RESTRICT". In Postgres
* we still implement this as an AFTER trigger, but it's non-deferrable.
* Cascaded delete foreign key references at delete event on PK table.
* ----------
*/
Datum
RI_FKey_restrict_del(PG_FUNCTION_ARGS)
RI_FKey_cascade_del(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
RI_ConstraintInfo riinfo;
......@@ -1401,7 +1093,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
/*
* Check that this is a valid trigger call on the right time and event.
*/
ri_CheckTrigger(fcinfo, "RI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
ri_CheckTrigger(fcinfo, "RI_FKey_cascade_del", RI_TRIGTYPE_DELETE);
/*
* Get arguments.
......@@ -1418,10 +1110,10 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
/*
* Get the relation descriptors of the FK and PK tables and the old tuple.
*
* fk_rel is opened in RowShareLock mode since that's what our eventual
* SELECT FOR SHARE will get on it.
* fk_rel is opened in RowExclusiveLock mode since that's what our
* eventual DELETE will get on it.
*/
fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
old_row = trigdata->tg_trigtuple;
......@@ -1429,9 +1121,9 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
{
/* ----------
* SQL:2008 15.17 <Execution of referential actions>
* General rules 9) a) iv):
* General rules 9) a) i):
* MATCH SIMPLE/FULL
* ... ON DELETE RESTRICT
* ... ON DELETE CASCADE
* ----------
*/
case FKCONSTR_MATCH_SIMPLE:
......@@ -1445,7 +1137,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
* No check needed - there cannot be any reference to old
* key if it contains a NULL
*/
heap_close(fk_rel, RowShareLock);
heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
case RI_KEYS_NONE_NULL:
......@@ -1460,9 +1152,9 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
elog(ERROR, "SPI_connect failed");
/*
* Fetch or prepare a saved plan for the restrict delete lookup
* Fetch or prepare a saved plan for the cascaded delete
*/
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_RESTRICT_DEL_CHECKREF);
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_CASCADE_DEL_DODELETE);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
......@@ -1475,15 +1167,14 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
/* ----------
* The query string built is
* SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
* DELETE FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
* corresponding PK attributes.
* ----------
*/
initStringInfo(&querybuf);
quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
fkrelname);
appendStringInfo(&querybuf, "DELETE FROM ONLY %s", fkrelname);
querysep = "WHERE";
for (i = 0; i < riinfo.nkeys; i++)
{
......@@ -1500,7 +1191,6 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
querysep = "AND";
queryoids[i] = pk_type;
}
appendStringInfo(&querybuf, " FOR SHARE OF x");
/* Prepare and save the plan */
qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
......@@ -1508,23 +1198,24 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
}
/*
* We have a plan now. Run it to check for existing references.
* We have a plan now. Build up the arguments from the key values
* in the deleted PK tuple and delete the referencing rows
*/
ri_PerformCheck(&riinfo, &qkey, qplan,
fk_rel, pk_rel,
old_row, NULL,
true, /* must detect new rows */
SPI_OK_SELECT);
SPI_OK_DELETE);
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
heap_close(fk_rel, RowShareLock);
heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
/*
* Handle MATCH PARTIAL restrict delete.
* Handle MATCH PARTIAL cascaded delete.
*/
case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
......@@ -1544,18 +1235,13 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
/* ----------
* RI_FKey_restrict_upd -
*
* Restrict update of PK to rows unreferenced by foreign key.
* RI_FKey_cascade_upd -
*
* The SQL standard intends that this referential action occur BEFORE
* the update is performed, rather than after. This appears to be
* the only difference between "NO ACTION" and "RESTRICT". In Postgres
* we still implement this as an AFTER trigger, but it's non-deferrable.
* Cascaded update foreign key references at update event on PK table.
* ----------
*/
Datum
RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
RI_ConstraintInfo riinfo;
......@@ -1566,11 +1252,12 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
RI_QueryKey qkey;
SPIPlanPtr qplan;
int i;
int j;
/*
* Check that this is a valid trigger call on the right time and event.
*/
ri_CheckTrigger(fcinfo, "RI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
ri_CheckTrigger(fcinfo, "RI_FKey_cascade_upd", RI_TRIGTYPE_UPDATE);
/*
* Get arguments.
......@@ -1588,10 +1275,10 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
* Get the relation descriptors of the FK and PK tables and the new and
* old tuple.
*
* fk_rel is opened in RowShareLock mode since that's what our eventual
* SELECT FOR SHARE will get on it.
* fk_rel is opened in RowExclusiveLock mode since that's what our
* eventual UPDATE will get on it.
*/
fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
......@@ -1600,9 +1287,9 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
{
/* ----------
* SQL:2008 15.17 <Execution of referential actions>
* General rules 10) a) iv):
* General rules 10) a) i):
* MATCH SIMPLE/FULL
* ... ON UPDATE RESTRICT
* ... ON UPDATE CASCADE
* ----------
*/
case FKCONSTR_MATCH_SIMPLE:
......@@ -1616,7 +1303,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
* No check needed - there cannot be any reference to old
* key if it contains a NULL
*/
heap_close(fk_rel, RowShareLock);
heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
case RI_KEYS_NONE_NULL:
......@@ -1628,11 +1315,11 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
}
/*
* No need to check anything if old and new keys are equal
* No need to do anything if old and new keys are equal
*/
if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
{
heap_close(fk_rel, RowShareLock);
heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
}
......@@ -1640,71 +1327,82 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
elog(ERROR, "SPI_connect failed");
/*
* Fetch or prepare a saved plan for the restrict update lookup
* Fetch or prepare a saved plan for the cascaded update
*/
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_RESTRICT_UPD_CHECKREF);
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_CASCADE_UPD_DOUPDATE);
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];
const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS * 2];
/* ----------
* The query string built is
* SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
* UPDATE ONLY <fktable> SET fkatt1 = $1 [, ...]
* WHERE $n = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
* corresponding PK attributes.
* corresponding PK attributes. Note that we are assuming
* there is an assignment cast from the PK to the FK type;
* else the parser will fail.
* ----------
*/
initStringInfo(&querybuf);
initStringInfo(&qualbuf);
quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
fkrelname);
querysep = "WHERE";
for (i = 0; i < riinfo.nkeys; i++)
appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
querysep = "";
qualsep = "WHERE";
for (i = 0, j = riinfo.nkeys; i < riinfo.nkeys; i++, j++)
{
Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
quoteOneName(attname,
RIAttName(fk_rel, riinfo.fk_attnums[i]));
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&querybuf, querysep,
appendStringInfo(&querybuf,
"%s %s = $%d",
querysep, attname, i + 1);
sprintf(paramname, "$%d", j + 1);
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo.pf_eq_oprs[i],
attname, fk_type);
querysep = "AND";
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
queryoids[j] = pk_type;
}
appendStringInfo(&querybuf, " FOR SHARE OF x");
appendStringInfoString(&querybuf, qualbuf.data);
/* Prepare and save the plan */
qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys * 2, queryoids,
&qkey, fk_rel, pk_rel, true);
}
/*
* We have a plan now. Run it to check for existing references.
* We have a plan now. Run it to update the existing references.
*/
ri_PerformCheck(&riinfo, &qkey, qplan,
fk_rel, pk_rel,
old_row, NULL,
old_row, new_row,
true, /* must detect new rows */
SPI_OK_SELECT);
SPI_OK_UPDATE);
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
heap_close(fk_rel, RowShareLock);
heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
/*
* Handle MATCH PARTIAL restrict update.
* Handle MATCH PARTIAL cascade update.
*/
case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
......@@ -3043,14 +2741,6 @@ ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname, int tgkind)
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("function \"%s\" must be fired for UPDATE", funcname)));
break;
case RI_TRIGTYPE_INUP:
if (!TRIGGER_FIRED_BY_INSERT(trigdata->tg_event) &&
!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("function \"%s\" must be fired for INSERT or UPDATE",
funcname)));
break;
case RI_TRIGTYPE_DELETE:
if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
ereport(ERROR,
......
......@@ -1355,3 +1355,29 @@ select * from defc;
delete from defp where f1 = 1; -- fail
ERROR: update or delete on table "defp" violates foreign key constraint "defc_f1_fkey" on table "defc"
DETAIL: Key (f1)=(1) is still referenced from table "defc".
--
-- Test the difference between NO ACTION and RESTRICT
--
create temp table pp (f1 int primary key);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "pp_pkey" for table "pp"
create temp table cc (f1 int references pp on update no action);
insert into pp values(12);
insert into pp values(11);
update pp set f1=f1+1;
insert into cc values(13);
update pp set f1=f1+1;
update pp set f1=f1+1; -- fail
ERROR: update or delete on table "pp" violates foreign key constraint "cc_f1_fkey" on table "cc"
DETAIL: Key (f1)=(13) is still referenced from table "cc".
drop table pp, cc;
create temp table pp (f1 int primary key);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "pp_pkey" for table "pp"
create temp table cc (f1 int references pp on update restrict);
insert into pp values(12);
insert into pp values(11);
update pp set f1=f1+1;
insert into cc values(13);
update pp set f1=f1+1; -- fail
ERROR: update or delete on table "pp" violates foreign key constraint "cc_f1_fkey" on table "cc"
DETAIL: Key (f1)=(13) is still referenced from table "cc".
drop table pp, cc;
......@@ -960,3 +960,25 @@ alter table defc alter column f1 set default 1;
delete from defp where f1 = 0;
select * from defc;
delete from defp where f1 = 1; -- fail
--
-- Test the difference between NO ACTION and RESTRICT
--
create temp table pp (f1 int primary key);
create temp table cc (f1 int references pp on update no action);
insert into pp values(12);
insert into pp values(11);
update pp set f1=f1+1;
insert into cc values(13);
update pp set f1=f1+1;
update pp set f1=f1+1; -- fail
drop table pp, cc;
create temp table pp (f1 int primary key);
create temp table cc (f1 int references pp on update restrict);
insert into pp values(12);
insert into pp values(11);
update pp set f1=f1+1;
insert into cc values(13);
update pp set f1=f1+1; -- fail
drop table pp, cc;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment