diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2007-02-14 01:58:58 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2007-02-14 01:58:58 +0000 |
commit | 7bddca3450cc8631e5bf05e43988cf10ae32230e (patch) | |
tree | 0bdc9972eb5c687d2dd6d266295de3d317dee229 /src/backend/utils/adt/ri_triggers.c | |
parent | 65e2f55031802dd1ee8f22d880e49b94b3534483 (diff) | |
download | postgresql-7bddca3450cc8631e5bf05e43988cf10ae32230e.tar.gz postgresql-7bddca3450cc8631e5bf05e43988cf10ae32230e.zip |
Fix up foreign-key mechanism so that there is a sound semantic basis for the
equality checks it applies, instead of a random dependence on whatever
operators might be named "=". The equality operators will now be selected
from the opfamily of the unique index that the FK constraint depends on to
enforce uniqueness of the referenced columns; therefore they are certain to be
consistent with that index's notion of equality. Among other things this
should fix the problem noted awhile back that pg_dump may fail for foreign-key
constraints on user-defined types when the required operators aren't in the
search path. This also means that the former warning condition about "foreign
key constraint will require costly sequential scans" is gone: if the
comparison condition isn't indexable then we'll reject the constraint
entirely. All per past discussions.
Along the way, make the RI triggers look into pg_constraint for their
information, instead of using pg_trigger.tgargs; and get rid of the always
error-prone fixed-size string buffers in ri_triggers.c in favor of building up
the RI queries in StringInfo buffers.
initdb forced due to columns added to pg_constraint and pg_trigger.
Diffstat (limited to 'src/backend/utils/adt/ri_triggers.c')
-rw-r--r-- | src/backend/utils/adt/ri_triggers.c | 1772 |
1 files changed, 1013 insertions, 759 deletions
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index c21d827d0c7..4ba2bb7a98f 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -17,7 +17,7 @@ * * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.90 2007/01/05 22:19:42 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.91 2007/02/14 01:58:57 tgl Exp $ * * ---------- */ @@ -32,13 +32,17 @@ #include "postgres.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_operator.h" #include "commands/trigger.h" #include "executor/spi_priv.h" +#include "parser/parse_coerce.h" +#include "parser/parse_relation.h" +#include "miscadmin.h" #include "utils/acl.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" -#include "utils/typcache.h" -#include "miscadmin.h" +#include "utils/memutils.h" /* ---------- @@ -46,11 +50,9 @@ * ---------- */ -#define RI_INIT_QUERYHASHSIZE 128 +#define RI_MAX_NUMKEYS INDEX_MAX_KEYS -#define RI_MATCH_TYPE_UNSPECIFIED 0 -#define RI_MATCH_TYPE_FULL 1 -#define RI_MATCH_TYPE_PARTIAL 2 +#define RI_INIT_QUERYHASHSIZE 128 #define RI_KEYS_ALL_NULL 0 #define RI_KEYS_SOME_NULL 1 @@ -72,21 +74,51 @@ #define MAX_QUOTED_NAME_LEN (NAMEDATALEN*2+3) #define MAX_QUOTED_REL_NAME_LEN (MAX_QUOTED_NAME_LEN*2) +#define RIAttName(rel, attnum) NameStr(*attnumAttName(rel, attnum)) +#define RIAttType(rel, attnum) SPI_gettypeid(RelationGetDescr(rel), attnum) + #define RI_TRIGTYPE_INSERT 1 #define RI_TRIGTYPE_UPDATE 2 #define RI_TRIGTYPE_INUP 3 #define RI_TRIGTYPE_DELETE 4 +#define RI_KEYPAIR_FK_IDX 0 +#define RI_KEYPAIR_PK_IDX 1 + + +/* ---------- + * RI_ConstraintInfo + * + * Information extracted from an FK pg_constraint entry. + * ---------- + */ +typedef struct RI_ConstraintInfo +{ + Oid constraint_id; /* OID of pg_constraint entry */ + NameData conname; /* name of the FK constraint */ + Oid pk_relid; /* referenced relation */ + Oid fk_relid; /* referencing relation */ + char confupdtype; /* foreign key's ON UPDATE action */ + char confdeltype; /* foreign key's ON DELETE action */ + char confmatchtype; /* foreign key's match type */ + int nkeys; /* number of key columns */ + int16 pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */ + int16 fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */ + Oid pf_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = FK) */ + Oid pp_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = PK) */ + Oid ff_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (FK = FK) */ +} RI_ConstraintInfo; + /* ---------- * RI_QueryKey * - * The key identifying a prepared SPI plan in our private hashtable + * The key identifying a prepared SPI plan in our query hashtable * ---------- */ typedef struct RI_QueryKey { - int32 constr_type; + char constr_type; Oid constr_id; int32 constr_queryno; Oid fk_relid; @@ -108,10 +140,37 @@ typedef struct RI_QueryHashEntry /* ---------- + * RI_CompareKey + * + * The key identifying an entry showing how to compare two values + * ---------- + */ +typedef struct RI_CompareKey +{ + Oid eq_opr; /* the equality operator to apply */ + Oid typeid; /* the data type to apply it to */ +} RI_CompareKey; + + +/* ---------- + * RI_CompareHashEntry + * ---------- + */ +typedef struct RI_CompareHashEntry +{ + RI_CompareKey key; + bool valid; /* successfully initialized? */ + FmgrInfo eq_opr_finfo; /* call info for equality fn */ + FmgrInfo cast_func_finfo; /* in case we must coerce input */ +} RI_CompareHashEntry; + + +/* ---------- * Local data * ---------- */ static HTAB *ri_query_cache = NULL; +static HTAB *ri_compare_cache = NULL; /* ---------- @@ -120,35 +179,41 @@ static HTAB *ri_query_cache = NULL; */ static void quoteOneName(char *buffer, const char *name); static void quoteRelationName(char *buffer, Relation rel); -static int ri_DetermineMatchType(char *str); +static void ri_GenerateQual(StringInfo buf, + const char *sep, + const char *leftop, Oid leftoptype, + Oid opoid, + const char *rightop, Oid rightoptype); static int ri_NullCheck(Relation rel, HeapTuple tup, RI_QueryKey *key, int pairidx); -static void ri_BuildQueryKeyFull(RI_QueryKey *key, Oid constr_id, - int32 constr_queryno, - Relation fk_rel, Relation pk_rel, - int argc, char **argv); -static void ri_BuildQueryKeyPkCheck(RI_QueryKey *key, Oid constr_id, - int32 constr_queryno, - Relation pk_rel, - int argc, char **argv); +static void ri_BuildQueryKeyFull(RI_QueryKey *key, + const RI_ConstraintInfo *riinfo, + int32 constr_queryno); +static void ri_BuildQueryKeyPkCheck(RI_QueryKey *key, + const RI_ConstraintInfo *riinfo, + int32 constr_queryno); static bool ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup, - RI_QueryKey *key, int pairidx); + const RI_ConstraintInfo *riinfo, bool rel_is_pk); static bool ri_AllKeysUnequal(Relation rel, HeapTuple oldtup, HeapTuple newtup, - RI_QueryKey *key, int pairidx); -static bool ri_OneKeyEqual(Relation rel, int column, HeapTuple oldtup, - HeapTuple newtup, RI_QueryKey *key, int pairidx); -static bool ri_AttributesEqual(Oid typeid, Datum oldvalue, Datum newvalue); + const RI_ConstraintInfo *riinfo, bool rel_is_pk); +static bool ri_OneKeyEqual(Relation rel, int column, + 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, - Oid tgoid, int match_type, - int tgnargs, char **tgargs); + const RI_ConstraintInfo *riinfo); static void ri_InitHashTables(void); static void *ri_FetchPreparedPlan(RI_QueryKey *key); static void ri_HashPreparedPlan(RI_QueryKey *key, void *plan); +static RI_CompareHashEntry *ri_HashCompareOp(Oid eq_opr, Oid typeid); static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname, int tgkind); +static void ri_FetchConstraintInfo(RI_ConstraintInfo *riinfo, + Trigger *trigger, Relation trig_rel, bool rel_is_pk); static void *ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes, RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel, bool cache_plan); @@ -176,8 +241,7 @@ static Datum RI_FKey_check(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; - int tgnargs; - char **tgargs; + RI_ConstraintInfo riinfo; Relation fk_rel; Relation pk_rel; HeapTuple new_row; @@ -186,7 +250,6 @@ RI_FKey_check(PG_FUNCTION_ARGS) RI_QueryKey qkey; void *qplan; int i; - int match_type; /* * Check that this is a valid trigger call on the right time and event. @@ -196,8 +259,9 @@ RI_FKey_check(PG_FUNCTION_ARGS) /* * Get arguments. */ - tgnargs = trigdata->tg_trigger->tgnargs; - tgargs = trigdata->tg_trigger->tgargs; + ri_FetchConstraintInfo(&riinfo, + trigdata->tg_trigger, trigdata->tg_relation, false); + if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) { old_row = trigdata->tg_trigtuple; @@ -237,7 +301,7 @@ RI_FKey_check(PG_FUNCTION_ARGS) * SELECT FOR SHARE will get on it. */ fk_rel = trigdata->tg_relation; - pk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock); + pk_rel = heap_open(riinfo.pk_relid, RowShareLock); /* ---------- * SQL3 11.9 <referential constraint definition> @@ -250,12 +314,9 @@ RI_FKey_check(PG_FUNCTION_ARGS) * future enhancements. * ---------- */ - if (tgnargs == 4) + if (riinfo.nkeys == 0) { - ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, - RI_PLAN_CHECK_LOOKUPPK_NOCOLS, - fk_rel, pk_rel, - tgnargs, tgargs); + ri_BuildQueryKeyFull(&qkey, &riinfo, RI_PLAN_CHECK_LOOKUPPK_NOCOLS); if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); @@ -271,7 +332,8 @@ RI_FKey_check(PG_FUNCTION_ARGS) * ---------- */ quoteRelationName(pkrelname, pk_rel); - snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x FOR SHARE OF x", + snprintf(querystr, sizeof(querystr), + "SELECT 1 FROM ONLY %s x FOR SHARE OF x", pkrelname); /* Prepare and save the plan */ @@ -287,7 +349,7 @@ RI_FKey_check(PG_FUNCTION_ARGS) NULL, NULL, false, SPI_OK_SELECT, - tgargs[RI_CONSTRAINT_NAME_ARGNO]); + NameStr(riinfo.conname)); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); @@ -295,19 +357,14 @@ RI_FKey_check(PG_FUNCTION_ARGS) heap_close(pk_rel, RowShareLock); return PointerGetDatum(NULL); - } - match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]); - - if (match_type == RI_MATCH_TYPE_PARTIAL) + if (riinfo.confmatchtype == FKCONSTR_MATCH_PARTIAL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); - ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, - RI_PLAN_CHECK_LOOKUPPK, fk_rel, pk_rel, - tgnargs, tgargs); + ri_BuildQueryKeyFull(&qkey, &riinfo, RI_PLAN_CHECK_LOOKUPPK); switch (ri_NullCheck(fk_rel, new_row, &qkey, RI_KEYPAIR_FK_IDX)) { @@ -329,9 +386,9 @@ RI_FKey_check(PG_FUNCTION_ARGS) * This is the only case that differs between the three kinds of * MATCH. */ - switch (match_type) + switch (riinfo.confmatchtype) { - case RI_MATCH_TYPE_FULL: + case FKCONSTR_MATCH_FULL: /* * Not allowed - MATCH FULL says either all or none of the @@ -341,12 +398,12 @@ RI_FKey_check(PG_FUNCTION_ARGS) (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", RelationGetRelationName(trigdata->tg_relation), - tgargs[RI_CONSTRAINT_NAME_ARGNO]), + NameStr(riinfo.conname)), errdetail("MATCH FULL does not allow mixing of null and nonnull key values."))); heap_close(pk_rel, RowShareLock); return PointerGetDatum(NULL); - case RI_MATCH_TYPE_UNSPECIFIED: + case FKCONSTR_MATCH_UNSPECIFIED: /* * MATCH <unspecified> - if ANY column is null, we have a @@ -355,7 +412,7 @@ RI_FKey_check(PG_FUNCTION_ARGS) heap_close(pk_rel, RowShareLock); return PointerGetDatum(NULL); - case RI_MATCH_TYPE_PARTIAL: + case FKCONSTR_MATCH_PARTIAL: /* * MATCH PARTIAL - all non-null columns must match. (not @@ -387,39 +444,43 @@ RI_FKey_check(PG_FUNCTION_ARGS) */ if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { - char querystr[MAX_QUOTED_REL_NAME_LEN + 100 + - (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS]; + StringInfoData querybuf; char pkrelname[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 <pktable> WHERE pkatt1 = $1 [AND ...] + * SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...] FOR SHARE * The type id's for the $ parameters are those of the - * corresponding FK attributes. Thus, ri_PlanCheck could - * eventually fail if the parser cannot identify some way - * how to compare these two types by '='. + * corresponding FK attributes. * ---------- */ + initStringInfo(&querybuf); quoteRelationName(pkrelname, pk_rel); - snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", pkrelname); + appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname); querysep = "WHERE"; - for (i = 0; i < qkey.nkeypairs; i++) + 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, - tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_PK_IDX]); - snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d", - querysep, attname, i + 1); + RIAttName(pk_rel, riinfo.pk_attnums[i])); + sprintf(paramname, "$%d", i + 1); + ri_GenerateQual(&querybuf, querysep, + attname, pk_type, + riinfo.pf_eq_oprs[i], + paramname, fk_type); querysep = "AND"; - queryoids[i] = SPI_gettypeid(fk_rel->rd_att, - qkey.keypair[i][RI_KEYPAIR_FK_IDX]); + queryoids[i] = fk_type; } - strcat(querystr, " FOR SHARE OF x"); + appendStringInfo(&querybuf, " FOR SHARE OF x"); /* Prepare and save the plan */ - qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids, + qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids, &qkey, fk_rel, pk_rel, true); } @@ -431,7 +492,7 @@ RI_FKey_check(PG_FUNCTION_ARGS) NULL, new_row, false, SPI_OK_SELECT, - tgargs[RI_CONSTRAINT_NAME_ARGNO]); + NameStr(riinfo.conname)); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); @@ -471,7 +532,7 @@ RI_FKey_check_upd(PG_FUNCTION_ARGS) /* ---------- * ri_Check_Pk_Match * - * Check for matching value of old pk row in current state for + * 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. * ---------- @@ -479,17 +540,14 @@ RI_FKey_check_upd(PG_FUNCTION_ARGS) static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, HeapTuple old_row, - Oid tgoid, int match_type, - int tgnargs, char **tgargs) + const RI_ConstraintInfo *riinfo) { void *qplan; RI_QueryKey qkey; int i; bool result; - ri_BuildQueryKeyPkCheck(&qkey, tgoid, - RI_PLAN_CHECK_LOOKUPPK, pk_rel, - tgnargs, tgargs); + ri_BuildQueryKeyPkCheck(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK); switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX)) { @@ -506,10 +564,10 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, * This is the only case that differs between the three kinds of * MATCH. */ - switch (match_type) + switch (riinfo->confmatchtype) { - case RI_MATCH_TYPE_FULL: - case RI_MATCH_TYPE_UNSPECIFIED: + case FKCONSTR_MATCH_FULL: + case FKCONSTR_MATCH_UNSPECIFIED: /* * MATCH <unspecified>/FULL - if ANY column is null, we @@ -517,7 +575,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, */ return true; - case RI_MATCH_TYPE_PARTIAL: + case FKCONSTR_MATCH_PARTIAL: /* * MATCH PARTIAL - all non-null columns must match. (not @@ -548,39 +606,42 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, */ if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { - char querystr[MAX_QUOTED_REL_NAME_LEN + 100 + - (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS]; + StringInfoData querybuf; char pkrelname[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 <pktable> WHERE pkatt1 = $1 [AND ...] + * SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...] FOR SHARE * The type id's for the $ parameters are those of the - * corresponding FK attributes. Thus, ri_PlanCheck could - * eventually fail if the parser cannot identify some way - * how to compare these two types by '='. + * PK attributes themselves. * ---------- */ + initStringInfo(&querybuf); quoteRelationName(pkrelname, pk_rel); - snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", pkrelname); + appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname); querysep = "WHERE"; - for (i = 0; i < qkey.nkeypairs; i++) + for (i = 0; i < riinfo->nkeys; i++) { + Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); + quoteOneName(attname, - tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_PK_IDX]); - snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d", - querysep, attname, i + 1); + RIAttName(pk_rel, riinfo->pk_attnums[i])); + sprintf(paramname, "$%d", i + 1); + ri_GenerateQual(&querybuf, querysep, + attname, pk_type, + riinfo->pp_eq_oprs[i], + paramname, pk_type); querysep = "AND"; - queryoids[i] = SPI_gettypeid(pk_rel->rd_att, - qkey.keypair[i][RI_KEYPAIR_PK_IDX]); + queryoids[i] = pk_type; } - strcat(querystr, " FOR SHARE OF x"); + appendStringInfo(&querybuf, " FOR SHARE OF x"); /* Prepare and save the plan */ - qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids, + qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, &qkey, fk_rel, pk_rel, true); } @@ -612,28 +673,29 @@ Datum RI_FKey_noaction_del(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; - int tgnargs; - char **tgargs; + RI_ConstraintInfo riinfo; Relation fk_rel; Relation pk_rel; HeapTuple old_row; RI_QueryKey qkey; void *qplan; int i; - int match_type; /* * Check that this is a valid trigger call on the right time and event. */ ri_CheckTrigger(fcinfo, "RI_FKey_noaction_del", RI_TRIGTYPE_DELETE); - tgnargs = trigdata->tg_trigger->tgnargs; - tgargs = trigdata->tg_trigger->tgargs; + /* + * Get arguments. + */ + ri_FetchConstraintInfo(&riinfo, + trigdata->tg_trigger, trigdata->tg_relation, true); /* * Nothing to do if no column names to compare given */ - if (tgnargs == 4) + if (riinfo.nkeys == 0) return PointerGetDatum(NULL); /* @@ -642,14 +704,11 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS) * fk_rel is opened in RowShareLock mode since that's what our eventual * SELECT FOR SHARE will get on it. */ - fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock); + fk_rel = heap_open(riinfo.fk_relid, RowShareLock); pk_rel = trigdata->tg_relation; old_row = trigdata->tg_trigtuple; - match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]); - if (ri_Check_Pk_Match(pk_rel, fk_rel, - old_row, trigdata->tg_trigger->tgoid, - match_type, tgnargs, tgargs)) + 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 @@ -659,7 +718,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS) return PointerGetDatum(NULL); } - switch (match_type) + switch (riinfo.confmatchtype) { /* ---------- * SQL3 11.9 <referential constraint definition> @@ -668,12 +727,10 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS) * ... ON DELETE CASCADE * ---------- */ - case RI_MATCH_TYPE_UNSPECIFIED: - case RI_MATCH_TYPE_FULL: - ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, - RI_PLAN_NOACTION_DEL_CHECKREF, - fk_rel, pk_rel, - tgnargs, tgargs); + case FKCONSTR_MATCH_UNSPECIFIED: + case FKCONSTR_MATCH_FULL: + ri_BuildQueryKeyFull(&qkey, &riinfo, + RI_PLAN_NOACTION_DEL_CHECKREF); switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX)) { @@ -704,39 +761,44 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS) */ if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { - char querystr[MAX_QUOTED_REL_NAME_LEN + 100 + - (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS]; + 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 fkatt1 = $1 [AND ...] + * SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the - * corresponding PK attributes. Thus, ri_PlanCheck could - * eventually fail if the parser cannot identify some way - * how to compare these two types by '='. + * corresponding PK attributes. * ---------- */ + initStringInfo(&querybuf); quoteRelationName(fkrelname, fk_rel); - snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", fkrelname); + appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", + fkrelname); querysep = "WHERE"; - for (i = 0; i < qkey.nkeypairs; i++) + 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, - tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]); - snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d", - querysep, attname, i + 1); + 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] = SPI_gettypeid(pk_rel->rd_att, - qkey.keypair[i][RI_KEYPAIR_PK_IDX]); + queryoids[i] = pk_type; } - strcat(querystr, " FOR SHARE OF x"); + appendStringInfo(&querybuf, " FOR SHARE OF x"); /* Prepare and save the plan */ - qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids, + qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids, &qkey, fk_rel, pk_rel, true); } @@ -748,7 +810,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS) old_row, NULL, true, /* must detect new rows */ SPI_OK_SELECT, - tgargs[RI_CONSTRAINT_NAME_ARGNO]); + NameStr(riinfo.conname)); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); @@ -760,7 +822,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS) /* * Handle MATCH PARTIAL restrict delete. */ - case RI_MATCH_TYPE_PARTIAL: + case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); @@ -770,7 +832,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -787,8 +849,7 @@ Datum RI_FKey_noaction_upd(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; - int tgnargs; - char **tgargs; + RI_ConstraintInfo riinfo; Relation fk_rel; Relation pk_rel; HeapTuple new_row; @@ -796,20 +857,22 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) RI_QueryKey qkey; void *qplan; int i; - int match_type; /* * Check that this is a valid trigger call on the right time and event. */ ri_CheckTrigger(fcinfo, "RI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE); - tgnargs = trigdata->tg_trigger->tgnargs; - tgargs = trigdata->tg_trigger->tgargs; + /* + * Get arguments. + */ + ri_FetchConstraintInfo(&riinfo, + trigdata->tg_trigger, trigdata->tg_relation, true); /* * Nothing to do if no column names to compare given */ - if (tgnargs == 4) + if (riinfo.nkeys == 0) return PointerGetDatum(NULL); /* @@ -819,14 +882,12 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) * fk_rel is opened in RowShareLock mode since that's what our eventual * SELECT FOR SHARE will get on it. */ - fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock); + fk_rel = heap_open(riinfo.fk_relid, RowShareLock); pk_rel = trigdata->tg_relation; new_row = trigdata->tg_newtuple; old_row = trigdata->tg_trigtuple; - match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]); - - switch (match_type) + switch (riinfo.confmatchtype) { /* ---------- * SQL3 11.9 <referential constraint definition> @@ -835,12 +896,10 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) * ... ON DELETE CASCADE * ---------- */ - case RI_MATCH_TYPE_UNSPECIFIED: - case RI_MATCH_TYPE_FULL: - ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, - RI_PLAN_NOACTION_UPD_CHECKREF, - fk_rel, pk_rel, - tgnargs, tgargs); + case FKCONSTR_MATCH_UNSPECIFIED: + case FKCONSTR_MATCH_FULL: + ri_BuildQueryKeyFull(&qkey, &riinfo, + RI_PLAN_NOACTION_UPD_CHECKREF); switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX)) { @@ -865,16 +924,13 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) /* * No need to check anything if old and new keys are equal */ - if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey, - RI_KEYPAIR_PK_IDX)) + if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true)) { heap_close(fk_rel, RowShareLock); return PointerGetDatum(NULL); } - if (ri_Check_Pk_Match(pk_rel, fk_rel, - old_row, trigdata->tg_trigger->tgoid, - match_type, tgnargs, tgargs)) + if (ri_Check_Pk_Match(pk_rel, fk_rel, old_row, &riinfo)) { /* * There's either another row, or no row could match this one. @@ -893,39 +949,44 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) */ if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { - char querystr[MAX_QUOTED_REL_NAME_LEN + 100 + - (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS]; + 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 fkatt1 = $1 [AND ...] + * SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the - * corresponding PK attributes. Thus, ri_PlanCheck could - * eventually fail if the parser cannot identify some way - * how to compare these two types by '='. + * corresponding PK attributes. * ---------- */ + initStringInfo(&querybuf); quoteRelationName(fkrelname, fk_rel); - snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", fkrelname); + appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", + fkrelname); querysep = "WHERE"; - for (i = 0; i < qkey.nkeypairs; i++) + 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, - tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]); - snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d", - querysep, attname, i + 1); + 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] = SPI_gettypeid(pk_rel->rd_att, - qkey.keypair[i][RI_KEYPAIR_PK_IDX]); + queryoids[i] = pk_type; } - strcat(querystr, " FOR SHARE OF x"); + appendStringInfo(&querybuf, " FOR SHARE OF x"); /* Prepare and save the plan */ - qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids, + qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids, &qkey, fk_rel, pk_rel, true); } @@ -937,7 +998,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) old_row, NULL, true, /* must detect new rows */ SPI_OK_SELECT, - tgargs[RI_CONSTRAINT_NAME_ARGNO]); + NameStr(riinfo.conname)); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); @@ -949,7 +1010,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) /* * Handle MATCH PARTIAL noaction update. */ - case RI_MATCH_TYPE_PARTIAL: + case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); @@ -959,7 +1020,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -974,8 +1035,7 @@ Datum RI_FKey_cascade_del(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; - int tgnargs; - char **tgargs; + RI_ConstraintInfo riinfo; Relation fk_rel; Relation pk_rel; HeapTuple old_row; @@ -988,13 +1048,16 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) */ ri_CheckTrigger(fcinfo, "RI_FKey_cascade_del", RI_TRIGTYPE_DELETE); - tgnargs = trigdata->tg_trigger->tgnargs; - tgargs = trigdata->tg_trigger->tgargs; + /* + * Get arguments. + */ + ri_FetchConstraintInfo(&riinfo, + trigdata->tg_trigger, trigdata->tg_relation, true); /* * Nothing to do if no column names to compare given */ - if (tgnargs == 4) + if (riinfo.nkeys == 0) return PointerGetDatum(NULL); /* @@ -1003,11 +1066,11 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) * fk_rel is opened in RowExclusiveLock mode since that's what our * eventual DELETE will get on it. */ - fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock); + fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock); pk_rel = trigdata->tg_relation; old_row = trigdata->tg_trigtuple; - switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO])) + switch (riinfo.confmatchtype) { /* ---------- * SQL3 11.9 <referential constraint definition> @@ -1016,12 +1079,10 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) * ... ON DELETE CASCADE * ---------- */ - case RI_MATCH_TYPE_UNSPECIFIED: - case RI_MATCH_TYPE_FULL: - ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, - RI_PLAN_CASCADE_DEL_DODELETE, - fk_rel, pk_rel, - tgnargs, tgargs); + case FKCONSTR_MATCH_UNSPECIFIED: + case FKCONSTR_MATCH_FULL: + ri_BuildQueryKeyFull(&qkey, &riinfo, + RI_PLAN_CASCADE_DEL_DODELETE); switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX)) { @@ -1051,38 +1112,42 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) */ if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { - char querystr[MAX_QUOTED_REL_NAME_LEN + 100 + - (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS]; + 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 fkatt1 = $1 [AND ...] + * DELETE FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the - * corresponding PK attributes. Thus, ri_PlanCheck could - * eventually fail if the parser cannot identify some way - * how to compare these two types by '='. + * corresponding PK attributes. * ---------- */ + initStringInfo(&querybuf); quoteRelationName(fkrelname, fk_rel); - snprintf(querystr, sizeof(querystr), "DELETE FROM ONLY %s", fkrelname); + appendStringInfo(&querybuf, "DELETE FROM ONLY %s", fkrelname); querysep = "WHERE"; - for (i = 0; i < qkey.nkeypairs; i++) + 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, - tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]); - snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d", - querysep, attname, i + 1); + 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] = SPI_gettypeid(pk_rel->rd_att, - qkey.keypair[i][RI_KEYPAIR_PK_IDX]); + queryoids[i] = pk_type; } /* Prepare and save the plan */ - qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids, + qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids, &qkey, fk_rel, pk_rel, true); } @@ -1095,7 +1160,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) old_row, NULL, true, /* must detect new rows */ SPI_OK_DELETE, - tgargs[RI_CONSTRAINT_NAME_ARGNO]); + NameStr(riinfo.conname)); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); @@ -1107,7 +1172,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) /* * Handle MATCH PARTIAL cascaded delete. */ - case RI_MATCH_TYPE_PARTIAL: + case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); @@ -1117,7 +1182,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -1132,8 +1197,7 @@ Datum RI_FKey_cascade_upd(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; - int tgnargs; - char **tgargs; + RI_ConstraintInfo riinfo; Relation fk_rel; Relation pk_rel; HeapTuple new_row; @@ -1148,13 +1212,16 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) */ ri_CheckTrigger(fcinfo, "RI_FKey_cascade_upd", RI_TRIGTYPE_UPDATE); - tgnargs = trigdata->tg_trigger->tgnargs; - tgargs = trigdata->tg_trigger->tgargs; + /* + * Get arguments. + */ + ri_FetchConstraintInfo(&riinfo, + trigdata->tg_trigger, trigdata->tg_relation, true); /* * Nothing to do if no column names to compare given */ - if (tgnargs == 4) + if (riinfo.nkeys == 0) return PointerGetDatum(NULL); /* @@ -1164,12 +1231,12 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) * fk_rel is opened in RowExclusiveLock mode since that's what our * eventual UPDATE will get on it. */ - fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock); + fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock); pk_rel = trigdata->tg_relation; new_row = trigdata->tg_newtuple; old_row = trigdata->tg_trigtuple; - switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO])) + switch (riinfo.confmatchtype) { /* ---------- * SQL3 11.9 <referential constraint definition> @@ -1178,12 +1245,10 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) * ... ON UPDATE CASCADE * ---------- */ - case RI_MATCH_TYPE_UNSPECIFIED: - case RI_MATCH_TYPE_FULL: - ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, - RI_PLAN_CASCADE_UPD_DOUPDATE, - fk_rel, pk_rel, - tgnargs, tgargs); + case FKCONSTR_MATCH_UNSPECIFIED: + case FKCONSTR_MATCH_FULL: + ri_BuildQueryKeyFull(&qkey, &riinfo, + RI_PLAN_CASCADE_UPD_DOUPDATE); switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX)) { @@ -1208,8 +1273,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) /* * No need to do anything if old and new keys are equal */ - if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey, - RI_KEYPAIR_PK_IDX)) + if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true)) { heap_close(fk_rel, RowExclusiveLock); return PointerGetDatum(NULL); @@ -1224,11 +1288,11 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) */ if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { - char querystr[MAX_QUOTED_REL_NAME_LEN + 100 + - (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2]; - char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS]; + 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]; @@ -1236,36 +1300,43 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) /* ---------- * The query string built is * UPDATE ONLY <fktable> SET fkatt1 = $1 [, ...] - * WHERE fkatt1 = $n [AND ...] + * WHERE $n = fkatt1 [AND ...] * The type id's for the $ parameters are those of the - * corresponding PK attributes. Thus, ri_PlanCheck could - * eventually fail if the parser cannot identify some way - * how to compare these two types by '='. + * 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); - snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname); - qualstr[0] = '\0'; + appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname); querysep = ""; qualsep = "WHERE"; - for (i = 0, j = qkey.nkeypairs; i < qkey.nkeypairs; i++, j++) + 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, - tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]); - snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = $%d", - querysep, attname, i + 1); - snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d", - qualsep, attname, j + 1); + 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, + paramname, pk_type, + riinfo.pf_eq_oprs[i], + attname, fk_type); querysep = ","; qualsep = "AND"; - queryoids[i] = SPI_gettypeid(pk_rel->rd_att, - qkey.keypair[i][RI_KEYPAIR_PK_IDX]); - queryoids[j] = queryoids[i]; + queryoids[i] = pk_type; + queryoids[j] = pk_type; } - strcat(querystr, qualstr); + appendStringInfoString(&querybuf, qualbuf.data); /* Prepare and save the plan */ - qplan = ri_PlanCheck(querystr, qkey.nkeypairs * 2, queryoids, + qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys * 2, queryoids, &qkey, fk_rel, pk_rel, true); } @@ -1277,7 +1348,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) old_row, new_row, true, /* must detect new rows */ SPI_OK_UPDATE, - tgargs[RI_CONSTRAINT_NAME_ARGNO]); + NameStr(riinfo.conname)); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); @@ -1289,7 +1360,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) /* * Handle MATCH PARTIAL cascade update. */ - case RI_MATCH_TYPE_PARTIAL: + case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); @@ -1299,7 +1370,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -1321,8 +1392,7 @@ Datum RI_FKey_restrict_del(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; - int tgnargs; - char **tgargs; + RI_ConstraintInfo riinfo; Relation fk_rel; Relation pk_rel; HeapTuple old_row; @@ -1335,13 +1405,16 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS) */ ri_CheckTrigger(fcinfo, "RI_FKey_restrict_del", RI_TRIGTYPE_DELETE); - tgnargs = trigdata->tg_trigger->tgnargs; - tgargs = trigdata->tg_trigger->tgargs; + /* + * Get arguments. + */ + ri_FetchConstraintInfo(&riinfo, + trigdata->tg_trigger, trigdata->tg_relation, true); /* * Nothing to do if no column names to compare given */ - if (tgnargs == 4) + if (riinfo.nkeys == 0) return PointerGetDatum(NULL); /* @@ -1350,11 +1423,11 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS) * fk_rel is opened in RowShareLock mode since that's what our eventual * SELECT FOR SHARE will get on it. */ - fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock); + fk_rel = heap_open(riinfo.fk_relid, RowShareLock); pk_rel = trigdata->tg_relation; old_row = trigdata->tg_trigtuple; - switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO])) + switch (riinfo.confmatchtype) { /* ---------- * SQL3 11.9 <referential constraint definition> @@ -1363,12 +1436,10 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS) * ... ON DELETE CASCADE * ---------- */ - case RI_MATCH_TYPE_UNSPECIFIED: - case RI_MATCH_TYPE_FULL: - ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, - RI_PLAN_RESTRICT_DEL_CHECKREF, - fk_rel, pk_rel, - tgnargs, tgargs); + case FKCONSTR_MATCH_UNSPECIFIED: + case FKCONSTR_MATCH_FULL: + ri_BuildQueryKeyFull(&qkey, &riinfo, + RI_PLAN_RESTRICT_DEL_CHECKREF); switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX)) { @@ -1399,39 +1470,44 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS) */ if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { - char querystr[MAX_QUOTED_REL_NAME_LEN + 100 + - (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS]; + 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 fkatt1 = $1 [AND ...] + * SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the - * corresponding PK attributes. Thus, ri_PlanCheck could - * eventually fail if the parser cannot identify some way - * how to compare these two types by '='. + * corresponding PK attributes. * ---------- */ + initStringInfo(&querybuf); quoteRelationName(fkrelname, fk_rel); - snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", fkrelname); + appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", + fkrelname); querysep = "WHERE"; - for (i = 0; i < qkey.nkeypairs; i++) + 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, - tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]); - snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d", - querysep, attname, i + 1); + 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] = SPI_gettypeid(pk_rel->rd_att, - qkey.keypair[i][RI_KEYPAIR_PK_IDX]); + queryoids[i] = pk_type; } - strcat(querystr, " FOR SHARE OF x"); + appendStringInfo(&querybuf, " FOR SHARE OF x"); /* Prepare and save the plan */ - qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids, + qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids, &qkey, fk_rel, pk_rel, true); } @@ -1443,7 +1519,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS) old_row, NULL, true, /* must detect new rows */ SPI_OK_SELECT, - tgargs[RI_CONSTRAINT_NAME_ARGNO]); + NameStr(riinfo.conname)); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); @@ -1455,7 +1531,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS) /* * Handle MATCH PARTIAL restrict delete. */ - case RI_MATCH_TYPE_PARTIAL: + case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); @@ -1465,7 +1541,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -1487,8 +1563,7 @@ Datum RI_FKey_restrict_upd(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; - int tgnargs; - char **tgargs; + RI_ConstraintInfo riinfo; Relation fk_rel; Relation pk_rel; HeapTuple new_row; @@ -1502,13 +1577,16 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS) */ ri_CheckTrigger(fcinfo, "RI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE); - tgnargs = trigdata->tg_trigger->tgnargs; - tgargs = trigdata->tg_trigger->tgargs; + /* + * Get arguments. + */ + ri_FetchConstraintInfo(&riinfo, + trigdata->tg_trigger, trigdata->tg_relation, true); /* * Nothing to do if no column names to compare given */ - if (tgnargs == 4) + if (riinfo.nkeys == 0) return PointerGetDatum(NULL); /* @@ -1518,12 +1596,12 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS) * fk_rel is opened in RowShareLock mode since that's what our eventual * SELECT FOR SHARE will get on it. */ - fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock); + fk_rel = heap_open(riinfo.fk_relid, RowShareLock); pk_rel = trigdata->tg_relation; new_row = trigdata->tg_newtuple; old_row = trigdata->tg_trigtuple; - switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO])) + switch (riinfo.confmatchtype) { /* ---------- * SQL3 11.9 <referential constraint definition> @@ -1532,12 +1610,10 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS) * ... ON DELETE CASCADE * ---------- */ - case RI_MATCH_TYPE_UNSPECIFIED: - case RI_MATCH_TYPE_FULL: - ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, - RI_PLAN_RESTRICT_UPD_CHECKREF, - fk_rel, pk_rel, - tgnargs, tgargs); + case FKCONSTR_MATCH_UNSPECIFIED: + case FKCONSTR_MATCH_FULL: + ri_BuildQueryKeyFull(&qkey, &riinfo, + RI_PLAN_RESTRICT_UPD_CHECKREF); switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX)) { @@ -1562,8 +1638,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS) /* * No need to check anything if old and new keys are equal */ - if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey, - RI_KEYPAIR_PK_IDX)) + if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true)) { heap_close(fk_rel, RowShareLock); return PointerGetDatum(NULL); @@ -1578,39 +1653,44 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS) */ if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { - char querystr[MAX_QUOTED_REL_NAME_LEN + 100 + - (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS]; + 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 fkatt1 = $1 [AND ...] + * SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the - * corresponding PK attributes. Thus, ri_PlanCheck could - * eventually fail if the parser cannot identify some way - * how to compare these two types by '='. + * corresponding PK attributes. * ---------- */ + initStringInfo(&querybuf); quoteRelationName(fkrelname, fk_rel); - snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", fkrelname); + appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", + fkrelname); querysep = "WHERE"; - for (i = 0; i < qkey.nkeypairs; i++) + 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, - tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]); - snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d", - querysep, attname, i + 1); + 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] = SPI_gettypeid(pk_rel->rd_att, - qkey.keypair[i][RI_KEYPAIR_PK_IDX]); + queryoids[i] = pk_type; } - strcat(querystr, " FOR SHARE OF x"); + appendStringInfo(&querybuf, " FOR SHARE OF x"); /* Prepare and save the plan */ - qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids, + qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids, &qkey, fk_rel, pk_rel, true); } @@ -1622,7 +1702,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS) old_row, NULL, true, /* must detect new rows */ SPI_OK_SELECT, - tgargs[RI_CONSTRAINT_NAME_ARGNO]); + NameStr(riinfo.conname)); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); @@ -1634,7 +1714,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS) /* * Handle MATCH PARTIAL restrict update. */ - case RI_MATCH_TYPE_PARTIAL: + case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); @@ -1644,7 +1724,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -1659,8 +1739,7 @@ Datum RI_FKey_setnull_del(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; - int tgnargs; - char **tgargs; + RI_ConstraintInfo riinfo; Relation fk_rel; Relation pk_rel; HeapTuple old_row; @@ -1673,13 +1752,16 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS) */ ri_CheckTrigger(fcinfo, "RI_FKey_setnull_del", RI_TRIGTYPE_DELETE); - tgnargs = trigdata->tg_trigger->tgnargs; - tgargs = trigdata->tg_trigger->tgargs; + /* + * Get arguments. + */ + ri_FetchConstraintInfo(&riinfo, + trigdata->tg_trigger, trigdata->tg_relation, true); /* * Nothing to do if no column names to compare given */ - if (tgnargs == 4) + if (riinfo.nkeys == 0) return PointerGetDatum(NULL); /* @@ -1688,11 +1770,11 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS) * fk_rel is opened in RowExclusiveLock mode since that's what our * eventual UPDATE will get on it. */ - fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock); + fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock); pk_rel = trigdata->tg_relation; old_row = trigdata->tg_trigtuple; - switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO])) + switch (riinfo.confmatchtype) { /* ---------- * SQL3 11.9 <referential constraint definition> @@ -1701,12 +1783,10 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS) * ... ON DELETE SET NULL * ---------- */ - case RI_MATCH_TYPE_UNSPECIFIED: - case RI_MATCH_TYPE_FULL: - ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, - RI_PLAN_SETNULL_DEL_DOUPDATE, - fk_rel, pk_rel, - tgnargs, tgargs); + case FKCONSTR_MATCH_UNSPECIFIED: + case FKCONSTR_MATCH_FULL: + ri_BuildQueryKeyFull(&qkey, &riinfo, + RI_PLAN_SETNULL_DEL_DOUPDATE); switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX)) { @@ -1736,11 +1816,11 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS) */ if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { - char querystr[MAX_QUOTED_REL_NAME_LEN + 100 + - (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2]; - char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS]; + 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]; @@ -1748,35 +1828,40 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS) /* ---------- * The query string built is * UPDATE ONLY <fktable> SET fkatt1 = NULL [, ...] - * WHERE fkatt1 = $1 [AND ...] + * WHERE $1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the - * corresponding PK attributes. Thus, ri_PlanCheck could - * eventually fail if the parser cannot identify some way - * how to compare these two types by '='. + * corresponding PK attributes. * ---------- */ + initStringInfo(&querybuf); + initStringInfo(&qualbuf); quoteRelationName(fkrelname, fk_rel); - snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname); - qualstr[0] = '\0'; + appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname); querysep = ""; qualsep = "WHERE"; - for (i = 0; i < qkey.nkeypairs; i++) + 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, - tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]); - snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = NULL", - querysep, attname); - snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d", - qualsep, attname, i + 1); + 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] = SPI_gettypeid(pk_rel->rd_att, - qkey.keypair[i][RI_KEYPAIR_PK_IDX]); + queryoids[i] = pk_type; } - strcat(querystr, qualstr); + appendStringInfoString(&querybuf, qualbuf.data); /* Prepare and save the plan */ - qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids, + qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids, &qkey, fk_rel, pk_rel, true); } @@ -1788,7 +1873,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS) old_row, NULL, true, /* must detect new rows */ SPI_OK_UPDATE, - tgargs[RI_CONSTRAINT_NAME_ARGNO]); + NameStr(riinfo.conname)); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); @@ -1800,7 +1885,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS) /* * Handle MATCH PARTIAL set null delete. */ - case RI_MATCH_TYPE_PARTIAL: + case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); @@ -1810,7 +1895,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -1825,8 +1910,7 @@ Datum RI_FKey_setnull_upd(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; - int tgnargs; - char **tgargs; + RI_ConstraintInfo riinfo; Relation fk_rel; Relation pk_rel; HeapTuple new_row; @@ -1834,7 +1918,6 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) RI_QueryKey qkey; void *qplan; int i; - int match_type; bool use_cached_query; /* @@ -1842,13 +1925,16 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) */ ri_CheckTrigger(fcinfo, "RI_FKey_setnull_upd", RI_TRIGTYPE_UPDATE); - tgnargs = trigdata->tg_trigger->tgnargs; - tgargs = trigdata->tg_trigger->tgargs; + /* + * Get arguments. + */ + ri_FetchConstraintInfo(&riinfo, + trigdata->tg_trigger, trigdata->tg_relation, true); /* * Nothing to do if no column names to compare given */ - if (tgnargs == 4) + if (riinfo.nkeys == 0) return PointerGetDatum(NULL); /* @@ -1857,13 +1943,12 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) * fk_rel is opened in RowExclusiveLock mode since that's what our * eventual UPDATE will get on it. */ - fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock); + fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock); pk_rel = trigdata->tg_relation; new_row = trigdata->tg_newtuple; old_row = trigdata->tg_trigtuple; - match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]); - switch (match_type) + switch (riinfo.confmatchtype) { /* ---------- * SQL3 11.9 <referential constraint definition> @@ -1872,12 +1957,10 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) * ... ON UPDATE SET NULL * ---------- */ - case RI_MATCH_TYPE_UNSPECIFIED: - case RI_MATCH_TYPE_FULL: - ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, - RI_PLAN_SETNULL_UPD_DOUPDATE, - fk_rel, pk_rel, - tgnargs, tgargs); + case FKCONSTR_MATCH_UNSPECIFIED: + case FKCONSTR_MATCH_FULL: + ri_BuildQueryKeyFull(&qkey, &riinfo, + RI_PLAN_SETNULL_UPD_DOUPDATE); switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX)) { @@ -1902,8 +1985,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) /* * No need to do anything if old and new keys are equal */ - if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey, - RI_KEYPAIR_PK_IDX)) + if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true)) { heap_close(fk_rel, RowExclusiveLock); return PointerGetDatum(NULL); @@ -1925,9 +2007,9 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) * know that the old key value has no NULLs (see above). */ - use_cached_query = match_type == RI_MATCH_TYPE_FULL || + use_cached_query = (riinfo.confmatchtype == FKCONSTR_MATCH_FULL) || ri_AllKeysUnequal(pk_rel, old_row, new_row, - &qkey, RI_KEYPAIR_PK_IDX); + &riinfo, true); /* * Fetch or prepare a saved plan for the set null update operation @@ -1936,11 +2018,11 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) if (!use_cached_query || (qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { - char querystr[MAX_QUOTED_REL_NAME_LEN + 100 + - (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2]; - char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS]; + 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]; @@ -1948,48 +2030,52 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) /* ---------- * The query string built is * UPDATE ONLY <fktable> SET fkatt1 = NULL [, ...] - * WHERE fkatt1 = $1 [AND ...] + * WHERE $1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the - * corresponding PK attributes. Thus, ri_PlanCheck could - * eventually fail if the parser cannot identify some way - * how to compare these two types by '='. + * corresponding PK attributes. * ---------- */ + initStringInfo(&querybuf); + initStringInfo(&qualbuf); quoteRelationName(fkrelname, fk_rel); - snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname); - qualstr[0] = '\0'; + appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname); querysep = ""; qualsep = "WHERE"; - for (i = 0; i < qkey.nkeypairs; i++) + for (i = 0; i < riinfo.nkeys; i++) { - quoteOneName(attname, - tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]); + 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])); /* * MATCH <unspecified> - only change columns corresponding * to changed columns in pk_rel's key */ - if (match_type == RI_MATCH_TYPE_FULL || - !ri_OneKeyEqual(pk_rel, i, old_row, new_row, &qkey, - RI_KEYPAIR_PK_IDX)) + if (riinfo.confmatchtype == FKCONSTR_MATCH_FULL || + !ri_OneKeyEqual(pk_rel, i, old_row, new_row, + &riinfo, true)) { - snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = NULL", - querysep, attname); + appendStringInfo(&querybuf, + "%s %s = NULL", + querysep, attname); querysep = ","; } - snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d", - qualsep, attname, i + 1); + sprintf(paramname, "$%d", i + 1); + ri_GenerateQual(&qualbuf, qualsep, + paramname, pk_type, + riinfo.pf_eq_oprs[i], + attname, fk_type); qualsep = "AND"; - queryoids[i] = SPI_gettypeid(pk_rel->rd_att, - qkey.keypair[i][RI_KEYPAIR_PK_IDX]); + queryoids[i] = pk_type; } - strcat(querystr, qualstr); + appendStringInfoString(&querybuf, qualbuf.data); /* * Prepare the plan. Save it only if we're building the * "standard" plan. */ - qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids, + qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids, &qkey, fk_rel, pk_rel, use_cached_query); } @@ -2002,7 +2088,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) old_row, NULL, true, /* must detect new rows */ SPI_OK_UPDATE, - tgargs[RI_CONSTRAINT_NAME_ARGNO]); + NameStr(riinfo.conname)); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); @@ -2014,7 +2100,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) /* * Handle MATCH PARTIAL set null update. */ - case RI_MATCH_TYPE_PARTIAL: + case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); @@ -2024,7 +2110,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -2039,8 +2125,7 @@ Datum RI_FKey_setdefault_del(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; - int tgnargs; - char **tgargs; + RI_ConstraintInfo riinfo; Relation fk_rel; Relation pk_rel; HeapTuple old_row; @@ -2052,13 +2137,16 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) */ ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_del", RI_TRIGTYPE_DELETE); - tgnargs = trigdata->tg_trigger->tgnargs; - tgargs = trigdata->tg_trigger->tgargs; + /* + * Get arguments. + */ + ri_FetchConstraintInfo(&riinfo, + trigdata->tg_trigger, trigdata->tg_relation, true); /* * Nothing to do if no column names to compare given */ - if (tgnargs == 4) + if (riinfo.nkeys == 0) return PointerGetDatum(NULL); /* @@ -2067,11 +2155,11 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) * fk_rel is opened in RowExclusiveLock mode since that's what our * eventual UPDATE will get on it. */ - fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock); + fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock); pk_rel = trigdata->tg_relation; old_row = trigdata->tg_trigtuple; - switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO])) + switch (riinfo.confmatchtype) { /* ---------- * SQL3 11.9 <referential constraint definition> @@ -2080,12 +2168,10 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) * ... ON DELETE SET DEFAULT * ---------- */ - case RI_MATCH_TYPE_UNSPECIFIED: - case RI_MATCH_TYPE_FULL: - ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, - RI_PLAN_SETNULL_DEL_DOUPDATE, - fk_rel, pk_rel, - tgnargs, tgargs); + case FKCONSTR_MATCH_UNSPECIFIED: + case FKCONSTR_MATCH_FULL: + ri_BuildQueryKeyFull(&qkey, &riinfo, + RI_PLAN_SETNULL_DEL_DOUPDATE); switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX)) { @@ -2116,11 +2202,11 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) * default value could potentially change between calls. */ { - char querystr[MAX_QUOTED_REL_NAME_LEN + 100 + - (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2]; - char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS]; + 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]; @@ -2129,35 +2215,40 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) /* ---------- * The query string built is * UPDATE ONLY <fktable> SET fkatt1 = DEFAULT [, ...] - * WHERE fkatt1 = $1 [AND ...] + * WHERE $1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the - * corresponding PK attributes. Thus, ri_PlanCheck could - * eventually fail if the parser cannot identify some way - * how to compare these two types by '='. + * corresponding PK attributes. * ---------- */ + initStringInfo(&querybuf); + initStringInfo(&qualbuf); quoteRelationName(fkrelname, fk_rel); - snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname); - qualstr[0] = '\0'; + appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname); querysep = ""; qualsep = "WHERE"; - for (i = 0; i < qkey.nkeypairs; i++) + 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, - tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]); - snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = DEFAULT", - querysep, attname); - snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d", - qualsep, attname, i + 1); + 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] = SPI_gettypeid(pk_rel->rd_att, - qkey.keypair[i][RI_KEYPAIR_PK_IDX]); + queryoids[i] = pk_type; } - strcat(querystr, qualstr); + appendStringInfoString(&querybuf, qualbuf.data); /* Prepare the plan, don't save it */ - qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids, + qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids, &qkey, fk_rel, pk_rel, false); } @@ -2169,7 +2260,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) old_row, NULL, true, /* must detect new rows */ SPI_OK_UPDATE, - tgargs[RI_CONSTRAINT_NAME_ARGNO]); + NameStr(riinfo.conname)); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); @@ -2191,7 +2282,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) /* * Handle MATCH PARTIAL set null delete. */ - case RI_MATCH_TYPE_PARTIAL: + case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); @@ -2201,7 +2292,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -2216,28 +2307,29 @@ Datum RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; - int tgnargs; - char **tgargs; + RI_ConstraintInfo riinfo; Relation fk_rel; Relation pk_rel; HeapTuple new_row; HeapTuple old_row; RI_QueryKey qkey; void *qplan; - int match_type; /* * Check that this is a valid trigger call on the right time and event. */ ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_upd", RI_TRIGTYPE_UPDATE); - tgnargs = trigdata->tg_trigger->tgnargs; - tgargs = trigdata->tg_trigger->tgargs; + /* + * Get arguments. + */ + ri_FetchConstraintInfo(&riinfo, + trigdata->tg_trigger, trigdata->tg_relation, true); /* * Nothing to do if no column names to compare given */ - if (tgnargs == 4) + if (riinfo.nkeys == 0) return PointerGetDatum(NULL); /* @@ -2246,14 +2338,12 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) * fk_rel is opened in RowExclusiveLock mode since that's what our * eventual UPDATE will get on it. */ - fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock); + fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock); pk_rel = trigdata->tg_relation; new_row = trigdata->tg_newtuple; old_row = trigdata->tg_trigtuple; - match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]); - - switch (match_type) + switch (riinfo.confmatchtype) { /* ---------- * SQL3 11.9 <referential constraint definition> @@ -2262,12 +2352,10 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) * ... ON UPDATE SET DEFAULT * ---------- */ - case RI_MATCH_TYPE_UNSPECIFIED: - case RI_MATCH_TYPE_FULL: - ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, - RI_PLAN_SETNULL_DEL_DOUPDATE, - fk_rel, pk_rel, - tgnargs, tgargs); + case FKCONSTR_MATCH_UNSPECIFIED: + case FKCONSTR_MATCH_FULL: + ri_BuildQueryKeyFull(&qkey, &riinfo, + RI_PLAN_SETNULL_DEL_DOUPDATE); switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX)) { @@ -2292,8 +2380,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) /* * No need to do anything if old and new keys are equal */ - if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey, - RI_KEYPAIR_PK_IDX)) + if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true)) { heap_close(fk_rel, RowExclusiveLock); return PointerGetDatum(NULL); @@ -2308,11 +2395,11 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) * default value could potentially change between calls. */ { - char querystr[MAX_QUOTED_REL_NAME_LEN + 100 + - (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2]; - char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS]; + 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]; @@ -2321,45 +2408,50 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) /* ---------- * The query string built is * UPDATE ONLY <fktable> SET fkatt1 = DEFAULT [, ...] - * WHERE fkatt1 = $1 [AND ...] + * WHERE $1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the - * corresponding PK attributes. Thus, ri_PlanCheck could - * eventually fail if the parser cannot identify some way - * how to compare these two types by '='. + * corresponding PK attributes. * ---------- */ + initStringInfo(&querybuf); + initStringInfo(&qualbuf); quoteRelationName(fkrelname, fk_rel); - snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname); - qualstr[0] = '\0'; + appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname); querysep = ""; qualsep = "WHERE"; - for (i = 0; i < qkey.nkeypairs; i++) + 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, - tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]); + RIAttName(fk_rel, riinfo.fk_attnums[i])); /* * MATCH <unspecified> - only change columns corresponding * to changed columns in pk_rel's key */ - if (match_type == RI_MATCH_TYPE_FULL || - !ri_OneKeyEqual(pk_rel, i, old_row, - new_row, &qkey, RI_KEYPAIR_PK_IDX)) + if (riinfo.confmatchtype == FKCONSTR_MATCH_FULL || + !ri_OneKeyEqual(pk_rel, i, old_row, new_row, + &riinfo, true)) { - snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = DEFAULT", - querysep, attname); + appendStringInfo(&querybuf, + "%s %s = DEFAULT", + querysep, attname); querysep = ","; } - snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d", - qualsep, attname, i + 1); + sprintf(paramname, "$%d", i + 1); + ri_GenerateQual(&qualbuf, qualsep, + paramname, pk_type, + riinfo.pf_eq_oprs[i], + attname, fk_type); qualsep = "AND"; - queryoids[i] = SPI_gettypeid(pk_rel->rd_att, - qkey.keypair[i][RI_KEYPAIR_PK_IDX]); + queryoids[i] = pk_type; } - strcat(querystr, qualstr); + appendStringInfoString(&querybuf, qualbuf.data); /* Prepare the plan, don't save it */ - qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids, + qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids, &qkey, fk_rel, pk_rel, false); } @@ -2371,7 +2463,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) old_row, NULL, true, /* must detect new rows */ SPI_OK_UPDATE, - tgargs[RI_CONSTRAINT_NAME_ARGNO]); + NameStr(riinfo.conname)); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); @@ -2393,7 +2485,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) /* * Handle MATCH PARTIAL set null delete. */ - case RI_MATCH_TYPE_PARTIAL: + case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); @@ -2403,7 +2495,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -2420,57 +2512,37 @@ bool RI_FKey_keyequal_upd_pk(Trigger *trigger, Relation pk_rel, HeapTuple old_row, HeapTuple new_row) { - int tgnargs; - char **tgargs; + RI_ConstraintInfo riinfo; Relation fk_rel; RI_QueryKey qkey; /* - * Check for the correct # of call arguments + * Get arguments. */ - tgnargs = trigger->tgnargs; - tgargs = trigger->tgargs; - if (tgnargs < 4 || - tgnargs > RI_MAX_ARGUMENTS || - (tgnargs % 2) != 0) - ereport(ERROR, - (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), - errmsg("function \"%s\" called with wrong number of trigger arguments", - "RI_FKey_keyequal_upd"))); + ri_FetchConstraintInfo(&riinfo, trigger, pk_rel, true); /* * Nothing to do if no column names to compare given */ - if (tgnargs == 4) + if (riinfo.nkeys == 0) return true; - if (!OidIsValid(trigger->tgconstrrelid)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("no target table given for trigger \"%s\" on table \"%s\"", - trigger->tgname, - RelationGetRelationName(pk_rel)), - errhint("Remove this referential integrity trigger and its mates, " - "then do ALTER TABLE ADD CONSTRAINT."))); - - fk_rel = heap_open(trigger->tgconstrrelid, AccessShareLock); + fk_rel = heap_open(riinfo.fk_relid, AccessShareLock); - switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO])) + switch (riinfo.confmatchtype) { - case RI_MATCH_TYPE_UNSPECIFIED: - case RI_MATCH_TYPE_FULL: - ri_BuildQueryKeyFull(&qkey, trigger->tgoid, - RI_PLAN_KEYEQUAL_UPD, - fk_rel, pk_rel, - tgnargs, tgargs); + case FKCONSTR_MATCH_UNSPECIFIED: + case FKCONSTR_MATCH_FULL: + ri_BuildQueryKeyFull(&qkey, &riinfo, + RI_PLAN_KEYEQUAL_UPD); + heap_close(fk_rel, AccessShareLock); /* Return if key's are equal */ - return ri_KeysEqual(pk_rel, old_row, new_row, &qkey, - RI_KEYPAIR_PK_IDX); + return ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true); /* Handle MATCH PARTIAL set null delete. */ - case RI_MATCH_TYPE_PARTIAL: + case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); @@ -2478,7 +2550,7 @@ RI_FKey_keyequal_upd_pk(Trigger *trigger, Relation pk_rel, } /* Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return false; } @@ -2494,57 +2566,36 @@ bool RI_FKey_keyequal_upd_fk(Trigger *trigger, Relation fk_rel, HeapTuple old_row, HeapTuple new_row) { - int tgnargs; - char **tgargs; + RI_ConstraintInfo riinfo; Relation pk_rel; RI_QueryKey qkey; /* - * Check for the correct # of call arguments + * Get arguments. */ - tgnargs = trigger->tgnargs; - tgargs = trigger->tgargs; - if (tgnargs < 4 || - tgnargs > RI_MAX_ARGUMENTS || - (tgnargs % 2) != 0) - ereport(ERROR, - (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), - errmsg("function \"%s\" called with wrong number of trigger arguments", - "RI_FKey_keyequal_upd"))); + ri_FetchConstraintInfo(&riinfo, trigger, fk_rel, false); /* * Nothing to do if no column names to compare given */ - if (tgnargs == 4) + if (riinfo.nkeys == 0) return true; - if (!OidIsValid(trigger->tgconstrrelid)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("no target table given for trigger \"%s\" on table \"%s\"", - trigger->tgname, - RelationGetRelationName(fk_rel)), - errhint("Remove this referential integrity trigger and its mates, " - "then do ALTER TABLE ADD CONSTRAINT."))); - - pk_rel = heap_open(trigger->tgconstrrelid, AccessShareLock); + pk_rel = heap_open(riinfo.pk_relid, AccessShareLock); - switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO])) + switch (riinfo.confmatchtype) { - case RI_MATCH_TYPE_UNSPECIFIED: - case RI_MATCH_TYPE_FULL: - ri_BuildQueryKeyFull(&qkey, trigger->tgoid, - RI_PLAN_KEYEQUAL_UPD, - fk_rel, pk_rel, - tgnargs, tgargs); + case FKCONSTR_MATCH_UNSPECIFIED: + case FKCONSTR_MATCH_FULL: + ri_BuildQueryKeyFull(&qkey, &riinfo, + RI_PLAN_KEYEQUAL_UPD); heap_close(pk_rel, AccessShareLock); /* Return if key's are equal */ - return ri_KeysEqual(fk_rel, old_row, new_row, &qkey, - RI_KEYPAIR_FK_IDX); + return ri_KeysEqual(fk_rel, old_row, new_row, &riinfo, false); /* Handle MATCH PARTIAL set null delete. */ - case RI_MATCH_TYPE_PARTIAL: + case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); @@ -2552,7 +2603,7 @@ RI_FKey_keyequal_upd_fk(Trigger *trigger, Relation fk_rel, } /* Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return false; } @@ -2572,18 +2623,17 @@ RI_FKey_keyequal_upd_fk(Trigger *trigger, Relation fk_rel, * ---------- */ bool -RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel) +RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) { - const char *constrname = fkconstraint->constr_name; - char querystr[MAX_QUOTED_REL_NAME_LEN * 2 + 250 + - (MAX_QUOTED_NAME_LEN + 32) * ((RI_MAX_NUMKEYS * 4) + 1)]; + RI_ConstraintInfo riinfo; + const char *constrname = trigger->tgname; + StringInfoData querybuf; char pkrelname[MAX_QUOTED_REL_NAME_LEN]; - char relname[MAX_QUOTED_REL_NAME_LEN]; - char attname[MAX_QUOTED_NAME_LEN]; - char fkattname[MAX_QUOTED_NAME_LEN]; + char fkrelname[MAX_QUOTED_REL_NAME_LEN]; + char pkattname[MAX_QUOTED_NAME_LEN + 3]; + char fkattname[MAX_QUOTED_NAME_LEN + 3]; const char *sep; - ListCell *l; - ListCell *l2; + int i; int old_work_mem; char workmembuf[32]; int spi_result; @@ -2596,11 +2646,13 @@ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel) * * XXX are there any other show-stopper conditions to check? */ - if (pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_SELECT) != ACLCHECK_OK) + if (pg_class_aclcheck(RelationGetRelid(fk_rel), GetUserId(), ACL_SELECT) != ACLCHECK_OK) return false; - if (pg_class_aclcheck(RelationGetRelid(pkrel), GetUserId(), ACL_SELECT) != ACLCHECK_OK) + if (pg_class_aclcheck(RelationGetRelid(pk_rel), GetUserId(), ACL_SELECT) != ACLCHECK_OK) return false; + ri_FetchConstraintInfo(&riinfo, trigger, fk_rel, false); + /*---------- * The query string built is: * SELECT fk.keycols FROM ONLY relname fk @@ -2613,50 +2665,57 @@ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel) * (fk.keycol1 IS NOT NULL [OR ...]) *---------- */ - - sprintf(querystr, "SELECT "); + initStringInfo(&querybuf); + appendStringInfo(&querybuf, "SELECT "); sep = ""; - foreach(l, fkconstraint->fk_attrs) + for (i = 0; i < riinfo.nkeys; i++) { - quoteOneName(attname, strVal(lfirst(l))); - snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), - "%sfk.%s", sep, attname); + quoteOneName(fkattname, + RIAttName(fk_rel, riinfo.fk_attnums[i])); + appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname); sep = ", "; } - quoteRelationName(pkrelname, pkrel); - quoteRelationName(relname, rel); - snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), - " FROM ONLY %s fk LEFT OUTER JOIN ONLY %s pk ON (", - relname, pkrelname); + quoteRelationName(pkrelname, pk_rel); + quoteRelationName(fkrelname, fk_rel); + appendStringInfo(&querybuf, + " FROM ONLY %s fk LEFT OUTER JOIN ONLY %s pk ON", + fkrelname, pkrelname); - sep = ""; - forboth(l, fkconstraint->pk_attrs, l2, fkconstraint->fk_attrs) + strcpy(pkattname, "pk."); + strcpy(fkattname, "fk."); + sep = "("; + for (i = 0; i < riinfo.nkeys; i++) { - quoteOneName(attname, strVal(lfirst(l))); - quoteOneName(fkattname, strVal(lfirst(l2))); - snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), - "%spk.%s=fk.%s", - sep, attname, fkattname); - sep = " AND "; + Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]); + Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]); + + quoteOneName(pkattname + 3, + RIAttName(pk_rel, riinfo.pk_attnums[i])); + quoteOneName(fkattname + 3, + RIAttName(fk_rel, riinfo.fk_attnums[i])); + ri_GenerateQual(&querybuf, sep, + pkattname, pk_type, + riinfo.pf_eq_oprs[i], + fkattname, fk_type); + sep = "AND"; } /* * It's sufficient to test any one pk attribute for null to detect a join * failure. */ - quoteOneName(attname, strVal(linitial(fkconstraint->pk_attrs))); - snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), - ") WHERE pk.%s IS NULL AND (", attname); + quoteOneName(pkattname, RIAttName(pk_rel, riinfo.pk_attnums[0])); + appendStringInfo(&querybuf, ") WHERE pk.%s IS NULL AND (", pkattname); sep = ""; - foreach(l, fkconstraint->fk_attrs) + for (i = 0; i < riinfo.nkeys; i++) { - quoteOneName(attname, strVal(lfirst(l))); - snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), - "%sfk.%s IS NOT NULL", - sep, attname); - switch (fkconstraint->fk_matchtype) + quoteOneName(fkattname, RIAttName(fk_rel, riinfo.fk_attnums[i])); + appendStringInfo(&querybuf, + "%sfk.%s IS NOT NULL", + sep, fkattname); + switch (riinfo.confmatchtype) { case FKCONSTR_MATCH_UNSPECIFIED: sep = " AND "; @@ -2671,12 +2730,11 @@ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel) break; default: elog(ERROR, "unrecognized match type: %d", - fkconstraint->fk_matchtype); + riinfo.confmatchtype); break; } } - snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), - ")"); + appendStringInfo(&querybuf, ")"); /* * Temporarily increase work_mem so that the check query can be executed @@ -2702,10 +2760,11 @@ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel) * Generate the plan. We don't need to cache it, and there are no * arguments to the plan. */ - qplan = SPI_prepare(querystr, 0, NULL); + qplan = SPI_prepare(querybuf.data, 0, NULL); if (qplan == NULL) - elog(ERROR, "SPI_prepare returned %d for %s", SPI_result, querystr); + elog(ERROR, "SPI_prepare returned %d for %s", + SPI_result, querybuf.data); /* * Run the plan. For safety we force a current snapshot to be used. (In @@ -2728,8 +2787,6 @@ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel) { HeapTuple tuple = SPI_tuptable->vals[0]; TupleDesc tupdesc = SPI_tuptable->tupdesc; - int nkeys = list_length(fkconstraint->fk_attrs); - int i; RI_QueryKey qkey; /* @@ -2737,11 +2794,11 @@ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel) * complain about that rather than the lack of a match. MATCH FULL * disallows partially-null FK rows. */ - if (fkconstraint->fk_matchtype == FKCONSTR_MATCH_FULL) + if (riinfo.confmatchtype == FKCONSTR_MATCH_FULL) { bool isnull = false; - for (i = 1; i <= nkeys; i++) + for (i = 1; i <= riinfo.nkeys; i++) { (void) SPI_getbinval(tuple, tupdesc, i, &isnull); if (isnull) @@ -2751,7 +2808,7 @@ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel) ereport(ERROR, (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", - RelationGetRelationName(rel), + RelationGetRelationName(fk_rel), constrname), errdetail("MATCH FULL does not allow mixing of null and nonnull key values."))); } @@ -2762,12 +2819,12 @@ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel) */ MemSet(&qkey, 0, sizeof(qkey)); qkey.constr_queryno = RI_PLAN_CHECK_LOOKUPPK; - qkey.nkeypairs = nkeys; - for (i = 0; i < nkeys; i++) + qkey.nkeypairs = riinfo.nkeys; + for (i = 0; i < riinfo.nkeys; i++) qkey.keypair[i][RI_KEYPAIR_FK_IDX] = i + 1; ri_ReportViolation(&qkey, constrname, - pkrel, rel, + pk_rel, fk_rel, tuple, tupdesc, false); } @@ -2829,90 +2886,80 @@ quoteRelationName(char *buffer, Relation rel) quoteOneName(buffer, RelationGetRelationName(rel)); } - -/* ---------- - * ri_DetermineMatchType - +/* + * ri_GenerateQual --- generate a WHERE clause equating two variables * - * Convert the MATCH TYPE string into a switchable int - * ---------- + * The idea is to append " sep leftop op rightop" to buf. The complexity + * comes from needing to be sure that the parser will select the desired + * operator. We always name the operator using OPERATOR(schema.op) syntax + * (readability isn't a big priority here). We have to emit casts too, + * if either input isn't already the input type of the operator. */ -static int -ri_DetermineMatchType(char *str) +static void +ri_GenerateQual(StringInfo buf, + const char *sep, + const char *leftop, Oid leftoptype, + Oid opoid, + const char *rightop, Oid rightoptype) { - if (strcmp(str, "UNSPECIFIED") == 0) - return RI_MATCH_TYPE_UNSPECIFIED; - if (strcmp(str, "FULL") == 0) - return RI_MATCH_TYPE_FULL; - if (strcmp(str, "PARTIAL") == 0) - return RI_MATCH_TYPE_PARTIAL; - - elog(ERROR, "unrecognized referential integrity match type \"%s\"", str); - return 0; + HeapTuple opertup; + Form_pg_operator operform; + char *oprname; + char *nspname; + + opertup = SearchSysCache(OPEROID, + ObjectIdGetDatum(opoid), + 0, 0, 0); + if (!HeapTupleIsValid(opertup)) + elog(ERROR, "cache lookup failed for operator %u", opoid); + operform = (Form_pg_operator) GETSTRUCT(opertup); + Assert(operform->oprkind == 'b'); + oprname = NameStr(operform->oprname); + + nspname = get_namespace_name(operform->oprnamespace); + + appendStringInfo(buf, " %s %s", sep, leftop); + if (leftoptype != operform->oprleft) + appendStringInfo(buf, "::%s", format_type_be(operform->oprleft)); + appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname)); + appendStringInfoString(buf, oprname); + appendStringInfo(buf, ") %s", rightop); + if (rightoptype != operform->oprright) + appendStringInfo(buf, "::%s", format_type_be(operform->oprright)); + + ReleaseSysCache(opertup); } - /* ---------- * ri_BuildQueryKeyFull - * * Build up a new hashtable key for a prepared SPI plan of a - * constraint trigger of MATCH FULL. The key consists of: + * constraint trigger of MATCH FULL. * - * constr_type is FULL - * constr_id is the OID of the pg_trigger row that invoked us - * constr_queryno is an internal number of the query inside the proc - * fk_relid is the OID of referencing relation - * pk_relid is the OID of referenced relation - * nkeypairs is the number of keypairs - * following are the attribute number keypairs of the trigger invocation + * key: output argument, *key is filled in based on the other arguments + * riinfo: info from pg_constraint entry + * constr_queryno: an internal number of the query inside the proc * * At least for MATCH FULL this builds a unique key per plan. * ---------- */ static void -ri_BuildQueryKeyFull(RI_QueryKey *key, Oid constr_id, int32 constr_queryno, - Relation fk_rel, Relation pk_rel, - int argc, char **argv) +ri_BuildQueryKeyFull(RI_QueryKey *key, const RI_ConstraintInfo *riinfo, + int32 constr_queryno) { int i; - int j; - int fno; - /* - * Initialize the key and fill in type, oid's and number of keypairs - */ - memset(key, 0, sizeof(RI_QueryKey)); - key->constr_type = RI_MATCH_TYPE_FULL; - key->constr_id = constr_id; + MemSet(key, 0, sizeof(RI_QueryKey)); + key->constr_type = FKCONSTR_MATCH_FULL; + key->constr_id = riinfo->constraint_id; key->constr_queryno = constr_queryno; - key->fk_relid = fk_rel->rd_id; - key->pk_relid = pk_rel->rd_id; - key->nkeypairs = (argc - RI_FIRST_ATTNAME_ARGNO) / 2; - - /* - * Lookup the attribute numbers of the arguments to the trigger call and - * fill in the keypairs. - */ - for (i = 0, j = RI_FIRST_ATTNAME_ARGNO; j < argc; i++, j += 2) + key->fk_relid = riinfo->fk_relid; + key->pk_relid = riinfo->pk_relid; + key->nkeypairs = riinfo->nkeys; + for (i = 0; i < riinfo->nkeys; i++) { - fno = SPI_fnumber(fk_rel->rd_att, argv[j]); - if (fno == SPI_ERROR_NOATTRIBUTE) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("table \"%s\" does not have column \"%s\" referenced by constraint \"%s\"", - RelationGetRelationName(fk_rel), - argv[j], - argv[RI_CONSTRAINT_NAME_ARGNO]))); - key->keypair[i][RI_KEYPAIR_FK_IDX] = fno; - - fno = SPI_fnumber(pk_rel->rd_att, argv[j + 1]); - if (fno == SPI_ERROR_NOATTRIBUTE) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("table \"%s\" does not have column \"%s\" referenced by constraint \"%s\"", - RelationGetRelationName(pk_rel), - argv[j + 1], - argv[RI_CONSTRAINT_NAME_ARGNO]))); - key->keypair[i][RI_KEYPAIR_PK_IDX] = fno; + key->keypair[i][RI_KEYPAIR_FK_IDX] = riinfo->fk_attnums[i]; + key->keypair[i][RI_KEYPAIR_PK_IDX] = riinfo->pk_attnums[i]; } } @@ -2923,7 +2970,6 @@ static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname, int tgkind) { TriggerData *trigdata = (TriggerData *) fcinfo->context; - int tgnargs; if (!CALLED_AS_TRIGGER(fcinfo)) ereport(ERROR, @@ -2968,30 +3014,148 @@ ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname, int tgkind) errmsg("function \"%s\" must be fired for DELETE", funcname))); break; } +} - /* - * Check for the correct # of call arguments - */ - tgnargs = trigdata->tg_trigger->tgnargs; - if (tgnargs < 4 || - tgnargs > RI_MAX_ARGUMENTS || - (tgnargs % 2) != 0) - ereport(ERROR, - (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), - errmsg("function \"%s\" called with wrong number of trigger arguments", - funcname))); + +/* + * Fetch the pg_constraint entry for the FK constraint, and fill *riinfo + */ +static void +ri_FetchConstraintInfo(RI_ConstraintInfo *riinfo, + Trigger *trigger, Relation trig_rel, bool rel_is_pk) +{ + Oid constraintOid = trigger->tgconstraint; + HeapTuple tup; + Form_pg_constraint conForm; + Datum adatum; + bool isNull; + ArrayType *arr; + int numkeys; /* - * Check that tgconstrrelid is known. We need to check here because of - * ancient pg_dump bug; see notes in CreateTrigger(). + * Check that the FK constraint's OID is available; it might not be + * if we've been invoked via an ordinary trigger or an old-style + * "constraint trigger". */ - if (!OidIsValid(trigdata->tg_trigger->tgconstrrelid)) + if (!OidIsValid(constraintOid)) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("no target table given for trigger \"%s\" on table \"%s\"", - trigdata->tg_trigger->tgname, - RelationGetRelationName(trigdata->tg_relation)), + errmsg("no pg_constraint entry for trigger \"%s\" on table \"%s\"", + trigger->tgname, RelationGetRelationName(trig_rel)), errhint("Remove this referential integrity trigger and its mates, then do ALTER TABLE ADD CONSTRAINT."))); + + /* OK, fetch the tuple */ + tup = SearchSysCache(CONSTROID, + ObjectIdGetDatum(constraintOid), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) /* should not happen */ + elog(ERROR, "cache lookup failed for constraint %u", constraintOid); + conForm = (Form_pg_constraint) GETSTRUCT(tup); + + /* Do some easy cross-checks against the trigger call data */ + if (rel_is_pk) + { + if (conForm->contype != CONSTRAINT_FOREIGN || + conForm->conrelid != trigger->tgconstrrelid || + conForm->confrelid != RelationGetRelid(trig_rel)) + elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"", + trigger->tgname, RelationGetRelationName(trig_rel)); + } + else + { + if (conForm->contype != CONSTRAINT_FOREIGN || + conForm->conrelid != RelationGetRelid(trig_rel) || + conForm->confrelid != trigger->tgconstrrelid) + elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"", + trigger->tgname, RelationGetRelationName(trig_rel)); + } + + /* And extract data */ + riinfo->constraint_id = constraintOid; + memcpy(&riinfo->conname, &conForm->conname, sizeof(NameData)); + riinfo->pk_relid = conForm->confrelid; + riinfo->fk_relid = conForm->conrelid; + riinfo->confupdtype = conForm->confupdtype; + riinfo->confdeltype = conForm->confdeltype; + riinfo->confmatchtype = conForm->confmatchtype; + + /* + * We expect the arrays to be 1-D arrays of the right types; verify that. + * We don't need to use deconstruct_array() since the array data is + * just going to look like a C array of values. + */ + adatum = SysCacheGetAttr(CONSTROID, tup, + Anum_pg_constraint_conkey, &isNull); + if (isNull) + elog(ERROR, "null conkey for constraint %u", constraintOid); + arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */ + numkeys = ARR_DIMS(arr)[0]; + if (ARR_NDIM(arr) != 1 || + numkeys < 0 || + numkeys > RI_MAX_NUMKEYS || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != INT2OID) + elog(ERROR, "conkey is not a 1-D smallint array"); + riinfo->nkeys = numkeys; + memcpy(riinfo->fk_attnums, ARR_DATA_PTR(arr), numkeys * sizeof(int16)); + + adatum = SysCacheGetAttr(CONSTROID, tup, + Anum_pg_constraint_confkey, &isNull); + if (isNull) + elog(ERROR, "null confkey for constraint %u", constraintOid); + arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */ + numkeys = ARR_DIMS(arr)[0]; + if (ARR_NDIM(arr) != 1 || + numkeys != riinfo->nkeys || + numkeys > RI_MAX_NUMKEYS || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != INT2OID) + elog(ERROR, "confkey is not a 1-D smallint array"); + memcpy(riinfo->pk_attnums, ARR_DATA_PTR(arr), numkeys * sizeof(int16)); + + adatum = SysCacheGetAttr(CONSTROID, tup, + Anum_pg_constraint_conpfeqop, &isNull); + if (isNull) + elog(ERROR, "null conpfeqop for constraint %u", constraintOid); + arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */ + numkeys = ARR_DIMS(arr)[0]; + if (ARR_NDIM(arr) != 1 || + numkeys != riinfo->nkeys || + numkeys > RI_MAX_NUMKEYS || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != OIDOID) + elog(ERROR, "conpfeqop is not a 1-D Oid array"); + memcpy(riinfo->pf_eq_oprs, ARR_DATA_PTR(arr), numkeys * sizeof(Oid)); + + adatum = SysCacheGetAttr(CONSTROID, tup, + Anum_pg_constraint_conppeqop, &isNull); + if (isNull) + elog(ERROR, "null conppeqop for constraint %u", constraintOid); + arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */ + numkeys = ARR_DIMS(arr)[0]; + if (ARR_NDIM(arr) != 1 || + numkeys != riinfo->nkeys || + numkeys > RI_MAX_NUMKEYS || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != OIDOID) + elog(ERROR, "conppeqop is not a 1-D Oid array"); + memcpy(riinfo->pp_eq_oprs, ARR_DATA_PTR(arr), numkeys * sizeof(Oid)); + + adatum = SysCacheGetAttr(CONSTROID, tup, + Anum_pg_constraint_conffeqop, &isNull); + if (isNull) + elog(ERROR, "null conffeqop for constraint %u", constraintOid); + arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */ + numkeys = ARR_DIMS(arr)[0]; + if (ARR_NDIM(arr) != 1 || + numkeys != riinfo->nkeys || + numkeys > RI_MAX_NUMKEYS || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != OIDOID) + elog(ERROR, "conffeqop is not a 1-D Oid array"); + memcpy(riinfo->ff_eq_oprs, ARR_DATA_PTR(arr), numkeys * sizeof(Oid)); + + ReleaseSysCache(tup); } @@ -3313,52 +3477,30 @@ ri_ReportViolation(RI_QueryKey *qkey, const char *constrname, * Build up a new hashtable key for a prepared SPI plan of a * check for PK rows in noaction triggers. * - * constr_type is FULL - * constr_id is the OID of the pg_trigger row that invoked us - * constr_queryno is an internal number of the query inside the proc - * pk_relid is the OID of referenced relation - * nkeypairs is the number of keypairs - * following are the attribute number keypairs of the trigger invocation + * key: output argument, *key is filled in based on the other arguments + * riinfo: info from pg_constraint entry + * constr_queryno: an internal number of the query inside the proc * * At least for MATCH FULL this builds a unique key per plan. * ---------- */ static void -ri_BuildQueryKeyPkCheck(RI_QueryKey *key, Oid constr_id, int32 constr_queryno, - Relation pk_rel, - int argc, char **argv) +ri_BuildQueryKeyPkCheck(RI_QueryKey *key, const RI_ConstraintInfo *riinfo, + int32 constr_queryno) { int i; - int j; - int fno; - /* - * Initialize the key and fill in type, oid's and number of keypairs - */ - memset((void *) key, 0, sizeof(RI_QueryKey)); - key->constr_type = RI_MATCH_TYPE_FULL; - key->constr_id = constr_id; + MemSet(key, 0, sizeof(RI_QueryKey)); + key->constr_type = FKCONSTR_MATCH_FULL; + key->constr_id = riinfo->constraint_id; key->constr_queryno = constr_queryno; - key->fk_relid = 0; - key->pk_relid = pk_rel->rd_id; - key->nkeypairs = (argc - RI_FIRST_ATTNAME_ARGNO) / 2; - - /* - * Lookup the attribute numbers of the arguments to the trigger call and - * fill in the keypairs. - */ - for (i = 0, j = RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_PK_IDX; j < argc; i++, j += 2) + key->fk_relid = InvalidOid; + key->pk_relid = riinfo->pk_relid; + key->nkeypairs = riinfo->nkeys; + for (i = 0; i < riinfo->nkeys; i++) { - fno = SPI_fnumber(pk_rel->rd_att, argv[j]); - if (fno == SPI_ERROR_NOATTRIBUTE) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("table \"%s\" does not have column \"%s\" referenced by constraint \"%s\"", - RelationGetRelationName(pk_rel), - argv[j], - argv[RI_CONSTRAINT_NAME_ARGNO]))); - key->keypair[i][RI_KEYPAIR_PK_IDX] = fno; key->keypair[i][RI_KEYPAIR_FK_IDX] = 0; + key->keypair[i][RI_KEYPAIR_PK_IDX] = riinfo->pk_attnums[i]; } } @@ -3402,8 +3544,8 @@ ri_NullCheck(Relation rel, HeapTuple tup, RI_QueryKey *key, int pairidx) /* ---------- * ri_InitHashTables - * - * Initialize our internal hash table for prepared - * query plans. + * Initialize our internal hash tables for prepared + * query plans and comparison operators. * ---------- */ static void @@ -3417,6 +3559,13 @@ ri_InitHashTables(void) ctl.hash = tag_hash; ri_query_cache = hash_create("RI query cache", RI_INIT_QUERYHASHSIZE, &ctl, HASH_ELEM | HASH_FUNCTION); + + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(RI_CompareKey); + ctl.entrysize = sizeof(RI_CompareHashEntry); + ctl.hash = tag_hash; + ri_compare_cache = hash_create("RI compare cache", RI_INIT_QUERYHASHSIZE, + &ctl, HASH_ELEM | HASH_FUNCTION); } @@ -3486,38 +3635,49 @@ ri_HashPreparedPlan(RI_QueryKey *key, void *plan) */ static bool ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup, - RI_QueryKey *key, int pairidx) + const RI_ConstraintInfo *riinfo, bool rel_is_pk) { + TupleDesc tupdesc = RelationGetDescr(rel); + const int16 *attnums; + const Oid *eq_oprs; int i; - Oid typeid; - Datum oldvalue; - Datum newvalue; - bool isnull; - for (i = 0; i < key->nkeypairs; i++) + if (rel_is_pk) + { + attnums = riinfo->pk_attnums; + eq_oprs = riinfo->pp_eq_oprs; + } + else + { + attnums = riinfo->fk_attnums; + eq_oprs = riinfo->ff_eq_oprs; + } + + for (i = 0; i < riinfo->nkeys; i++) { + Datum oldvalue; + Datum newvalue; + bool isnull; + /* * Get one attribute's oldvalue. If it is NULL - they're not equal. */ - oldvalue = SPI_getbinval(oldtup, rel->rd_att, - key->keypair[i][pairidx], &isnull); + oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[i], &isnull); if (isnull) return false; /* - * Get one attribute's oldvalue. If it is NULL - they're not equal. + * Get one attribute's newvalue. If it is NULL - they're not equal. */ - newvalue = SPI_getbinval(newtup, rel->rd_att, - key->keypair[i][pairidx], &isnull); + newvalue = SPI_getbinval(newtup, tupdesc, attnums[i], &isnull); if (isnull) return false; /* - * Get the attribute's type OID and call the '=' operator to compare - * the values. + * Compare them with the appropriate equality operator. */ - typeid = SPI_gettypeid(rel->rd_att, key->keypair[i][pairidx]); - if (!ri_AttributesEqual(typeid, oldvalue, newvalue)) + if (!ri_AttributesEqual(eq_oprs[i], RIAttType(rel, attnums[i]), + oldvalue, newvalue)) return false; } @@ -3533,52 +3693,61 @@ ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup, */ static bool ri_AllKeysUnequal(Relation rel, HeapTuple oldtup, HeapTuple newtup, - RI_QueryKey *key, int pairidx) + const RI_ConstraintInfo *riinfo, bool rel_is_pk) { + TupleDesc tupdesc = RelationGetDescr(rel); + const int16 *attnums; + const Oid *eq_oprs; int i; - Oid typeid; - Datum oldvalue; - Datum newvalue; - bool isnull; - bool keys_unequal; - keys_unequal = true; - for (i = 0; keys_unequal && i < key->nkeypairs; i++) + if (rel_is_pk) { + attnums = riinfo->pk_attnums; + eq_oprs = riinfo->pp_eq_oprs; + } + else + { + attnums = riinfo->fk_attnums; + eq_oprs = riinfo->ff_eq_oprs; + } + + for (i = 0; i < riinfo->nkeys; i++) + { + Datum oldvalue; + Datum newvalue; + bool isnull; + /* - * Get one attributes oldvalue. If it is NULL - they're not equal. + * Get one attribute's oldvalue. If it is NULL - they're not equal. */ - oldvalue = SPI_getbinval(oldtup, rel->rd_att, - key->keypair[i][pairidx], &isnull); + oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[i], &isnull); if (isnull) continue; /* - * Get one attributes oldvalue. If it is NULL - they're not equal. + * Get one attribute's newvalue. If it is NULL - they're not equal. */ - newvalue = SPI_getbinval(newtup, rel->rd_att, - key->keypair[i][pairidx], &isnull); + newvalue = SPI_getbinval(newtup, tupdesc, attnums[i], &isnull); if (isnull) continue; /* - * Get the attributes type OID and call the '=' operator to compare - * the values. + * Compare them with the appropriate equality operator. */ - typeid = SPI_gettypeid(rel->rd_att, key->keypair[i][pairidx]); - if (!ri_AttributesEqual(typeid, oldvalue, newvalue)) - continue; - keys_unequal = false; + if (ri_AttributesEqual(eq_oprs[i], RIAttType(rel, attnums[i]), + oldvalue, newvalue)) + return false; /* found two equal items */ } - return keys_unequal; + return true; } /* ---------- * ri_OneKeyEqual - * - * Check if one key value in OLD and NEW is equal. + * Check if one key value in OLD and NEW is equal. Note column is indexed + * from zero. * * ri_KeysEqual could call this but would run a bit slower. For * now, let's duplicate the code. @@ -3586,73 +3755,158 @@ ri_AllKeysUnequal(Relation rel, HeapTuple oldtup, HeapTuple newtup, */ static bool ri_OneKeyEqual(Relation rel, int column, HeapTuple oldtup, HeapTuple newtup, - RI_QueryKey *key, int pairidx) + const RI_ConstraintInfo *riinfo, bool rel_is_pk) { - Oid typeid; + TupleDesc tupdesc = RelationGetDescr(rel); + const int16 *attnums; + const Oid *eq_oprs; Datum oldvalue; Datum newvalue; bool isnull; + if (rel_is_pk) + { + attnums = riinfo->pk_attnums; + eq_oprs = riinfo->pp_eq_oprs; + } + else + { + attnums = riinfo->fk_attnums; + eq_oprs = riinfo->ff_eq_oprs; + } + /* - * Get one attributes oldvalue. If it is NULL - they're not equal. + * Get one attribute's oldvalue. If it is NULL - they're not equal. */ - oldvalue = SPI_getbinval(oldtup, rel->rd_att, - key->keypair[column][pairidx], &isnull); + oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[column], &isnull); if (isnull) return false; /* - * Get one attributes oldvalue. If it is NULL - they're not equal. + * Get one attribute's newvalue. If it is NULL - they're not equal. */ - newvalue = SPI_getbinval(newtup, rel->rd_att, - key->keypair[column][pairidx], &isnull); + newvalue = SPI_getbinval(newtup, tupdesc, attnums[column], &isnull); if (isnull) return false; /* - * Get the attributes type OID and call the '=' operator to compare the - * values. + * Compare them with the appropriate equality operator. */ - typeid = SPI_gettypeid(rel->rd_att, key->keypair[column][pairidx]); - if (!ri_AttributesEqual(typeid, oldvalue, newvalue)) + if (!ri_AttributesEqual(eq_oprs[column], RIAttType(rel, attnums[column]), + oldvalue, newvalue)) return false; return true; } - /* ---------- * ri_AttributesEqual - * - * Call the type specific '=' operator comparison function - * for two values. + * Call the appropriate equality comparison operator for two values. * * NB: we have already checked that neither value is null. * ---------- */ static bool -ri_AttributesEqual(Oid typeid, Datum oldvalue, Datum newvalue) +ri_AttributesEqual(Oid eq_opr, Oid typeid, + Datum oldvalue, Datum newvalue) +{ + RI_CompareHashEntry *entry = ri_HashCompareOp(eq_opr, typeid); + + /* Do we need to cast the values? */ + if (OidIsValid(entry->cast_func_finfo.fn_oid)) + { + oldvalue = FunctionCall3(&entry->cast_func_finfo, + oldvalue, + Int32GetDatum(-1), /* typmod */ + BoolGetDatum(false)); /* implicit coercion */ + newvalue = FunctionCall3(&entry->cast_func_finfo, + newvalue, + Int32GetDatum(-1), /* typmod */ + BoolGetDatum(false)); /* implicit coercion */ + } + + /* Apply the comparison operator */ + return DatumGetBool(FunctionCall2(&entry->eq_opr_finfo, + oldvalue, newvalue)); +} + +/* ---------- + * ri_HashCompareOp - + * + * See if we know how to compare two values, and create a new hash entry + * if not. + * ---------- + */ +static RI_CompareHashEntry * +ri_HashCompareOp(Oid eq_opr, Oid typeid) { - TypeCacheEntry *typentry; + RI_CompareKey key; + RI_CompareHashEntry *entry; + bool found; /* - * Find the data type in the typcache, and ask for eq_opr info. + * On the first call initialize the hashtable */ - typentry = lookup_type_cache(typeid, TYPECACHE_EQ_OPR_FINFO); + if (!ri_compare_cache) + ri_InitHashTables(); - if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("could not identify an equality operator for type %s", - format_type_be(typeid)))); + /* + * Find or create a hash entry. Note we're assuming RI_CompareKey + * contains no struct padding. + */ + key.eq_opr = eq_opr; + key.typeid = typeid; + entry = (RI_CompareHashEntry *) hash_search(ri_compare_cache, + (void *) &key, + HASH_ENTER, &found); + if (!found) + entry->valid = false; /* - * Call the type specific '=' function + * If not already initialized, do so. Since we'll keep this hash entry + * for the life of the backend, put any subsidiary info for the function + * cache structs into TopMemoryContext. */ - return DatumGetBool(FunctionCall2(&(typentry->eq_opr_finfo), - oldvalue, newvalue)); + if (!entry->valid) + { + Oid lefttype, + righttype, + castfunc; + + /* We always need to know how to call the equality operator */ + fmgr_info_cxt(get_opcode(eq_opr), &entry->eq_opr_finfo, + TopMemoryContext); + + /* + * If we chose to use a cast from FK to PK type, we may have to + * apply the cast function to get to the operator's input type. + */ + op_input_types(eq_opr, &lefttype, &righttype); + Assert(lefttype == righttype); + if (typeid == lefttype) + castfunc = InvalidOid; /* simplest case */ + else if (!find_coercion_pathway(lefttype, typeid, COERCION_IMPLICIT, + &castfunc)) + { + /* If target is ANYARRAY, assume it's OK, else punt. */ + if (lefttype != ANYARRAYOID) + elog(ERROR, "no conversion function from %s to %s", + format_type_be(typeid), + format_type_be(lefttype)); + } + if (OidIsValid(castfunc)) + fmgr_info_cxt(castfunc, &entry->cast_func_finfo, + TopMemoryContext); + else + entry->cast_func_finfo.fn_oid = InvalidOid; + entry->valid = true; + } + + return entry; } + /* * Given a trigger function OID, determine whether it is an RI trigger, * and if so whether it is attached to PK or FK relation. |