#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#define RI_MAX_NUMKEYS INDEX_MAX_KEYS
-#define RI_INIT_QUERYHASHSIZE 128
+#define RI_INIT_CONSTRAINTHASHSIZE 64
+#define RI_INIT_QUERYHASHSIZE (RI_INIT_CONSTRAINTHASHSIZE * 4)
#define RI_KEYS_ALL_NULL 0
#define RI_KEYS_SOME_NULL 1
/* ----------
* RI_ConstraintInfo
*
- * Information extracted from an FK pg_constraint entry.
+ * Information extracted from an FK pg_constraint entry. This is cached in
+ * ri_constraint_cache.
* ----------
*/
typedef struct RI_ConstraintInfo
{
- Oid constraint_id; /* OID of pg_constraint entry */
+ Oid constraint_id; /* OID of pg_constraint entry (hash key) */
+ bool valid; /* successfully initialized? */
+ uint32 oidHashValue; /* hash value of pg_constraint OID */
NameData conname; /* name of the FK constraint */
Oid pk_relid; /* referenced relation */
Oid fk_relid; /* referencing relation */
* Local data
* ----------
*/
+static HTAB *ri_constraint_cache = NULL;
static HTAB *ri_query_cache = NULL;
static HTAB *ri_compare_cache = NULL;
Datum oldvalue, Datum newvalue);
static void ri_InitHashTables(void);
+static void InvalidateConstraintCacheCallBack(Datum arg, int cacheid, uint32 hashvalue);
static SPIPlanPtr ri_FetchPreparedPlan(RI_QueryKey *key);
static void ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr 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 const RI_ConstraintInfo *ri_FetchConstraintInfo(Trigger *trigger,
+ Relation trig_rel, bool rel_is_pk);
+static const RI_ConstraintInfo *ri_LoadConstraintInfo(Oid constraintOid);
static SPIPlanPtr ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel,
bool cache_plan);
static Datum
RI_FKey_check(TriggerData *trigdata)
{
- RI_ConstraintInfo riinfo;
+ const RI_ConstraintInfo *riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple new_row;
/*
* Get arguments.
*/
- ri_FetchConstraintInfo(&riinfo,
- trigdata->tg_trigger, trigdata->tg_relation, false);
+ riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
+ trigdata->tg_relation, false);
if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
{
* SELECT FOR SHARE will get on it.
*/
fk_rel = trigdata->tg_relation;
- pk_rel = heap_open(riinfo.pk_relid, RowShareLock);
+ pk_rel = heap_open(riinfo->pk_relid, RowShareLock);
/* ----------
* SQL:2008 4.17.3 <Table constraints>
* standard too); it's just there for future enhancements.
* ----------
*/
- if (riinfo.nkeys == 0)
+ if (riinfo->nkeys == 0)
{
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed");
- ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_CHECK_LOOKUPPK);
+ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
/*
* Execute the plan
*/
- ri_PerformCheck(&riinfo, &qkey, qplan,
+ ri_PerformCheck(riinfo, &qkey, qplan,
fk_rel, pk_rel,
NULL, NULL,
false,
return PointerGetDatum(NULL);
}
- if (riinfo.confmatchtype == FKCONSTR_MATCH_PARTIAL)
+ if (riinfo->confmatchtype == FKCONSTR_MATCH_PARTIAL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
- switch (ri_NullCheck(new_row, &riinfo, false))
+ switch (ri_NullCheck(new_row, riinfo, false))
{
case RI_KEYS_ALL_NULL:
* This is the only case that differs between the three kinds of
* MATCH.
*/
- switch (riinfo.confmatchtype)
+ switch (riinfo->confmatchtype)
{
case FKCONSTR_MATCH_FULL:
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
RelationGetRelationName(trigdata->tg_relation),
- NameStr(riinfo.conname)),
+ NameStr(riinfo->conname)),
errdetail("MATCH FULL does not allow mixing of null and nonnull key values.")));
heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
default:
elog(ERROR, "unrecognized confmatchtype: %d",
- riinfo.confmatchtype);
+ riinfo->confmatchtype);
break;
}
/*
* Fetch or prepare a saved plan for the real check
*/
- ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_CHECK_LOOKUPPK);
+ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
quoteRelationName(pkrelname, pk_rel);
appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname);
querysep = "WHERE";
- for (i = 0; i < riinfo.nkeys; 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]);
+ Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
+ Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
quoteOneName(attname,
- RIAttName(pk_rel, riinfo.pk_attnums[i]));
+ RIAttName(pk_rel, riinfo->pk_attnums[i]));
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&querybuf, querysep,
attname, pk_type,
- riinfo.pf_eq_oprs[i],
+ riinfo->pf_eq_oprs[i],
paramname, fk_type);
querysep = "AND";
queryoids[i] = fk_type;
appendStringInfo(&querybuf, " FOR SHARE OF x");
/* Prepare and save the plan */
- qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
/*
* Now check that foreign key exists in PK table
*/
- ri_PerformCheck(&riinfo, &qkey, qplan,
+ ri_PerformCheck(riinfo, &qkey, qplan,
fk_rel, pk_rel,
NULL, new_row,
false,
static Datum
ri_restrict_del(TriggerData *trigdata, bool is_no_action)
{
- RI_ConstraintInfo riinfo;
+ const RI_ConstraintInfo *riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple old_row;
/*
* Get arguments.
*/
- ri_FetchConstraintInfo(&riinfo,
- trigdata->tg_trigger, trigdata->tg_relation, true);
+ riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
+ trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
- if (riinfo.nkeys == 0)
+ if (riinfo->nkeys == 0)
return PointerGetDatum(NULL);
/*
* fk_rel is opened in RowShareLock mode since that's what our eventual
* SELECT FOR SHARE will get on it.
*/
- fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
+ fk_rel = heap_open(riinfo->fk_relid, RowShareLock);
pk_rel = trigdata->tg_relation;
old_row = trigdata->tg_trigtuple;
- switch (riinfo.confmatchtype)
+ switch (riinfo->confmatchtype)
{
/* ----------
* SQL:2008 15.17 <Execution of referential actions>
*/
case FKCONSTR_MATCH_SIMPLE:
case FKCONSTR_MATCH_FULL:
- switch (ri_NullCheck(old_row, &riinfo, true))
+ switch (ri_NullCheck(old_row, riinfo, true))
{
case RI_KEYS_ALL_NULL:
case RI_KEYS_SOME_NULL:
* allow another row to be substituted.
*/
if (is_no_action &&
- ri_Check_Pk_Match(pk_rel, fk_rel, old_row, &riinfo))
+ ri_Check_Pk_Match(pk_rel, fk_rel, old_row, riinfo))
{
heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
/*
* Fetch or prepare a saved plan for the restrict delete lookup
*/
- ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_RESTRICT_DEL_CHECKREF);
+ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_RESTRICT_DEL_CHECKREF);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
fkrelname);
querysep = "WHERE";
- for (i = 0; i < riinfo.nkeys; 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]);
+ 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]));
+ RIAttName(fk_rel, riinfo->fk_attnums[i]));
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
- riinfo.pf_eq_oprs[i],
+ riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = "AND";
queryoids[i] = pk_type;
appendStringInfo(&querybuf, " FOR SHARE OF x");
/* Prepare and save the plan */
- qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
/*
* We have a plan now. Run it to check for existing references.
*/
- ri_PerformCheck(&riinfo, &qkey, qplan,
+ ri_PerformCheck(riinfo, &qkey, qplan,
fk_rel, pk_rel,
old_row, NULL,
true, /* must detect new rows */
default:
elog(ERROR, "unrecognized confmatchtype: %d",
- riinfo.confmatchtype);
+ riinfo->confmatchtype);
break;
}
static Datum
ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
{
- RI_ConstraintInfo riinfo;
+ const RI_ConstraintInfo *riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple new_row;
/*
* Get arguments.
*/
- ri_FetchConstraintInfo(&riinfo,
- trigdata->tg_trigger, trigdata->tg_relation, true);
+ riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
+ trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
- if (riinfo.nkeys == 0)
+ if (riinfo->nkeys == 0)
return PointerGetDatum(NULL);
/*
* fk_rel is opened in RowShareLock mode since that's what our eventual
* SELECT FOR SHARE will get on it.
*/
- fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
+ fk_rel = heap_open(riinfo->fk_relid, RowShareLock);
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
- switch (riinfo.confmatchtype)
+ switch (riinfo->confmatchtype)
{
/* ----------
* SQL:2008 15.17 <Execution of referential actions>
*/
case FKCONSTR_MATCH_SIMPLE:
case FKCONSTR_MATCH_FULL:
- switch (ri_NullCheck(old_row, &riinfo, true))
+ switch (ri_NullCheck(old_row, riinfo, true))
{
case RI_KEYS_ALL_NULL:
case RI_KEYS_SOME_NULL:
/*
* No need to check anything if old and new keys are equal
*/
- if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
+ if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true))
{
heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
* allow another row to be substituted.
*/
if (is_no_action &&
- ri_Check_Pk_Match(pk_rel, fk_rel, old_row, &riinfo))
+ ri_Check_Pk_Match(pk_rel, fk_rel, old_row, riinfo))
{
heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
/*
* Fetch or prepare a saved plan for the restrict update lookup
*/
- ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_RESTRICT_UPD_CHECKREF);
+ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_RESTRICT_UPD_CHECKREF);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
fkrelname);
querysep = "WHERE";
- for (i = 0; i < riinfo.nkeys; 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]);
+ 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]));
+ RIAttName(fk_rel, riinfo->fk_attnums[i]));
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
- riinfo.pf_eq_oprs[i],
+ riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = "AND";
queryoids[i] = pk_type;
appendStringInfo(&querybuf, " FOR SHARE OF x");
/* Prepare and save the plan */
- qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
/*
* We have a plan now. Run it to check for existing references.
*/
- ri_PerformCheck(&riinfo, &qkey, qplan,
+ ri_PerformCheck(riinfo, &qkey, qplan,
fk_rel, pk_rel,
old_row, NULL,
true, /* must detect new rows */
default:
elog(ERROR, "unrecognized confmatchtype: %d",
- riinfo.confmatchtype);
+ riinfo->confmatchtype);
break;
}
RI_FKey_cascade_del(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
- RI_ConstraintInfo riinfo;
+ const RI_ConstraintInfo *riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple old_row;
/*
* Get arguments.
*/
- ri_FetchConstraintInfo(&riinfo,
- trigdata->tg_trigger, trigdata->tg_relation, true);
+ riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
+ trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
- if (riinfo.nkeys == 0)
+ if (riinfo->nkeys == 0)
return PointerGetDatum(NULL);
/*
* fk_rel is opened in RowExclusiveLock mode since that's what our
* eventual DELETE will get on it.
*/
- fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
+ fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
old_row = trigdata->tg_trigtuple;
- switch (riinfo.confmatchtype)
+ switch (riinfo->confmatchtype)
{
/* ----------
* SQL:2008 15.17 <Execution of referential actions>
*/
case FKCONSTR_MATCH_SIMPLE:
case FKCONSTR_MATCH_FULL:
- switch (ri_NullCheck(old_row, &riinfo, true))
+ switch (ri_NullCheck(old_row, riinfo, true))
{
case RI_KEYS_ALL_NULL:
case RI_KEYS_SOME_NULL:
/*
* Fetch or prepare a saved plan for the cascaded delete
*/
- ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_CASCADE_DEL_DODELETE);
+ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_DEL_DODELETE);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "DELETE FROM ONLY %s", fkrelname);
querysep = "WHERE";
- for (i = 0; i < riinfo.nkeys; 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]);
+ 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]));
+ RIAttName(fk_rel, riinfo->fk_attnums[i]));
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
- riinfo.pf_eq_oprs[i],
+ riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = "AND";
queryoids[i] = pk_type;
}
/* Prepare and save the plan */
- qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
* We have a plan now. Build up the arguments from the key values
* in the deleted PK tuple and delete the referencing rows
*/
- ri_PerformCheck(&riinfo, &qkey, qplan,
+ ri_PerformCheck(riinfo, &qkey, qplan,
fk_rel, pk_rel,
old_row, NULL,
true, /* must detect new rows */
default:
elog(ERROR, "unrecognized confmatchtype: %d",
- riinfo.confmatchtype);
+ riinfo->confmatchtype);
break;
}
RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
- RI_ConstraintInfo riinfo;
+ const RI_ConstraintInfo *riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple new_row;
/*
* Get arguments.
*/
- ri_FetchConstraintInfo(&riinfo,
- trigdata->tg_trigger, trigdata->tg_relation, true);
+ riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
+ trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
- if (riinfo.nkeys == 0)
+ if (riinfo->nkeys == 0)
return PointerGetDatum(NULL);
/*
* fk_rel is opened in RowExclusiveLock mode since that's what our
* eventual UPDATE will get on it.
*/
- fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
+ fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
- switch (riinfo.confmatchtype)
+ switch (riinfo->confmatchtype)
{
/* ----------
* SQL:2008 15.17 <Execution of referential actions>
*/
case FKCONSTR_MATCH_SIMPLE:
case FKCONSTR_MATCH_FULL:
- switch (ri_NullCheck(old_row, &riinfo, true))
+ switch (ri_NullCheck(old_row, riinfo, true))
{
case RI_KEYS_ALL_NULL:
case RI_KEYS_SOME_NULL:
/*
* No need to do anything if old and new keys are equal
*/
- if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
+ if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true))
{
heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
/*
* Fetch or prepare a saved plan for the cascaded update
*/
- ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_CASCADE_UPD_DOUPDATE);
+ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_UPD_DOUPDATE);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
querysep = "";
qualsep = "WHERE";
- for (i = 0, j = riinfo.nkeys; i < riinfo.nkeys; 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]);
+ 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]));
+ 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],
+ riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = ",";
qualsep = "AND";
appendStringInfoString(&querybuf, qualbuf.data);
/* Prepare and save the plan */
- qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys * 2, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys * 2, queryoids,
&qkey, fk_rel, pk_rel, true);
}
/*
* We have a plan now. Run it to update the existing references.
*/
- ri_PerformCheck(&riinfo, &qkey, qplan,
+ ri_PerformCheck(riinfo, &qkey, qplan,
fk_rel, pk_rel,
old_row, new_row,
true, /* must detect new rows */
default:
elog(ERROR, "unrecognized confmatchtype: %d",
- riinfo.confmatchtype);
+ riinfo->confmatchtype);
break;
}
RI_FKey_setnull_del(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
- RI_ConstraintInfo riinfo;
+ const RI_ConstraintInfo *riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple old_row;
/*
* Get arguments.
*/
- ri_FetchConstraintInfo(&riinfo,
- trigdata->tg_trigger, trigdata->tg_relation, true);
+ riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
+ trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
- if (riinfo.nkeys == 0)
+ if (riinfo->nkeys == 0)
return PointerGetDatum(NULL);
/*
* fk_rel is opened in RowExclusiveLock mode since that's what our
* eventual UPDATE will get on it.
*/
- fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
+ fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
old_row = trigdata->tg_trigtuple;
- switch (riinfo.confmatchtype)
+ switch (riinfo->confmatchtype)
{
/* ----------
* SQL:2008 15.17 <Execution of referential actions>
*/
case FKCONSTR_MATCH_SIMPLE:
case FKCONSTR_MATCH_FULL:
- switch (ri_NullCheck(old_row, &riinfo, true))
+ switch (ri_NullCheck(old_row, riinfo, true))
{
case RI_KEYS_ALL_NULL:
case RI_KEYS_SOME_NULL:
/*
* Fetch or prepare a saved plan for the set null delete operation
*/
- ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_SETNULL_DEL_DOUPDATE);
+ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETNULL_DEL_DOUPDATE);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
querysep = "";
qualsep = "WHERE";
- for (i = 0; i < riinfo.nkeys; 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]);
+ 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]));
+ 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],
+ riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = ",";
qualsep = "AND";
appendStringInfoString(&querybuf, qualbuf.data);
/* Prepare and save the plan */
- qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
/*
* We have a plan now. Run it to check for existing references.
*/
- ri_PerformCheck(&riinfo, &qkey, qplan,
+ ri_PerformCheck(riinfo, &qkey, qplan,
fk_rel, pk_rel,
old_row, NULL,
true, /* must detect new rows */
default:
elog(ERROR, "unrecognized confmatchtype: %d",
- riinfo.confmatchtype);
+ riinfo->confmatchtype);
break;
}
RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
- RI_ConstraintInfo riinfo;
+ const RI_ConstraintInfo *riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple new_row;
/*
* Get arguments.
*/
- ri_FetchConstraintInfo(&riinfo,
- trigdata->tg_trigger, trigdata->tg_relation, true);
+ riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
+ trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
- if (riinfo.nkeys == 0)
+ if (riinfo->nkeys == 0)
return PointerGetDatum(NULL);
/*
* fk_rel is opened in RowExclusiveLock mode since that's what our
* eventual UPDATE will get on it.
*/
- fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
+ fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
- switch (riinfo.confmatchtype)
+ switch (riinfo->confmatchtype)
{
/* ----------
* SQL:2008 15.17 <Execution of referential actions>
*/
case FKCONSTR_MATCH_SIMPLE:
case FKCONSTR_MATCH_FULL:
- switch (ri_NullCheck(old_row, &riinfo, true))
+ switch (ri_NullCheck(old_row, riinfo, true))
{
case RI_KEYS_ALL_NULL:
case RI_KEYS_SOME_NULL:
/*
* No need to do anything if old and new keys are equal
*/
- if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
+ if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true))
{
heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
/*
* Fetch or prepare a saved plan for the set null update operation
*/
- ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_SETNULL_UPD_DOUPDATE);
+ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETNULL_UPD_DOUPDATE);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
querysep = "";
qualsep = "WHERE";
- for (i = 0; i < riinfo.nkeys; 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]);
+ 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]));
+ 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],
+ riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = ",";
qualsep = "AND";
appendStringInfoString(&querybuf, qualbuf.data);
/* Prepare and save the plan */
- qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
/*
* We have a plan now. Run it to update the existing references.
*/
- ri_PerformCheck(&riinfo, &qkey, qplan,
+ ri_PerformCheck(riinfo, &qkey, qplan,
fk_rel, pk_rel,
old_row, NULL,
true, /* must detect new rows */
default:
elog(ERROR, "unrecognized confmatchtype: %d",
- riinfo.confmatchtype);
+ riinfo->confmatchtype);
break;
}
RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
- RI_ConstraintInfo riinfo;
+ const RI_ConstraintInfo *riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple old_row;
/*
* Get arguments.
*/
- ri_FetchConstraintInfo(&riinfo,
- trigdata->tg_trigger, trigdata->tg_relation, true);
+ riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
+ trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
- if (riinfo.nkeys == 0)
+ if (riinfo->nkeys == 0)
return PointerGetDatum(NULL);
/*
* fk_rel is opened in RowExclusiveLock mode since that's what our
* eventual UPDATE will get on it.
*/
- fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
+ fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
old_row = trigdata->tg_trigtuple;
- switch (riinfo.confmatchtype)
+ switch (riinfo->confmatchtype)
{
/* ----------
* SQL:2008 15.17 <Execution of referential actions>
*/
case FKCONSTR_MATCH_SIMPLE:
case FKCONSTR_MATCH_FULL:
- switch (ri_NullCheck(old_row, &riinfo, true))
+ switch (ri_NullCheck(old_row, riinfo, true))
{
case RI_KEYS_ALL_NULL:
case RI_KEYS_SOME_NULL:
* Fetch or prepare a saved plan for the set default delete
* operation
*/
- ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_SETDEFAULT_DEL_DOUPDATE);
+ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETDEFAULT_DEL_DOUPDATE);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
querysep = "";
qualsep = "WHERE";
- for (i = 0; i < riinfo.nkeys; 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]);
+ 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]));
+ 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],
+ riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = ",";
qualsep = "AND";
appendStringInfoString(&querybuf, qualbuf.data);
/* Prepare and save the plan */
- qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
/*
* We have a plan now. Run it to update the existing references.
*/
- ri_PerformCheck(&riinfo, &qkey, qplan,
+ ri_PerformCheck(riinfo, &qkey, qplan,
fk_rel, pk_rel,
old_row, NULL,
true, /* must detect new rows */
default:
elog(ERROR, "unrecognized confmatchtype: %d",
- riinfo.confmatchtype);
+ riinfo->confmatchtype);
break;
}
RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
- RI_ConstraintInfo riinfo;
+ const RI_ConstraintInfo *riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple new_row;
/*
* Get arguments.
*/
- ri_FetchConstraintInfo(&riinfo,
- trigdata->tg_trigger, trigdata->tg_relation, true);
+ riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
+ trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
- if (riinfo.nkeys == 0)
+ if (riinfo->nkeys == 0)
return PointerGetDatum(NULL);
/*
* fk_rel is opened in RowExclusiveLock mode since that's what our
* eventual UPDATE will get on it.
*/
- fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
+ fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
- switch (riinfo.confmatchtype)
+ switch (riinfo->confmatchtype)
{
/* ----------
* SQL:2008 15.17 <Execution of referential actions>
*/
case FKCONSTR_MATCH_SIMPLE:
case FKCONSTR_MATCH_FULL:
- switch (ri_NullCheck(old_row, &riinfo, true))
+ switch (ri_NullCheck(old_row, riinfo, true))
{
case RI_KEYS_ALL_NULL:
case RI_KEYS_SOME_NULL:
/*
* No need to do anything if old and new keys are equal
*/
- if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
+ if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true))
{
heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
* Fetch or prepare a saved plan for the set default update
* operation
*/
- ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_SETDEFAULT_UPD_DOUPDATE);
+ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETDEFAULT_UPD_DOUPDATE);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
querysep = "";
qualsep = "WHERE";
- for (i = 0; i < riinfo.nkeys; 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]);
+ 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]));
+ 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],
+ riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = ",";
qualsep = "AND";
appendStringInfoString(&querybuf, qualbuf.data);
/* Prepare and save the plan */
- qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
/*
* We have a plan now. Run it to update the existing references.
*/
- ri_PerformCheck(&riinfo, &qkey, qplan,
+ ri_PerformCheck(riinfo, &qkey, qplan,
fk_rel, pk_rel,
old_row, NULL,
true, /* must detect new rows */
default:
elog(ERROR, "unrecognized confmatchtype: %d",
- riinfo.confmatchtype);
+ riinfo->confmatchtype);
break;
}
RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel,
HeapTuple old_row, HeapTuple new_row)
{
- RI_ConstraintInfo riinfo;
+ const RI_ConstraintInfo *riinfo;
/*
* Get arguments.
*/
- ri_FetchConstraintInfo(&riinfo, trigger, pk_rel, true);
+ riinfo = ri_FetchConstraintInfo(trigger, pk_rel, true);
/*
* Nothing to do if no columns (satisfaction of such a constraint only
* requires existence of a PK row, and this update won't change that).
*/
- if (riinfo.nkeys == 0)
+ if (riinfo->nkeys == 0)
return false;
- switch (riinfo.confmatchtype)
+ switch (riinfo->confmatchtype)
{
case FKCONSTR_MATCH_SIMPLE:
case FKCONSTR_MATCH_FULL:
* If any old key value is NULL, the row could not have been
* referenced by an FK row, so no check is needed.
*/
- if (ri_NullCheck(old_row, &riinfo, true) != RI_KEYS_NONE_NULL)
+ if (ri_NullCheck(old_row, riinfo, true) != RI_KEYS_NONE_NULL)
return false;
/* If all old and new key values are equal, no check is needed */
- if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
+ if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true))
return false;
/* Else we need to fire the trigger. */
default:
elog(ERROR, "unrecognized confmatchtype: %d",
- riinfo.confmatchtype);
+ riinfo->confmatchtype);
break;
}
RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
HeapTuple old_row, HeapTuple new_row)
{
- RI_ConstraintInfo riinfo;
+ const RI_ConstraintInfo *riinfo;
/*
* Get arguments.
*/
- ri_FetchConstraintInfo(&riinfo, trigger, fk_rel, false);
+ riinfo = ri_FetchConstraintInfo(trigger, fk_rel, false);
/*
* Nothing to do if no columns (satisfaction of such a constraint only
* requires existence of a PK row, and this update won't change that).
*/
- if (riinfo.nkeys == 0)
+ if (riinfo->nkeys == 0)
return false;
- switch (riinfo.confmatchtype)
+ switch (riinfo->confmatchtype)
{
case FKCONSTR_MATCH_SIMPLE:
/*
* If any new key value is NULL, the row must satisfy the
* constraint, so no check is needed.
*/
- if (ri_NullCheck(new_row, &riinfo, false) != RI_KEYS_NONE_NULL)
+ if (ri_NullCheck(new_row, riinfo, false) != RI_KEYS_NONE_NULL)
return false;
/*
return true;
/* If all old and new key values are equal, no check is needed */
- if (ri_KeysEqual(fk_rel, old_row, new_row, &riinfo, false))
+ if (ri_KeysEqual(fk_rel, old_row, new_row, riinfo, false))
return false;
/* Else we need to fire the trigger. */
* invalidated before the constraint is to be checked, but we
* should queue the event to apply the check later.
*/
- switch (ri_NullCheck(new_row, &riinfo, false))
+ switch (ri_NullCheck(new_row, riinfo, false))
{
case RI_KEYS_ALL_NULL:
return false;
return true;
/* If all old and new key values are equal, no check is needed */
- if (ri_KeysEqual(fk_rel, old_row, new_row, &riinfo, false))
+ if (ri_KeysEqual(fk_rel, old_row, new_row, riinfo, false))
return false;
/* Else we need to fire the trigger. */
default:
elog(ERROR, "unrecognized confmatchtype: %d",
- riinfo.confmatchtype);
+ riinfo->confmatchtype);
break;
}
bool
RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
{
- RI_ConstraintInfo riinfo;
+ const RI_ConstraintInfo *riinfo;
StringInfoData querybuf;
char pkrelname[MAX_QUOTED_REL_NAME_LEN];
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
SPIPlanPtr qplan;
/* Fetch constraint info. */
- ri_FetchConstraintInfo(&riinfo, trigger, fk_rel, false);
+ riinfo = ri_FetchConstraintInfo(trigger, fk_rel, false);
/*
* Check to make sure current user has enough permissions to do the test
fkrte->relkind = fk_rel->rd_rel->relkind;
fkrte->requiredPerms = ACL_SELECT;
- for (i = 0; i < riinfo.nkeys; i++)
+ for (i = 0; i < riinfo->nkeys; i++)
{
int attno;
- attno = riinfo.pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
+ attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
- attno = riinfo.fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
+ attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
}
initStringInfo(&querybuf);
appendStringInfo(&querybuf, "SELECT ");
sep = "";
- for (i = 0; i < riinfo.nkeys; i++)
+ for (i = 0; i < riinfo->nkeys; i++)
{
quoteOneName(fkattname,
- RIAttName(fk_rel, riinfo.fk_attnums[i]));
+ RIAttName(fk_rel, riinfo->fk_attnums[i]));
appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
sep = ", ";
}
strcpy(pkattname, "pk.");
strcpy(fkattname, "fk.");
sep = "(";
- for (i = 0; i < riinfo.nkeys; 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]);
- Oid pk_coll = RIAttCollation(pk_rel, riinfo.pk_attnums[i]);
- Oid fk_coll = RIAttCollation(fk_rel, riinfo.fk_attnums[i]);
+ Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
+ Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
+ Oid pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
+ Oid fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
quoteOneName(pkattname + 3,
- RIAttName(pk_rel, riinfo.pk_attnums[i]));
+ RIAttName(pk_rel, riinfo->pk_attnums[i]));
quoteOneName(fkattname + 3,
- RIAttName(fk_rel, riinfo.fk_attnums[i]));
+ RIAttName(fk_rel, riinfo->fk_attnums[i]));
ri_GenerateQual(&querybuf, sep,
pkattname, pk_type,
- riinfo.pf_eq_oprs[i],
+ riinfo->pf_eq_oprs[i],
fkattname, fk_type);
if (pk_coll != fk_coll)
ri_GenerateQualCollation(&querybuf, pk_coll);
* It's sufficient to test any one pk attribute for null to detect a join
* failure.
*/
- quoteOneName(pkattname, RIAttName(pk_rel, riinfo.pk_attnums[0]));
+ quoteOneName(pkattname, RIAttName(pk_rel, riinfo->pk_attnums[0]));
appendStringInfo(&querybuf, ") WHERE pk.%s IS NULL AND (", pkattname);
sep = "";
- for (i = 0; i < riinfo.nkeys; i++)
+ for (i = 0; i < riinfo->nkeys; i++)
{
- quoteOneName(fkattname, RIAttName(fk_rel, riinfo.fk_attnums[i]));
+ quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
appendStringInfo(&querybuf,
"%sfk.%s IS NOT NULL",
sep, fkattname);
- switch (riinfo.confmatchtype)
+ switch (riinfo->confmatchtype)
{
case FKCONSTR_MATCH_SIMPLE:
sep = " AND ";
break;
default:
elog(ERROR, "unrecognized confmatchtype: %d",
- riinfo.confmatchtype);
+ riinfo->confmatchtype);
break;
}
}
{
HeapTuple tuple = SPI_tuptable->vals[0];
TupleDesc tupdesc = SPI_tuptable->tupdesc;
+ RI_ConstraintInfo fake_riinfo;
/*
* The columns to look at in the result tuple are 1..N, not whatever
* ri_ReportViolation, overriding its normal habit of using the pk_rel
* or fk_rel's tupdesc.
*/
- for (i = 0; i < riinfo.nkeys; i++)
- riinfo.fk_attnums[i] = i + 1;
+ memcpy(&fake_riinfo, riinfo, sizeof(RI_ConstraintInfo));
+ for (i = 0; i < fake_riinfo.nkeys; i++)
+ fake_riinfo.fk_attnums[i] = i + 1;
/*
* If it's MATCH FULL, and there are any nulls in the FK keys,
* complain about that rather than the lack of a match. MATCH FULL
* disallows partially-null FK rows.
*/
- if (riinfo.confmatchtype == FKCONSTR_MATCH_FULL &&
- ri_NullCheck(tuple, &riinfo, false) != RI_KEYS_NONE_NULL)
+ if (fake_riinfo.confmatchtype == FKCONSTR_MATCH_FULL &&
+ ri_NullCheck(tuple, &fake_riinfo, false) != RI_KEYS_NONE_NULL)
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
RelationGetRelationName(fk_rel),
- NameStr(riinfo.conname)),
+ NameStr(fake_riinfo.conname)),
errdetail("MATCH FULL does not allow mixing of null and nonnull key values.")));
/*
* We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK
- * query, which isn't true, but will cause it to use riinfo.fk_attnums
- * as we need.
+ * query, which isn't true, but will cause it to use
+ * fake_riinfo.fk_attnums as we need.
*/
- ri_ReportViolation(&riinfo,
+ ri_ReportViolation(&fake_riinfo,
pk_rel, fk_rel,
tuple, tupdesc,
RI_PLAN_CHECK_LOOKUPPK, false);
/*
- * Fetch the pg_constraint entry for the FK constraint, and fill *riinfo
+ * Fetch the RI_ConstraintInfo struct for the trigger's FK constraint.
*/
-static void
-ri_FetchConstraintInfo(RI_ConstraintInfo *riinfo,
- Trigger *trigger, Relation trig_rel, bool rel_is_pk)
+static const RI_ConstraintInfo *
+ri_FetchConstraintInfo(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;
+ const RI_ConstraintInfo *riinfo;
/*
* Check that the FK constraint's OID is available; it might not be if
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 = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintOid));
- if (!HeapTupleIsValid(tup)) /* should not happen */
- elog(ERROR, "cache lookup failed for constraint %u", constraintOid);
- conForm = (Form_pg_constraint) GETSTRUCT(tup);
+ /* Find or create a hashtable entry for the constraint */
+ riinfo = ri_LoadConstraintInfo(constraintOid);
/* 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))
+ if (riinfo->fk_relid != trigger->tgconstrrelid ||
+ riinfo->pk_relid != 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)
+ if (riinfo->fk_relid != RelationGetRelid(trig_rel) ||
+ riinfo->pk_relid != trigger->tgconstrrelid)
elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"",
trigger->tgname, RelationGetRelationName(trig_rel));
}
+ return riinfo;
+}
+
+/*
+ * Fetch or create the RI_ConstraintInfo struct for an FK constraint.
+ */
+static const RI_ConstraintInfo *
+ri_LoadConstraintInfo(Oid constraintOid)
+{
+ RI_ConstraintInfo *riinfo;
+ bool found;
+ HeapTuple tup;
+ Form_pg_constraint conForm;
+ Datum adatum;
+ bool isNull;
+ ArrayType *arr;
+ int numkeys;
+
+ /*
+ * On the first call initialize the hashtable
+ */
+ if (!ri_constraint_cache)
+ ri_InitHashTables();
+
+ /*
+ * Find or create a hash entry. If we find a valid one, just return it.
+ */
+ riinfo = (RI_ConstraintInfo *) hash_search(ri_constraint_cache,
+ (void *) &constraintOid,
+ HASH_ENTER, &found);
+ if (!found)
+ riinfo->valid = false;
+ else if (riinfo->valid)
+ return riinfo;
+
+ /*
+ * Fetch the pg_constraint row so we can fill in the entry.
+ */
+ tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintOid));
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for constraint %u", constraintOid);
+ conForm = (Form_pg_constraint) GETSTRUCT(tup);
+
+ if (conForm->contype != CONSTRAINT_FOREIGN) /* should not happen */
+ elog(ERROR, "constraint %u is not a foreign key constraint",
+ constraintOid);
+
/* And extract data */
- riinfo->constraint_id = constraintOid;
+ Assert(riinfo->constraint_id == constraintOid);
+ riinfo->oidHashValue = GetSysCacheHashValue1(CONSTROID,
+ ObjectIdGetDatum(constraintOid));
memcpy(&riinfo->conname, &conForm->conname, sizeof(NameData));
riinfo->pk_relid = conForm->confrelid;
riinfo->fk_relid = conForm->conrelid;
pfree(arr); /* free de-toasted copy, if any */
ReleaseSysCache(tup);
+
+ riinfo->valid = true;
+
+ return riinfo;
+}
+
+/*
+ * Callback for pg_constraint inval events
+ *
+ * While most syscache callbacks just flush all their entries, pg_constraint
+ * gets enough update traffic that it's probably worth being smarter.
+ * Invalidate any ri_constraint_cache entry associated with the syscache
+ * entry with the specified hash value, or all entries if hashvalue == 0.
+ */
+static void
+InvalidateConstraintCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
+{
+ HASH_SEQ_STATUS status;
+ RI_ConstraintInfo *hentry;
+
+ Assert(ri_constraint_cache != NULL);
+
+ hash_seq_init(&status, ri_constraint_cache);
+ while ((hentry = (RI_ConstraintInfo *) hash_seq_search(&status)) != NULL)
+ {
+ if (hashvalue == 0 || hentry->oidHashValue == hashvalue)
+ hentry->valid = false;
+ }
}
/* ----------
* ri_InitHashTables -
*
- * Initialize our internal hash tables for prepared
- * query plans and comparison operators.
+ * Initialize our internal hash tables.
* ----------
*/
static void
{
HASHCTL ctl;
+ memset(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(RI_ConstraintInfo);
+ ctl.hash = oid_hash;
+ ri_constraint_cache = hash_create("RI constraint cache",
+ RI_INIT_CONSTRAINTHASHSIZE,
+ &ctl, HASH_ELEM | HASH_FUNCTION);
+
+ /* Arrange to flush cache on pg_constraint changes */
+ CacheRegisterSyscacheCallback(CONSTROID,
+ InvalidateConstraintCacheCallBack,
+ (Datum) 0);
+
memset(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(RI_QueryKey);
ctl.entrysize = sizeof(RI_QueryHashEntry);
ctl.hash = tag_hash;
- ri_query_cache = hash_create("RI query cache", RI_INIT_QUERYHASHSIZE,
+ 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,
+ ri_compare_cache = hash_create("RI compare cache",
+ RI_INIT_QUERYHASHSIZE,
&ctl, HASH_ELEM | HASH_FUNCTION);
}