Commit 4797f9b5 authored by Tom Lane's avatar Tom Lane

Merge near-duplicate code in RI triggers.

Merge ri_restrict_del and ri_restrict_upd into one function ri_restrict.
Create a function ri_setnull that is the common implementation of
RI_FKey_setnull_del and RI_FKey_setnull_upd.  Likewise create a function
ri_setdefault that is the common implementation of RI_FKey_setdefault_del
and RI_FKey_setdefault_upd.  All of these pairs of functions were identical
except for needing to check for no-actual-key-change in the UPDATE cases;
the one extra if-test is a small price to pay for saving so much code.

Aside from removing about 400 lines of essentially duplicate code, this
allows us to recognize that we were uselessly caching two identical plans
whenever there were pairs of triggers using these duplicated functions
(which is likely very common).

Ildar Musin, reviewed by Ildus Kurbangaliev

Discussion: https://postgr.es/m/ca7064a7-6adc-6f22-ca47-8615ba9425a5@postgrespro.ru
parent d0aa965c
...@@ -81,12 +81,9 @@ ...@@ -81,12 +81,9 @@
/* these queries are executed against the FK (referencing) table: */ /* these queries are executed against the FK (referencing) table: */
#define RI_PLAN_CASCADE_DEL_DODELETE 3 #define RI_PLAN_CASCADE_DEL_DODELETE 3
#define RI_PLAN_CASCADE_UPD_DOUPDATE 4 #define RI_PLAN_CASCADE_UPD_DOUPDATE 4
#define RI_PLAN_RESTRICT_DEL_CHECKREF 5 #define RI_PLAN_RESTRICT_CHECKREF 5
#define RI_PLAN_RESTRICT_UPD_CHECKREF 6 #define RI_PLAN_SETNULL_DOUPDATE 6
#define RI_PLAN_SETNULL_DEL_DOUPDATE 7 #define RI_PLAN_SETDEFAULT_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_NAME_LEN (NAMEDATALEN*2+3)
#define MAX_QUOTED_REL_NAME_LEN (MAX_QUOTED_NAME_LEN*2) #define MAX_QUOTED_REL_NAME_LEN (MAX_QUOTED_NAME_LEN*2)
...@@ -196,8 +193,9 @@ static int ri_constraint_cache_valid_count = 0; ...@@ -196,8 +193,9 @@ static int ri_constraint_cache_valid_count = 0;
static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
HeapTuple old_row, HeapTuple old_row,
const RI_ConstraintInfo *riinfo); const RI_ConstraintInfo *riinfo);
static Datum ri_restrict_del(TriggerData *trigdata, bool is_no_action); static Datum ri_restrict(TriggerData *trigdata, bool is_no_action);
static Datum ri_restrict_upd(TriggerData *trigdata, bool is_no_action); static Datum ri_setnull(TriggerData *trigdata);
static Datum ri_setdefault(TriggerData *trigdata);
static void quoteOneName(char *buffer, const char *name); static void quoteOneName(char *buffer, const char *name);
static void quoteRelationName(char *buffer, Relation rel); static void quoteRelationName(char *buffer, Relation rel);
static void ri_GenerateQual(StringInfo buf, static void ri_GenerateQual(StringInfo buf,
...@@ -603,9 +601,9 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS) ...@@ -603,9 +601,9 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
ri_CheckTrigger(fcinfo, "RI_FKey_noaction_del", RI_TRIGTYPE_DELETE); ri_CheckTrigger(fcinfo, "RI_FKey_noaction_del", RI_TRIGTYPE_DELETE);
/* /*
* Share code with RESTRICT case. * Share code with RESTRICT/UPDATE cases.
*/ */
return ri_restrict_del((TriggerData *) fcinfo->context, true); return ri_restrict((TriggerData *) fcinfo->context, true);
} }
/* ---------- /* ----------
...@@ -628,176 +626,11 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS) ...@@ -628,176 +626,11 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
ri_CheckTrigger(fcinfo, "RI_FKey_restrict_del", RI_TRIGTYPE_DELETE); ri_CheckTrigger(fcinfo, "RI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
/* /*
* Share code with NO ACTION case. * Share code with NO ACTION/UPDATE cases.
*/ */
return ri_restrict_del((TriggerData *) fcinfo->context, false); return ri_restrict((TriggerData *) fcinfo->context, false);
} }
/* ----------
* ri_restrict_del -
*
* Common code for ON DELETE RESTRICT and ON DELETE NO ACTION.
* ----------
*/
static Datum
ri_restrict_del(TriggerData *trigdata, bool is_no_action)
{
const RI_ConstraintInfo *riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple old_row;
RI_QueryKey qkey;
SPIPlanPtr qplan;
int i;
/*
* Get arguments.
*/
riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
trigdata->tg_relation, true);
/*
* 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 KEY SHARE will get on it.
*/
fk_rel = heap_open(riinfo->fk_relid, RowShareLock);
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) iv):
* MATCH SIMPLE/FULL
* ... ON DELETE RESTRICT
* ----------
*/
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 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);
}
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_RESTRICT_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> x WHERE $1 = fkatt1 [AND ...]
* FOR KEY SHARE OF x
* 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;
}
appendStringInfoString(&querybuf, " FOR KEY 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);
}
/* ---------- /* ----------
* RI_FKey_noaction_upd - * RI_FKey_noaction_upd -
* *
...@@ -815,9 +648,9 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) ...@@ -815,9 +648,9 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
ri_CheckTrigger(fcinfo, "RI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE); ri_CheckTrigger(fcinfo, "RI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
/* /*
* Share code with RESTRICT case. * Share code with RESTRICT/DELETE cases.
*/ */
return ri_restrict_upd((TriggerData *) fcinfo->context, true); return ri_restrict((TriggerData *) fcinfo->context, true);
} }
/* ---------- /* ----------
...@@ -840,28 +673,27 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS) ...@@ -840,28 +673,27 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
ri_CheckTrigger(fcinfo, "RI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE); ri_CheckTrigger(fcinfo, "RI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
/* /*
* Share code with NO ACTION case. * Share code with NO ACTION/DELETE cases.
*/ */
return ri_restrict_upd((TriggerData *) fcinfo->context, false); return ri_restrict((TriggerData *) fcinfo->context, false);
} }
/* ---------- /* ----------
* ri_restrict_upd - * ri_restrict -
* *
* Common code for ON UPDATE RESTRICT and ON UPDATE NO ACTION. * Common code for ON DELETE RESTRICT, ON DELETE NO ACTION,
* ON UPDATE RESTRICT, and ON UPDATE NO ACTION.
* ---------- * ----------
*/ */
static Datum static Datum
ri_restrict_upd(TriggerData *trigdata, bool is_no_action) ri_restrict(TriggerData *trigdata, bool is_no_action)
{ {
const RI_ConstraintInfo *riinfo; const RI_ConstraintInfo *riinfo;
Relation fk_rel; Relation fk_rel;
Relation pk_rel; Relation pk_rel;
HeapTuple new_row;
HeapTuple old_row; HeapTuple old_row;
RI_QueryKey qkey; RI_QueryKey qkey;
SPIPlanPtr qplan; SPIPlanPtr qplan;
int i;
/* /*
* Get arguments. * Get arguments.
...@@ -870,21 +702,22 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action) ...@@ -870,21 +702,22 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
trigdata->tg_relation, true); trigdata->tg_relation, true);
/* /*
* Get the relation descriptors of the FK and PK tables and the new and * Get the relation descriptors of the FK and PK tables and the old tuple.
* old tuple.
* *
* fk_rel is opened in RowShareLock mode since that's what our eventual * fk_rel is opened in RowShareLock mode since that's what our eventual
* SELECT FOR KEY SHARE will get on it. * SELECT FOR KEY SHARE will get on it.
*/ */
fk_rel = heap_open(riinfo->fk_relid, RowShareLock); fk_rel = heap_open(riinfo->fk_relid, RowShareLock);
pk_rel = trigdata->tg_relation; pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple; old_row = trigdata->tg_trigtuple;
switch (riinfo->confmatchtype) switch (riinfo->confmatchtype)
{ {
/* ---------- /* ----------
* SQL:2008 15.17 <Execution of referential actions> * SQL:2008 15.17 <Execution of referential actions>
* General rules 9) a) iv):
* MATCH SIMPLE/FULL
* ... ON DELETE RESTRICT
* General rules 10) a) iv): * General rules 10) a) iv):
* MATCH SIMPLE/FULL * MATCH SIMPLE/FULL
* ... ON UPDATE RESTRICT * ... ON UPDATE RESTRICT
...@@ -913,13 +746,18 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action) ...@@ -913,13 +746,18 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
} }
/* /*
* No need to check anything if old and new keys are equal * In UPDATE, no need to do anything if old and new keys are equal
*/ */
if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
{
HeapTuple new_row = trigdata->tg_newtuple;
if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true)) if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true))
{ {
heap_close(fk_rel, RowShareLock); heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL); return PointerGetDatum(NULL);
} }
}
/* /*
* If another PK row now exists providing the old key values, we * If another PK row now exists providing the old key values, we
...@@ -938,9 +776,10 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action) ...@@ -938,9 +776,10 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
elog(ERROR, "SPI_connect failed"); elog(ERROR, "SPI_connect failed");
/* /*
* Fetch or prepare a saved plan for the restrict update lookup * 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_UPD_CHECKREF); ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_RESTRICT_CHECKREF);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{ {
...@@ -950,10 +789,12 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action) ...@@ -950,10 +789,12 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
char paramname[16]; char paramname[16];
const char *querysep; const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS]; Oid queryoids[RI_MAX_NUMKEYS];
int i;
/* ---------- /* ----------
* The query string built is * The query string built is
* SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...] * SELECT 1 FROM ONLY <fktable> x WHERE $1 = fkatt1 [AND ...]
* FOR KEY SHARE OF x
* The type id's for the $ parameters are those of the * The type id's for the $ parameters are those of the
* corresponding PK attributes. * corresponding PK attributes.
* ---------- * ----------
...@@ -1002,7 +843,7 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action) ...@@ -1002,7 +843,7 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
return PointerGetDatum(NULL); return PointerGetDatum(NULL);
/* /*
* Handle MATCH PARTIAL restrict update. * Handle MATCH PARTIAL restrict delete or update.
*/ */
case FKCONSTR_MATCH_PARTIAL: case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR, ereport(ERROR,
...@@ -1367,186 +1208,54 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) ...@@ -1367,186 +1208,54 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
Datum Datum
RI_FKey_setnull_del(PG_FUNCTION_ARGS) RI_FKey_setnull_del(PG_FUNCTION_ARGS)
{ {
TriggerData *trigdata = (TriggerData *) fcinfo->context;
const 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. * Check that this is a valid trigger call on the right time and event.
*/ */
ri_CheckTrigger(fcinfo, "RI_FKey_setnull_del", RI_TRIGTYPE_DELETE); ri_CheckTrigger(fcinfo, "RI_FKey_setnull_del", RI_TRIGTYPE_DELETE);
/* /*
* Get arguments. * Share code with UPDATE case
*/ */
riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, return ri_setnull((TriggerData *) fcinfo->context);
trigdata->tg_relation, true); }
/* /* ----------
* Get the relation descriptors of the FK and PK tables and the old tuple. * RI_FKey_setnull_upd -
* *
* fk_rel is opened in RowExclusiveLock mode since that's what our * Set foreign key references to NULL at update event on PK table.
* eventual UPDATE 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) ii):
* MATCH SIMPLE/FULL
* ... ON DELETE SET NULL
* ----------
*/
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 set null delete operation
*/
ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETNULL_DEL_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;
const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* The query string built is
* UPDATE ONLY <fktable> SET fkatt1 = NULL [, ...]
* WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
* corresponding PK attributes.
* ---------- * ----------
*/ */
initStringInfo(&querybuf); Datum
initStringInfo(&qualbuf); RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
quoteRelationName(fkrelname, fk_rel); {
appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
querysep = "";
qualsep = "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 = NULL",
querysep, attname);
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
}
appendStringInfoString(&querybuf, qualbuf.data);
/* 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. * Check that this is a valid trigger call on the right time and event.
*/ */
ri_PerformCheck(riinfo, &qkey, qplan, ri_CheckTrigger(fcinfo, "RI_FKey_setnull_upd", RI_TRIGTYPE_UPDATE);
fk_rel, pk_rel,
old_row, NULL,
true, /* must detect new rows */
SPI_OK_UPDATE);
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
/* /*
* Handle MATCH PARTIAL set null delete. * Share code with DELETE case
*/ */
case FKCONSTR_MATCH_PARTIAL: return ri_setnull((TriggerData *) fcinfo->context);
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_setnull_upd - * ri_setnull -
* *
* Set foreign key references to NULL at update event on PK table. * Common code for ON DELETE SET NULL and ON UPDATE SET NULL
* ---------- * ----------
*/ */
Datum static Datum
RI_FKey_setnull_upd(PG_FUNCTION_ARGS) ri_setnull(TriggerData *trigdata)
{ {
TriggerData *trigdata = (TriggerData *) fcinfo->context;
const RI_ConstraintInfo *riinfo; const RI_ConstraintInfo *riinfo;
Relation fk_rel; Relation fk_rel;
Relation pk_rel; Relation pk_rel;
HeapTuple new_row;
HeapTuple old_row; HeapTuple old_row;
RI_QueryKey qkey; RI_QueryKey qkey;
SPIPlanPtr qplan; SPIPlanPtr qplan;
int i; int i;
/*
* Check that this is a valid trigger call on the right time and event.
*/
ri_CheckTrigger(fcinfo, "RI_FKey_setnull_upd", RI_TRIGTYPE_UPDATE);
/* /*
* Get arguments. * Get arguments.
*/ */
...@@ -1561,13 +1270,15 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) ...@@ -1561,13 +1270,15 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
*/ */
fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock); fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
pk_rel = trigdata->tg_relation; pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple; old_row = trigdata->tg_trigtuple;
switch (riinfo->confmatchtype) switch (riinfo->confmatchtype)
{ {
/* ---------- /* ----------
* SQL:2008 15.17 <Execution of referential actions> * SQL:2008 15.17 <Execution of referential actions>
* General rules 9) a) ii):
* MATCH SIMPLE/FULL
* ... ON DELETE SET NULL
* General rules 10) a) ii): * General rules 10) a) ii):
* MATCH SIMPLE/FULL * MATCH SIMPLE/FULL
* ... ON UPDATE SET NULL * ... ON UPDATE SET NULL
...@@ -1596,21 +1307,27 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) ...@@ -1596,21 +1307,27 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
} }
/* /*
* No need to do anything if old and new keys are equal * In UPDATE, no need to do anything if old and new keys are equal
*/ */
if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
{
HeapTuple new_row = trigdata->tg_newtuple;
if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true)) if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true))
{ {
heap_close(fk_rel, RowExclusiveLock); heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL); return PointerGetDatum(NULL);
} }
}
if (SPI_connect() != SPI_OK_CONNECT) if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed"); elog(ERROR, "SPI_connect failed");
/* /*
* Fetch or prepare a saved plan for the set null update operation * Fetch or prepare a saved plan for the set null operation (it's
* the same query for delete and update cases)
*/ */
ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETNULL_UPD_DOUPDATE); ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETNULL_DOUPDATE);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{ {
...@@ -1680,7 +1397,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) ...@@ -1680,7 +1397,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
return PointerGetDatum(NULL); return PointerGetDatum(NULL);
/* /*
* Handle MATCH PARTIAL set null update. * Handle MATCH PARTIAL set null delete or update.
*/ */
case FKCONSTR_MATCH_PARTIAL: case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR, ereport(ERROR,
...@@ -1708,200 +1425,53 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) ...@@ -1708,200 +1425,53 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
Datum Datum
RI_FKey_setdefault_del(PG_FUNCTION_ARGS) RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
{ {
TriggerData *trigdata = (TriggerData *) fcinfo->context;
const RI_ConstraintInfo *riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple old_row;
RI_QueryKey qkey;
SPIPlanPtr qplan;
/* /*
* Check that this is a valid trigger call on the right time and event. * Check that this is a valid trigger call on the right time and event.
*/ */
ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_del", RI_TRIGTYPE_DELETE); ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_del", RI_TRIGTYPE_DELETE);
/* /*
* Get arguments. * Share code with UPDATE case
*/ */
riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, return ri_setdefault((TriggerData *) fcinfo->context);
trigdata->tg_relation, true); }
/* /* ----------
* Get the relation descriptors of the FK and PK tables and the old tuple. * RI_FKey_setdefault_upd -
* *
* fk_rel is opened in RowExclusiveLock mode since that's what our * Set foreign key references to defaults at update event on PK table.
* eventual UPDATE 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) iii):
* MATCH SIMPLE/FULL
* ... ON DELETE SET DEFAULT
* ----------
*/
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 set default delete
* operation
*/
ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETDEFAULT_DEL_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;
const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS];
int i;
/* ----------
* The query string built is
* UPDATE ONLY <fktable> SET fkatt1 = DEFAULT [, ...]
* WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
* corresponding PK attributes.
* ---------- * ----------
*/ */
initStringInfo(&querybuf); Datum
initStringInfo(&qualbuf); RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
quoteRelationName(fkrelname, fk_rel); {
appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
querysep = "";
qualsep = "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 = DEFAULT",
querysep, attname);
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
}
appendStringInfoString(&querybuf, qualbuf.data);
/* 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 update the existing references.
*/
ri_PerformCheck(riinfo, &qkey, qplan,
fk_rel, pk_rel,
old_row, NULL,
true, /* must detect new rows */
SPI_OK_UPDATE);
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
heap_close(fk_rel, RowExclusiveLock);
/* /*
* If we just deleted the PK row whose key was equal to the FK * Check that this is a valid trigger call on the right time and event.
* columns' default values, and a referencing row exists in the FK
* table, we would have updated that row to the same values it
* already had --- and RI_FKey_fk_upd_check_required would hence
* believe no check is necessary. So we need to do another lookup
* now and in case a reference still exists, abort the operation.
* That is already implemented in the NO ACTION trigger, so just
* run it. (This recheck is only needed in the SET DEFAULT case,
* since CASCADE would remove such rows, while SET NULL is certain
* to result in rows that satisfy the FK constraint.)
*/ */
RI_FKey_noaction_del(fcinfo); ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_upd", RI_TRIGTYPE_UPDATE);
return PointerGetDatum(NULL);
/* /*
* Handle MATCH PARTIAL set default delete. * Share code with DELETE case
*/ */
case FKCONSTR_MATCH_PARTIAL: return ri_setdefault((TriggerData *) fcinfo->context);
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_setdefault_upd - * ri_setdefault -
* *
* Set foreign key references to defaults at update event on PK table. * Common code for ON DELETE SET DEFAULT and ON UPDATE SET DEFAULT
* ---------- * ----------
*/ */
Datum static Datum
RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) ri_setdefault(TriggerData *trigdata)
{ {
TriggerData *trigdata = (TriggerData *) fcinfo->context;
const RI_ConstraintInfo *riinfo; const RI_ConstraintInfo *riinfo;
Relation fk_rel; Relation fk_rel;
Relation pk_rel; Relation pk_rel;
HeapTuple new_row;
HeapTuple old_row; HeapTuple old_row;
RI_QueryKey qkey; RI_QueryKey qkey;
SPIPlanPtr qplan; SPIPlanPtr qplan;
/*
* Check that this is a valid trigger call on the right time and event.
*/
ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_upd", RI_TRIGTYPE_UPDATE);
/* /*
* Get arguments. * Get arguments.
*/ */
...@@ -1916,13 +1486,15 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) ...@@ -1916,13 +1486,15 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
*/ */
fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock); fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
pk_rel = trigdata->tg_relation; pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple; old_row = trigdata->tg_trigtuple;
switch (riinfo->confmatchtype) switch (riinfo->confmatchtype)
{ {
/* ---------- /* ----------
* SQL:2008 15.17 <Execution of referential actions> * SQL:2008 15.17 <Execution of referential actions>
* General rules 9) a) iii):
* MATCH SIMPLE/FULL
* ... ON DELETE SET DEFAULT
* General rules 10) a) iii): * General rules 10) a) iii):
* MATCH SIMPLE/FULL * MATCH SIMPLE/FULL
* ... ON UPDATE SET DEFAULT * ... ON UPDATE SET DEFAULT
...@@ -1951,22 +1523,27 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) ...@@ -1951,22 +1523,27 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
} }
/* /*
* No need to do anything if old and new keys are equal * In UPDATE, no need to do anything if old and new keys are equal
*/ */
if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
{
HeapTuple new_row = trigdata->tg_newtuple;
if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true)) if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true))
{ {
heap_close(fk_rel, RowExclusiveLock); heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL); return PointerGetDatum(NULL);
} }
}
if (SPI_connect() != SPI_OK_CONNECT) if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed"); elog(ERROR, "SPI_connect failed");
/* /*
* Fetch or prepare a saved plan for the set default update * Fetch or prepare a saved plan for the set default operation
* operation * (it's the same query for delete and update cases)
*/ */
ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETDEFAULT_UPD_DOUPDATE); ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETDEFAULT_DOUPDATE);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{ {
...@@ -2035,23 +1612,23 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) ...@@ -2035,23 +1612,23 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
heap_close(fk_rel, RowExclusiveLock); heap_close(fk_rel, RowExclusiveLock);
/* /*
* If we just updated the PK row whose key was equal to the FK * If we just deleted or updated the PK row whose key was equal to
* columns' default values, and a referencing row exists in the FK * the FK columns' default values, and a referencing row exists in
* table, we would have updated that row to the same values it * the FK table, we would have updated that row to the same values
* already had --- and RI_FKey_fk_upd_check_required would hence * it already had --- and RI_FKey_fk_upd_check_required would
* believe no check is necessary. So we need to do another lookup * hence believe no check is necessary. So we need to do another
* now and in case a reference still exists, abort the operation. * lookup now and in case a reference still exists, abort the
* That is already implemented in the NO ACTION trigger, so just * operation. That is already implemented in the NO ACTION
* run it. (This recheck is only needed in the SET DEFAULT case, * trigger, so just run it. (This recheck is only needed in the
* since CASCADE must change the FK key values, while SET NULL is * SET DEFAULT case, since CASCADE would remove such rows in case
* certain to result in rows that satisfy the FK constraint.) * of a DELETE operation or would change the FK key values in case
* of an UPDATE, while SET NULL is certain to result in rows that
* satisfy the FK constraint.)
*/ */
RI_FKey_noaction_upd(fcinfo); return ri_restrict(trigdata, true);
return PointerGetDatum(NULL);
/* /*
* Handle MATCH PARTIAL set default update. * Handle MATCH PARTIAL set default delete or update.
*/ */
case FKCONSTR_MATCH_PARTIAL: case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR, ereport(ERROR,
......
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