From: Ildar Musin <i.musin@postgrespro.ru>
To: pgsql-hackers@postgresql.org
Subject: Repetitive code in RI triggers
Date: Mon, 10 Apr 2017 18:55:04 +0300
Message-ID: <ca7064a7-6adc-6f22-ca47-8615ba9425a5@postgrespro.ru> (raw)
List-Unsubscribe: <mailto:majordomo@postgresql.org?body=unsub%20pgsql-hackers>
Hi all,
I was looking through the RI triggers code recently and noticed a few
almost identical functions, e.g. ri_restrict_upd() and
ri_restrict_del(). The following patch is an attempt to reduce some of
repetitive code. Yet there is still room for improvement.
Thanks,
--
Ildar Musin
i.musin@postgrespro.ru
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackersAttachments:ri_patch.diff (text/x-patch, 27.4K) downloaddiff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.cindex 37139f9..259c988 100644--- a/src/backend/utils/adt/ri_triggers.c+++ b/src/backend/utils/adt/ri_triggers.c@@ -199,8 +199,9 @@ static int ri_constraint_cache_valid_count = 0;
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 Datum ri_restrict(TriggerData *trigdata, bool is_no_action, bool is_update);+static Datum ri_setnull(TriggerData *trigdata, bool is_update);+static Datum ri_setdefault(FunctionCallInfo fcinfo, bool is_update);
static void quoteOneName(char *buffer, const char *name);
static void quoteRelationName(char *buffer, Relation rel);
static void ri_GenerateQual(StringInfo buf,
@@ -608,7 +609,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
/*
* Share code with RESTRICT case.
*/
- return ri_restrict_del((TriggerData *) fcinfo->context, true);+ return ri_restrict((TriggerData *) fcinfo->context, true, false);
}
/* ----------
@@ -633,175 +634,10 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
/*
* Share code with NO ACTION case.
*/
- return ri_restrict_del((TriggerData *) fcinfo->context, false);+ return ri_restrict((TriggerData *) fcinfo->context, false, 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 -
*
* Give an error and roll back the current transaction if the
@@ -820,7 +656,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
/*
* Share code with RESTRICT case.
*/
- return ri_restrict_upd((TriggerData *) fcinfo->context, true);+ return ri_restrict((TriggerData *) fcinfo->context, true, true);
}
/* ----------
@@ -845,26 +681,26 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
/*
* Share code with NO ACTION case.
*/
- return ri_restrict_upd((TriggerData *) fcinfo->context, false);+ return ri_restrict((TriggerData *) fcinfo->context, false, true);
}
/* ----------
- * 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
-ri_restrict_upd(TriggerData *trigdata, bool is_no_action)+ri_restrict(TriggerData *trigdata, bool is_no_action, bool is_update)
{
const RI_ConstraintInfo *riinfo;
Relation fk_rel;
Relation pk_rel;
- HeapTuple new_row;
HeapTuple old_row;
RI_QueryKey qkey;
SPIPlanPtr qplan;
- int i;+ int32 qtype;
/*
* Get arguments.
@@ -873,21 +709,22 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
trigdata->tg_relation, true);
/*
- * 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 KEY 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 9) a) iv):+ * MATCH SIMPLE/FULL+ * ... ON DELETE RESTRICT
* General rules 10) a) iv):
* MATCH SIMPLE/FULL
* ... ON UPDATE RESTRICT
@@ -915,14 +752,23 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
break;
}
- /*- * No need to check anything if old and new keys are equal- */- if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true))+ if (is_update)
{
- heap_close(fk_rel, RowShareLock);- return PointerGetDatum(NULL);+ HeapTuple new_row = trigdata->tg_newtuple;++ /*+ * 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, RowShareLock);+ return PointerGetDatum(NULL);+ }+ qtype = RI_PLAN_RESTRICT_UPD_CHECKREF;
}
+ else+ qtype = RI_PLAN_RESTRICT_DEL_CHECKREF;+
/*
* If another PK row now exists providing the old key values, we
@@ -941,9 +787,10 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
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 delete or update+ * lookup
*/
- ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_RESTRICT_UPD_CHECKREF);+ ri_BuildQueryKey(&qkey, riinfo, qtype);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
@@ -953,10 +800,12 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
char paramname[16];
const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
+ int i;
/* ----------
* 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
* corresponding PK attributes.
* ----------
@@ -1005,7 +854,7 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
return PointerGetDatum(NULL);
/*
- * Handle MATCH PARTIAL restrict update.+ * Handle MATCH PARTIAL restrict delete or update.
*/
case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
@@ -1370,21 +1219,56 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
Datum
RI_FKey_setnull_del(PG_FUNCTION_ARGS)
{
- TriggerData *trigdata = (TriggerData *) fcinfo->context;+ /*+ * Check that this is a valid trigger call on the right time and event.+ */+ ri_CheckTrigger(fcinfo, "RI_FKey_setnull_del", RI_TRIGTYPE_DELETE);++ /*+ * Share code with UPDATE case+ */+ return ri_setnull((TriggerData *) fcinfo->context, false);+}++/* ----------+ * RI_FKey_setnull_upd -+ *+ * Set foreign key references to NULL at update event on PK table.+ * ----------+ */+Datum+RI_FKey_setnull_upd(PG_FUNCTION_ARGS)+{+ /*+ * Check that this is a valid trigger call on the right time and event.+ */+ ri_CheckTrigger(fcinfo, "RI_FKey_setnull_upd", RI_TRIGTYPE_UPDATE);++ /*+ * Share code with DELETE case+ */+ return ri_setnull((TriggerData *) fcinfo->context, true);+}++/* ----------+ * ri_setnull -+ *+ * Common code for ON DELETE SET NULL and ON UPDATE SET NULL+ * ----------+ */+static Datum+ri_setnull(TriggerData *trigdata, bool is_update)+{
const RI_ConstraintInfo *riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple old_row;
RI_QueryKey qkey;
SPIPlanPtr qplan;
+ int32 qtype;
int i;
/*
- * Check that this is a valid trigger call on the right time and event.- */- ri_CheckTrigger(fcinfo, "RI_FKey_setnull_del", RI_TRIGTYPE_DELETE);-- /*
* Get arguments.
*/
riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
@@ -1407,6 +1291,9 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
* General rules 9) a) ii):
* MATCH SIMPLE/FULL
* ... ON DELETE SET NULL
+ * General rules 10) a) ii):+ * MATCH SIMPLE/FULL+ * ... ON UPDATE SET NULL
* ----------
*/
case FKCONSTR_MATCH_SIMPLE:
@@ -1431,13 +1318,30 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
break;
}
+ if (is_update)+ {+ HeapTuple new_row = trigdata->tg_newtuple;+ /*+ * 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, RowExclusiveLock);+ return PointerGetDatum(NULL);+ }+ qtype = RI_PLAN_SETNULL_UPD_DOUPDATE;+ }+ else+ qtype = RI_PLAN_SETNULL_DEL_DOUPDATE;+
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed");
/*
- * Fetch or prepare a saved plan for the set null delete operation+ * Fetch or prepare a saved plan for the set null delete or update+ * operation
*/
- ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETNULL_DEL_DOUPDATE);+ ri_BuildQueryKey(&qkey, riinfo, qtype);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
@@ -1491,7 +1395,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
}
/*
- * 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,
@@ -1507,7 +1411,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
return PointerGetDatum(NULL);
/*
- * Handle MATCH PARTIAL set null delete.+ * Handle MATCH PARTIAL set null delete or update.
*/
case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
@@ -1527,383 +1431,50 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
/* ----------
- * RI_FKey_setnull_upd -+ * RI_FKey_setdefault_del -
*
- * Set foreign key references to NULL at update event on PK table.+ * Set foreign key references to defaults at delete event on PK table.
* ----------
*/
Datum
-RI_FKey_setnull_upd(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 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_setnull_upd", RI_TRIGTYPE_UPDATE);+ ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_del", RI_TRIGTYPE_DELETE);
/*
- * Get arguments.+ * Share code with UPDATE case
*/
- 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 RowExclusiveLock mode since that's what our- * eventual UPDATE will get on it.- */- fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);- 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) ii):- * MATCH SIMPLE/FULL- * ... ON UPDATE 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;- }-- /*- * 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, RowExclusiveLock);- return PointerGetDatum(NULL);- }-- if (SPI_connect() != SPI_OK_CONNECT)- elog(ERROR, "SPI_connect failed");-- /*- * Fetch or prepare a saved plan for the set null update operation- */- ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETNULL_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;- 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);- initStringInfo(&qualbuf);- 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 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);-- return PointerGetDatum(NULL);-- /*- * Handle MATCH PARTIAL set null 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);+ return ri_setdefault(fcinfo, false);
}
--/* ----------- * RI_FKey_setdefault_del -- *- * Set foreign key references to defaults at delete event on PK table.- * ----------- */
Datum
-RI_FKey_setdefault_del(PG_FUNCTION_ARGS)+RI_FKey_setdefault_upd(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.
*/
- ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_del", RI_TRIGTYPE_DELETE);-- /*- * Get arguments.- */- riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,- trigdata->tg_relation, true);+ ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_upd", RI_TRIGTYPE_UPDATE);
/*
- * 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 UPDATE will get on it.+ * Share code with DELETE case
*/
- 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);- initStringInfo(&qualbuf);- 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- * 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);-- return PointerGetDatum(NULL);-- /*- * Handle MATCH PARTIAL set default 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_setdefault(fcinfo, true);
}
--/* ----------- * RI_FKey_setdefault_upd -- *- * Set foreign key references to defaults at update event on PK table.- * ----------- */-Datum-RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)+static Datum+ri_setdefault(FunctionCallInfo fcinfo, bool is_update)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
const RI_ConstraintInfo *riinfo;
Relation fk_rel;
Relation pk_rel;
- HeapTuple new_row;
HeapTuple old_row;
RI_QueryKey qkey;
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);+ int32 qtype;
/*
* Get arguments.
@@ -1919,13 +1490,15 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
*/
fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
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 9) a) iii):+ * MATCH SIMPLE/FULL+ * ... ON DELETE SET DEFAULT
* General rules 10) a) iii):
* MATCH SIMPLE/FULL
* ... ON UPDATE SET DEFAULT
@@ -1953,23 +1526,31 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
break;
}
- /*- * No need to do anything if old and new keys are equal- */- if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true))+ if (is_update)
{
- heap_close(fk_rel, RowExclusiveLock);- return PointerGetDatum(NULL);+ HeapTuple new_row = trigdata->tg_newtuple;++ /*+ * 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, RowExclusiveLock);+ return PointerGetDatum(NULL);+ }+ qtype = RI_PLAN_SETDEFAULT_DEL_DOUPDATE;
}
+ else+ qtype = RI_PLAN_SETDEFAULT_UPD_DOUPDATE;
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed");
/*
- * Fetch or prepare a saved plan for the set default update- * operation+ * Fetch or prepare a saved plan for the set default delete+ * or update operation
*/
- ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETDEFAULT_UPD_DOUPDATE);+ ri_BuildQueryKey(&qkey, riinfo, qtype);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
@@ -2038,23 +1619,28 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
heap_close(fk_rel, RowExclusiveLock);
/*
- * If we just updated the PK row whose key was equal to the FK- * 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 must change the FK key values, while SET NULL is- * certain to result in rows that satisfy the FK constraint.)+ * If we just deleted or updated the PK row whose key was equal+ * to the FK 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 in case+ * of DELETE operation or would change the FK key values in case+ * of UPDATE, while SET NULL is certain to result in rows that+ * satisfy the FK constraint.)
*/
- RI_FKey_noaction_upd(fcinfo);+ if (is_update)+ RI_FKey_noaction_upd(fcinfo);+ else+ RI_FKey_noaction_del(fcinfo);
return PointerGetDatum(NULL);
/*
- * Handle MATCH PARTIAL set default update.+ * Handle MATCH PARTIAL set default delete or update.
*/
case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: i.musin@postgrespro.ru
Subject: Re: Repetitive code in RI triggers
In-Reply-To: <ca7064a7-6adc-6f22-ca47-8615ba9425a5@postgrespro.ru>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox