From fe3db740025d80f70ceccb33b2bf3c085a2fca80 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 19 Jun 2012 14:31:54 -0400 Subject: [PATCH] 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. --- src/backend/utils/adt/ri_triggers.c | 626 ++++++---------------- src/test/regress/expected/foreign_key.out | 26 + src/test/regress/sql/foreign_key.sql | 22 + 3 files changed, 206 insertions(+), 468 deletions(-) diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 2fb060b477..f83f20be7a 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -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,7 +637,51 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, Datum RI_FKey_noaction_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_noaction_del", RI_TRIGTYPE_DELETE); + + /* + * Share code with RESTRICT case. + */ + 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) +{ + /* + * Check that this is a valid trigger call on the right time and event. + */ + ri_CheckTrigger(fcinfo, "RI_FKey_restrict_del", RI_TRIGTYPE_DELETE); + + /* + * Share code with NO ACTION case. + */ + return ri_restrict_del((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) +{ RI_ConstraintInfo riinfo; Relation fk_rel; Relation pk_rel; @@ -684,11 +690,6 @@ RI_FKey_noaction_del(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_noaction_del", RI_TRIGTYPE_DELETE); - /* * Get arguments. */ @@ -711,23 +712,13 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS) 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 * General rules 9) a) iv): * MATCH SIMPLE/FULL - * ... ON DELETE NO ACTION + * ... ON DELETE RESTRICT * ---------- */ case FKCONSTR_MATCH_SIMPLE: @@ -752,13 +743,26 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS) 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_NOACTION_DEL_CHECKREF); + ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_RESTRICT_DEL_CHECKREF); if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { @@ -850,7 +854,51 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS) Datum RI_FKey_noaction_upd(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_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) +{ RI_ConstraintInfo riinfo; Relation fk_rel; Relation pk_rel; @@ -860,11 +908,6 @@ RI_FKey_noaction_upd(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_noaction_upd", RI_TRIGTYPE_UPDATE); - /* * Get arguments. */ @@ -895,7 +938,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) * SQL:2008 15.17 * General rules 10) a) iv): * MATCH SIMPLE/FULL - * ... ON UPDATE NO ACTION + * ... ON UPDATE RESTRICT * ---------- */ case FKCONSTR_MATCH_SIMPLE: @@ -929,12 +972,15 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) return PointerGetDatum(NULL); } - if (ri_Check_Pk_Match(pk_rel, fk_rel, old_row, &riinfo)) + /* + * 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)) { - /* - * 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); } @@ -943,9 +989,9 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) 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 update lookup */ - ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_NOACTION_UPD_CHECKREF); + ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_RESTRICT_UPD_CHECKREF); if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { @@ -1007,7 +1053,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) return PointerGetDatum(NULL); /* - * Handle MATCH PARTIAL noaction update. + * Handle MATCH PARTIAL restrict update. */ case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, @@ -1375,354 +1421,6 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) } -/* ---------- - * RI_FKey_restrict_del - - * - * Restrict delete from PK table to rows unreferenced by foreign key. - * - * 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. - * ---------- - */ -Datum -RI_FKey_restrict_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_restrict_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 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; - old_row = trigdata->tg_trigtuple; - - switch (riinfo.confmatchtype) - { - /* ---------- - * SQL:2008 15.17 - * 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 (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 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); -} - - -/* ---------- - * RI_FKey_restrict_upd - - * - * Restrict update of PK to rows unreferenced by foreign key. - * - * 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. - * ---------- - */ -Datum -RI_FKey_restrict_upd(PG_FUNCTION_ARGS) -{ - 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_restrict_upd", RI_TRIGTYPE_UPDATE); - - /* - * 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 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 = 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 - * General rules 10) a) iv): - * MATCH SIMPLE/FULL - * ... ON UPDATE 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; - } - - /* - * 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); - } - - if (SPI_connect() != SPI_OK_CONNECT) - elog(ERROR, "SPI_connect failed"); - - /* - * Fetch or prepare a saved plan for the restrict update lookup - */ - ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_RESTRICT_UPD_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 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 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_setnull_del - * @@ -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, diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index a63a89f40b..502307bac2 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -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; diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index 43703d234e..377b36c226 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -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;