/* RI query type codes */ /* these queries are executed against the PK (referenced) table: */ #define RI_PLAN_CHECK_LOOKUPPK 1 #define RI_PLAN_CHECK_LOOKUPPK_FROM_PK 2 #define RI_PLAN_LAST_ON_PK RI_PLAN_CHECK_LOOKUPPK_FROM_PK /* these queries are executed against the FK (referencing) table: */ #define RI_PLAN_CASCADE_ONDELETE 3 #define RI_PLAN_CASCADE_ONUPDATE 4 #define RI_PLAN_NO_ACTION 5 /* For RESTRICT, the same plan can be used for both ON DELETE and ON UPDATE triggers. */ #define RI_PLAN_RESTRICT 6 #define RI_PLAN_SETNULL_ONDELETE 7 #define RI_PLAN_SETNULL_ONUPDATE 8 #define RI_PLAN_SETDEFAULT_ONDELETE 9 #define RI_PLAN_SETDEFAULT_ONUPDATE 10
/* *RI_ConstraintInfo * *InformationextractedfromanFKpg_constraintentry.Thisiscachedin *ri_constraint_cache. * *Notethatpf/pp/ff_eq_oprsmayholdtheoverlapsoperatorinsteadofequals *forthePERIODpartofatemporalforeignkey.
*/ typedefstruct RI_ConstraintInfo
{
Oid constraint_id; /* OID of pg_constraint entry (hash key) */ bool valid; /* successfully initialized? */
Oid constraint_root_id; /* OID of topmost ancestor constraint;
* same as constraint_id if not inherited */
uint32 oidHashValue; /* hash value of constraint_id */
uint32 rootHashValue; /* hash value of constraint_root_id */
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 */ int ndelsetcols; /* number of columns referenced in ON DELETE
* SET clause */
int16 confdelsetcols[RI_MAX_NUMKEYS]; /* attnums of cols to set on
* delete */ char confmatchtype; /* foreign key's match type */ bool hasperiod; /* if the foreign key uses PERIOD */ 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) */
Oid period_contained_by_oper; /* anyrange <@ anyrange */
Oid agged_period_contained_by_oper; /* fkattr <@ range_agg(pkattr) */
Oid period_intersect_oper; /* anyrange * anyrange */
dlist_node valid_link; /* Link in list of valid entries */
} RI_ConstraintInfo;
/* *RI_QueryKey * *ThekeyidentifyingapreparedSPIplaninourqueryhashtable
*/ typedefstruct RI_QueryKey
{
Oid constr_id; /* OID of pg_constraint entry */
int32 constr_queryno; /* query type ID, see RI_PLAN_XXX above */
} RI_QueryKey;
/* *RI_CompareKey * *Thekeyidentifyinganentryshowinghowtocomparetwovalues
*/ typedefstruct RI_CompareKey
{
Oid eq_opr; /* the equality operator to apply */
Oid typeid; /* the data type to apply it to */
} RI_CompareKey;
/* *RI_CompareHashEntry
*/ typedefstruct 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;
appendStringInfo(&querybuf, "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
attname, pk_only, pkrelname);
} else
{
appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
pk_only, pkrelname);
}
querysep = "WHERE"; for (int i = 0; i < riinfo->nkeys; i++)
{
Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
quoteOneName(attname,
RIAttName(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] = fk_type;
}
appendStringInfoString(&querybuf, " FOR KEY SHARE OF x"); if (riinfo->hasperiod)
{
Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]);
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
table_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
}
/* *RI_FKey_check_ins- * *CheckforeignkeyexistenceatinserteventonFKtable.
*/
Datum
RI_FKey_check_ins(PG_FUNCTION_ARGS)
{ /* Check that this is a valid trigger call on the right time and event. */
ri_CheckTrigger(fcinfo, "RI_FKey_check_ins", RI_TRIGTYPE_INSERT);
/* *RI_FKey_check_upd- * *CheckforeignkeyexistenceatupdateeventonFKtable.
*/
Datum
RI_FKey_check_upd(PG_FUNCTION_ARGS)
{ /* Check that this is a valid trigger call on the right time and event. */
ri_CheckTrigger(fcinfo, "RI_FKey_check_upd", RI_TRIGTYPE_UPDATE);
appendStringInfo(&querybuf, "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
attname, pk_only, pkrelname);
} else
{
appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
pk_only, pkrelname);
}
querysep = "WHERE"; for (int i = 0; i < riinfo->nkeys; i++)
{
Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
quoteOneName(attname,
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] = pk_type;
}
appendStringInfoString(&querybuf, " FOR KEY SHARE OF x"); if (riinfo->hasperiod)
{
Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]);
/* Prepare and save the plan */
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
&qkey, fk_rel, pk_rel);
}
/* *Wehaveaplannow.Runit.
*/
result = ri_PerformCheck(riinfo, &qkey, qplan,
fk_rel, pk_rel,
oldslot, NULL, false, true, /* treat like update */
SPI_OK_SELECT);
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
return result;
}
/* *RI_FKey_noaction_del- * *Giveanerrorandrollbackthecurrenttransactionifthe *deletehasresultedinaviolationofthegivenreferential *integrityconstraint.
*/
Datum
RI_FKey_noaction_del(PG_FUNCTION_ARGS)
{ /* Check that this is a valid trigger call on the right time and event. */
ri_CheckTrigger(fcinfo, "RI_FKey_noaction_del", RI_TRIGTYPE_DELETE);
/* *RI_FKey_restrict_del- * *RestrictdeletefromPKtabletorowsunreferencedbyforeignkey. * *TheSQLstandardintendsthatthisreferentialactionoccurexactlywhen *thedeleteisperformed,ratherthanafter.Thisappearstobe *theonlydifferencebetween"NOACTION"and"RESTRICT".InPostgres *westillimplementthisasanAFTERtrigger,butit'snon-deferrable.
*/
Datum
RI_FKey_restrict_del(PG_FUNCTION_ARGS)
{ /* Check that this is a valid trigger call on the right time and event. */
ri_CheckTrigger(fcinfo, "RI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
/* Share code with NO ACTION/UPDATE cases. */ return ri_restrict((TriggerData *) fcinfo->context, false);
}
/* *RI_FKey_noaction_upd- * *Giveanerrorandrollbackthecurrenttransactionifthe *updatehasresultedinaviolationofthegivenreferential *integrityconstraint.
*/
Datum
RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
{ /* Check that this is a valid trigger call on the right time and event. */
ri_CheckTrigger(fcinfo, "RI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
/* *RI_FKey_restrict_upd- * *RestrictupdateofPKtorowsunreferencedbyforeignkey. * *TheSQLstandardintendsthatthisreferentialactionoccurexactlywhen *theupdateisperformed,ratherthanafter.Thisappearstobe *theonlydifferencebetween"NOACTION"and"RESTRICT".InPostgres *westillimplementthisasanAFTERtrigger,butit'snon-deferrable.
*/
Datum
RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
{ /* Check that this is a valid trigger call on the right time and event. */
ri_CheckTrigger(fcinfo, "RI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
/* Share code with NO ACTION/DELETE cases. */ return ri_restrict((TriggerData *) fcinfo->context, false);
}
appendStringInfoString(&querybuf, " AND NOT coalesce(");
/* Intersect the fk with the old pk range */
initStringInfo(&intersectbuf);
appendStringInfoChar(&intersectbuf, '(');
ri_GenerateQual(&intersectbuf, "",
attname, fk_period_type,
riinfo->period_intersect_oper,
paramname, pk_period_type);
appendStringInfoChar(&intersectbuf, ')');
/* Find the remaining history */
initStringInfo(&replacementsbuf);
appendStringInfoString(&replacementsbuf, "(SELECT pg_catalog.range_agg(r) FROM ");
quoteOneName(periodattname, RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
quoteRelationName(pkrelname, pk_rel);
appendStringInfo(&replacementsbuf, "(SELECT y.%s r FROM %s%s y",
periodattname, pk_only, pkrelname);
/* Restrict pk rows to what matches */
querysep = "WHERE"; for (int i = 0; i < riinfo->nkeys; i++)
{
Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
quoteOneName(attname,
RIAttName(pk_rel, riinfo->pk_attnums[i]));
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&replacementsbuf, querysep,
paramname, pk_type,
riinfo->pp_eq_oprs[i],
attname, pk_type);
querysep = "AND";
queryoids[i] = pk_type;
}
appendStringInfoString(&replacementsbuf, " FOR KEY SHARE OF y) y2)");
ri_GenerateQual(&querybuf, "",
intersectbuf.data, fk_period_type,
riinfo->agged_period_contained_by_oper,
replacementsbuf.data, ANYMULTIRANGEOID); /* end of coalesce: */
appendStringInfoString(&querybuf, ", false)");
}
appendStringInfoString(&querybuf, " FOR KEY SHARE OF x");
/* Prepare and save the plan */
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
&qkey, fk_rel, pk_rel);
}
/* *Wehaveaplannow.Runittocheckforexistingreferences.
*/
ri_PerformCheck(riinfo, &qkey, qplan,
fk_rel, pk_rel,
oldslot, NULL,
!is_no_action, true, /* must detect new rows */
SPI_OK_SELECT);
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
/* Prepare and save the plan */
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys * 2, queryoids,
&qkey, fk_rel, pk_rel);
}
/* *Wehaveaplannow.Runittoupdatetheexistingreferences.
*/
ri_PerformCheck(riinfo, &qkey, qplan,
fk_rel, pk_rel,
oldslot, newslot, false, true, /* must detect new rows */
SPI_OK_UPDATE);
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
table_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
}
/* *RI_FKey_setnull_del- * *SetforeignkeyreferencestoNULLvaluesatdeleteeventonPKtable.
*/
Datum
RI_FKey_setnull_del(PG_FUNCTION_ARGS)
{ /* Check that this is a valid trigger call on the right time and event. */
ri_CheckTrigger(fcinfo, "RI_FKey_setnull_del", RI_TRIGTYPE_DELETE);
/* Share code with UPDATE case */ return ri_set((TriggerData *) fcinfo->context, true, RI_TRIGTYPE_DELETE);
}
/* *RI_FKey_setnull_upd- * *SetforeignkeyreferencestoNULLatupdateeventonPKtable.
*/
Datum
RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
{ /* Check that this is a valid trigger call on the right time and event. */
ri_CheckTrigger(fcinfo, "RI_FKey_setnull_upd", RI_TRIGTYPE_UPDATE);
/* Share code with DELETE case */ return ri_set((TriggerData *) fcinfo->context, true, RI_TRIGTYPE_UPDATE);
}
/* *RI_FKey_setdefault_del- * *SetforeignkeyreferencestodefaultsatdeleteeventonPKtable.
*/
Datum
RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
{ /* Check that this is a valid trigger call on the right time and event. */
ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_del", RI_TRIGTYPE_DELETE);
/* Share code with UPDATE case */ return ri_set((TriggerData *) fcinfo->context, false, RI_TRIGTYPE_DELETE);
}
/* *RI_FKey_setdefault_upd- * *SetforeignkeyreferencestodefaultsatupdateeventonPKtable.
*/
Datum
RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
{ /* Check that this is a valid trigger call on the right time and event. */
ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_upd", RI_TRIGTYPE_UPDATE);
/* Share code with DELETE case */ return ri_set((TriggerData *) fcinfo->context, false, RI_TRIGTYPE_UPDATE);
}
/* *StarttheWHEREclausewiththepartitionconstraint(exceptifthisis *thedefaultpartitionandthere'snootherpartition,becausethe *partitionconstraintistheemptystringinthatcase.)
*/
constraintDef = pg_get_partconstrdef_string(RelationGetRelid(pk_rel), "pk"); if (constraintDef && constraintDef[0] != '\0')
appendStringInfo(&querybuf, ") WHERE %s AND (",
constraintDef); else
appendStringInfoString(&querybuf, ") WHERE (");
sep = ""; for (i = 0; i < riinfo->nkeys; i++)
{
quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
appendStringInfo(&querybuf, "%sfk.%s IS NOT NULL",
sep, fkattname); switch (riinfo->confmatchtype)
{ case FKCONSTR_MATCH_SIMPLE:
sep = " AND "; break; case FKCONSTR_MATCH_FULL:
sep = " OR "; break;
}
}
appendStringInfoChar(&querybuf, ')');
/* Check result */ if (spi_result != SPI_OK_SELECT)
elog(ERROR, "SPI_execute_snapshot returned %s", SPI_result_code_string(spi_result));
/* Did we find a tuple that would violate the constraint? */ if (SPI_processed > 0)
{
TupleTableSlot *slot;
HeapTuple tuple = SPI_tuptable->vals[0];
TupleDesc tupdesc = SPI_tuptable->tupdesc;
RI_ConstraintInfo fake_riinfo;
if (!CALLED_AS_TRIGGER(fcinfo))
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("function \"%s\" was not called by trigger manager", funcname)));
/* *Checkproperevent
*/ if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("function \"%s\" must be fired AFTER ROW", funcname)));
switch (tgkind)
{ case RI_TRIGTYPE_INSERT: if (!TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("function \"%s\" must be fired for INSERT", funcname))); break; case RI_TRIGTYPE_UPDATE: if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("function \"%s\" must be fired for UPDATE", funcname))); break; case RI_TRIGTYPE_DELETE: if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("function \"%s\" must be fired for DELETE", funcname))); break;
}
}
/* *CheckthattheFKconstraint'sOIDisavailable;itmightnotbeif *we'vebeeninvokedviaanordinarytriggeroranold-style"constraint *trigger".
*/ if (!OidIsValid(constraintOid))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
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.")));
/* 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 (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 (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));
}
/* Switch to proper UID to perform check as */
GetUserIdAndSecContext(&save_userid, &save_sec_context);
SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
save_sec_context | SECURITY_LOCAL_USERID_CHANGE |
SECURITY_NOFORCE_RLS);
/* Finally we can run the query. */
spi_result = SPI_execute_snapshot(qplan,
vals, nulls,
test_snapshot, crosscheck_snapshot, false, false, limit);
/* Restore UID and security context */
SetUserIdAndSecContext(save_userid, save_sec_context);
/* Check result */ if (spi_result < 0)
elog(ERROR, "SPI_execute_snapshot returned %s", SPI_result_code_string(spi_result));
if (expect_OK >= 0 && spi_result != expect_OK)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("referential integrity query on \"%s\" from constraint \"%s\" on \"%s\" gave unexpected result",
RelationGetRelationName(pk_rel),
NameStr(riinfo->conname),
RelationGetRelationName(fk_rel)),
errhint("This is most likely due to a rule having rewritten the query.")));
/* XXX wouldn't it be clearer to do this part at the caller? */ if (qkey->constr_queryno != RI_PLAN_CHECK_LOOKUPPK_FROM_PK &&
expect_OK == SPI_OK_SELECT &&
(SPI_processed == 0) == (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK))
ri_ReportViolation(riinfo,
pk_rel, fk_rel,
newslot ? newslot : oldslot,
NULL,
qkey->constr_queryno, is_restrict, false);
return SPI_processed != 0;
}
/*
* Extract fields from a tuple into Datum/nulls arrays
*/
static void
ri_ExtractValues(Relation rel, TupleTableSlot *slot,
const RI_ConstraintInfo *riinfo, bool rel_is_pk,
Datum *vals, char *nulls)
{
const int16 *attnums;
bool isnull;
if (rel_is_pk)
attnums = riinfo->pk_attnums; else
attnums = riinfo->fk_attnums;
for (int i = 0; i < riinfo->nkeys; i++)
{
vals[i] = slot_getattr(slot, attnums[i], &isnull);
nulls[i] = isnull ? 'n' : ' ';
}
}
/*
* Produce an error report
*
* If the failed constraint was on insert/update to the FK table,
* we want the key names and values extracted from there, and the error
* message to look like 'key blah is not present in PK'.
* Otherwise, the attr names and values come from the PK table and the
* message looks like 'key blah is still referenced from FK'.
*/
static void
ri_ReportViolation(const RI_ConstraintInfo *riinfo,
Relation pk_rel, Relation fk_rel,
TupleTableSlot *violatorslot, TupleDesc tupdesc,
int queryno, bool is_restrict, bool partgone)
{
StringInfoData key_names;
StringInfoData key_values;
bool onfk;
const int16 *attnums;
Oid rel_oid;
AclResult aclresult;
bool has_perm = true;
/*
* Determine which relation to complain about. If tupdesc wasn't passed
* by caller, assume the violator tuple came from there.
*/
onfk = (queryno == RI_PLAN_CHECK_LOOKUPPK); if (onfk)
{
attnums = riinfo->fk_attnums;
rel_oid = fk_rel->rd_id; if (tupdesc == NULL)
tupdesc = fk_rel->rd_att;
} else
{
attnums = riinfo->pk_attnums;
rel_oid = pk_rel->rd_id; if (tupdesc == NULL)
tupdesc = pk_rel->rd_att;
}
/*
* Check permissions- if the user does not have access to view the data in
* any of the key columns then we don't include the errdetail() below.
*
* Check if RLS is enabled on the relation first. If so, we don't return
* any specifics to avoid leaking data.
*
* Check table-level permissions next and, failing that, column-level
* privileges.
*
* When a partition at the referenced side is being detached/dropped, we
* needn't check, since the user must be the table owner anyway.
*/ if (partgone)
has_perm = true; elseif (check_enable_rls(rel_oid, InvalidOid, true) != RLS_ENABLED)
{
aclresult = pg_class_aclcheck(rel_oid, GetUserId(), ACL_SELECT); if (aclresult != ACLCHECK_OK)
{
/* Try for column-level permissions */ for (int idx = 0; idx < riinfo->nkeys; idx++)
{
aclresult = pg_attribute_aclcheck(rel_oid, attnums[idx],
GetUserId(),
ACL_SELECT);
/* No access to the key */ if (aclresult != ACLCHECK_OK)
{
has_perm = false;
break;
}
}
}
} else
has_perm = false;
if (has_perm)
{
/* Get printable versions of the keys involved */
initStringInfo(&key_names);
initStringInfo(&key_values); for (int idx = 0; idx < riinfo->nkeys; idx++)
{
int fnum = attnums[idx];
Form_pg_attribute att = TupleDescAttr(tupdesc, fnum - 1);
char *name,
*val;
Datum datum;
bool isnull;
name = NameStr(att->attname);
datum = slot_getattr(violatorslot, fnum, &isnull); if (!isnull)
{
Oid foutoid;
bool typisvarlena;
getTypeOutputInfo(att->atttypid, &foutoid, &typisvarlena);
val = OidOutputFunctionCall(foutoid, datum);
} else
val = "null";
if (partgone)
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
errmsg("removing partition \"%s\" violates foreign key constraint \"%s\"",
RelationGetRelationName(pk_rel),
NameStr(riinfo->conname)),
errdetail("Key (%s)=(%s) is still referenced from table \"%s\".",
key_names.data, key_values.data,
RelationGetRelationName(fk_rel)),
errtableconstraint(fk_rel, NameStr(riinfo->conname)))); elseif (onfk)
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)),
has_perm ?
errdetail("Key (%s)=(%s) is not present in table \"%s\".",
key_names.data, key_values.data,
RelationGetRelationName(pk_rel)) :
errdetail("Key is not present in table \"%s\".",
RelationGetRelationName(pk_rel)),
errtableconstraint(fk_rel, NameStr(riinfo->conname)))); elseif (is_restrict)
ereport(ERROR,
(errcode(ERRCODE_RESTRICT_VIOLATION),
errmsg("update or delete on table \"%s\" violates RESTRICT setting of foreign key constraint \"%s\" on table \"%s\"",
RelationGetRelationName(pk_rel),
NameStr(riinfo->conname),
RelationGetRelationName(fk_rel)),
has_perm ?
errdetail("Key (%s)=(%s) is referenced from table \"%s\".",
key_names.data, key_values.data,
RelationGetRelationName(fk_rel)) :
errdetail("Key is referenced from table \"%s\".",
RelationGetRelationName(fk_rel)),
errtableconstraint(fk_rel, NameStr(riinfo->conname)))); else
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
errmsg("update or delete on table \"%s\" violates foreign key constraint \"%s\" on table \"%s\"",
RelationGetRelationName(pk_rel),
NameStr(riinfo->conname),
RelationGetRelationName(fk_rel)),
has_perm ?
errdetail("Key (%s)=(%s) is still referenced from table \"%s\".",
key_names.data, key_values.data,
RelationGetRelationName(fk_rel)) :
errdetail("Key is still referenced from table \"%s\".",
RelationGetRelationName(fk_rel)),
errtableconstraint(fk_rel, NameStr(riinfo->conname))));
}
/*
* ri_NullCheck -
*
* Determine the NULL state of all key values in a tuple
*
* Returns one of RI_KEYS_ALL_NULL, RI_KEYS_NONE_NULL or RI_KEYS_SOME_NULL.
*/
static int
ri_NullCheck(TupleDesc tupDesc,
TupleTableSlot *slot,
const RI_ConstraintInfo *riinfo, bool rel_is_pk)
{
const int16 *attnums;
bool allnull = true;
bool nonenull = true;
if (rel_is_pk)
attnums = riinfo->pk_attnums; else
attnums = riinfo->fk_attnums;
for (int i = 0; i < riinfo->nkeys; i++)
{ if (slot_attisnull(slot, attnums[i]))
nonenull = false; else
allnull = false;
}
/*
* ri_FetchPreparedPlan -
*
* Lookup for a query key in our private hash table of prepared
* and saved SPI execution plans. Return the plan if found or NULL.
*/
static SPIPlanPtr
ri_FetchPreparedPlan(RI_QueryKey *key)
{
RI_QueryHashEntry *entry;
SPIPlanPtr plan;
/*
* On the first call initialize the hashtable
*/ if (!ri_query_cache)
ri_InitHashTables();
/*
* Lookup for the key
*/
entry = (RI_QueryHashEntry *) hash_search(ri_query_cache,
key,
HASH_FIND, NULL); if (entry == NULL)
return NULL;
/*
* Check whether the plan is still valid. If it isn't, we don't want to
* simply rely on plancache.c to regenerate it; rather we should start
* from scratch and rebuild the query text too. This is to cover cases
* such as table/column renames. We depend on the plancache machinery to
* detect possible invalidations, though.
*
* CAUTION: this check is only trustworthy if the caller has already
* locked both FK and PK rels.
*/
plan = entry->plan; if (plan && SPI_plan_is_valid(plan))
return plan;
/*
* Otherwise we might as well flush the cached plan now, to free a little
* memory space before we make a new one.
*/
entry->plan = NULL; if (plan)
SPI_freeplan(plan);
return NULL;
}
/*
* ri_HashPreparedPlan -
*
* Add another plan to our private SPI query plan hashtable.
*/
static void
ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
{
RI_QueryHashEntry *entry;
bool found;
/*
* On the first call initialize the hashtable
*/ if (!ri_query_cache)
ri_InitHashTables();
/*
* Add the new plan. We might be overwriting an entry previously found
* invalid by ri_FetchPreparedPlan.
*/
entry = (RI_QueryHashEntry *) hash_search(ri_query_cache,
key,
HASH_ENTER, &found);
Assert(!found || entry->plan == NULL);
entry->plan = plan;
}
/*
* ri_KeysEqual -
*
* Check ifall key values in OLD and NEW are "equivalent":
* For normal FKs we check for equality.
* For temporal FKs we check that the PK side is a superset of its old value,
* or the FK side is a subset of its old value.
*
* Note: at some point we might wish to redefine this as checking for
* "IS NOT DISTINCT" rather than "=", that is, allow two nulls to be
* considered equal. Currently there is no need since all callers have
* previously found at least one of the rows to contain no nulls.
*/
static bool
ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
const RI_ConstraintInfo *riinfo, bool rel_is_pk)
{
const int16 *attnums;
if (rel_is_pk)
attnums = riinfo->pk_attnums; else
attnums = riinfo->fk_attnums;
/* XXX: could be worthwhile to fetch all necessary attrs at once */ for (int 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 = slot_getattr(oldslot, attnums[i], &isnull); if (isnull)
return false;
/*
* Get one attribute's newvalue. If it is NULL - they're not equal.
*/
newvalue = slot_getattr(newslot, attnums[i], &isnull); if (isnull)
return false;
if (rel_is_pk)
{
/*
* If we are looking at the PK table, thendo a bytewise
* comparison. We must propagate PK changes if the value is
* changed to one that "looks" different but would compare as
* equal using the equality operator. This only makes a
* difference for ON UPDATE CASCADE, but for consistency we treat
* all changes to the PK the same.
*/
CompactAttribute *att = TupleDescCompactAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen))
return false;
} else
{
Oid eq_opr;
/*
* When comparing the PERIOD columns we can skip the check
* whenever the referencing column stayed equal or shrank, so test
* with the contained-by operator instead.
*/ if (riinfo->hasperiod && i == riinfo->nkeys - 1)
eq_opr = riinfo->period_contained_by_oper; else
eq_opr = riinfo->ff_eq_oprs[i];
/*
* For the FK table, compare with the appropriate equality
* operator. Changes that compare equal will still satisfy the
* constraint after the update.
*/ if (!ri_CompareWithCast(eq_opr, RIAttType(rel, attnums[i]), RIAttCollation(rel, attnums[i]),
newvalue, oldvalue))
return false;
}
}
return true;
}
/*
* ri_CompareWithCast -
*
* Call the appropriate comparison operator for two values.
* Normally this is equality, but for the PERIOD part of foreign keys
* it is ContainedBy, so the order of lhs vs rhs is significant.
* See below for how the collation is applied.
*
* NB: we have already checked that neither value is null.
*/
static bool
ri_CompareWithCast(Oid eq_opr, Oid typeid, Oid collid,
Datum lhs, Datum rhs)
{
RI_CompareHashEntry *entry = ri_HashCompareOp(eq_opr, typeid);
/* Do we need to cast the values? */ if (OidIsValid(entry->cast_func_finfo.fn_oid))
{
lhs = FunctionCall3(&entry->cast_func_finfo,
lhs,
Int32GetDatum(-1), /* typmod */
BoolGetDatum(false)); /* implicit coercion */
rhs = FunctionCall3(&entry->cast_func_finfo,
rhs,
Int32GetDatum(-1), /* typmod */
BoolGetDatum(false)); /* implicit coercion */
}
/*
* Apply the comparison operator.
*
* Note: This function is part of a call stack that determines whether an
* update to a row is significant enough that it needs checking or action
* on the other side of a foreign-key constraint. Therefore, the
* comparison here would need to be done with the collation of the *other*
* table. For simplicity (e.g., we might not even have the other table
* open), we'll use our own collation. This is fine because we require
* that both collations have the same notion of equality (either they are
* both deterministic or else they are both the same).
*
* With range/multirangetypes, the collation of the base type is stored as
* part of the rangetype (pg_range.rngcollation), and always used, so
* there is no danger of inconsistency even using a non-equals operator.
* But if we support arbitrary types with PERIOD, we should perhaps just
* always force a re-check.
*/
return DatumGetBool(FunctionCall2Coll(&entry->eq_opr_finfo, collid, lhs, rhs));
}
/*
* 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)
{
RI_CompareKey key;
RI_CompareHashEntry *entry;
bool found;
/*
* On the first call initialize the hashtable
*/ if (!ri_compare_cache)
ri_InitHashTables();
/*
* 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,
&key,
HASH_ENTER, &found); if (!found)
entry->valid = false;
/*
* 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.
*/ if (!entry->valid)
{
Oid lefttype,
righttype,
castfunc;
CoercionPathType pathtype;
/* 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.
*
* XXX eventually it would be good to support array-coercion cases
* here and in ri_CompareWithCast(). At the moment there is no point
* because cases involving nonidentical array types will be rejected
* at constraint creation time.
*
* XXX perhaps also consider supporting CoerceViaIO? No need at the
* moment since that will never be generated for implicit coercions.
*/
op_input_types(eq_opr, &lefttype, &righttype);
Assert(lefttype == righttype); if (typeid == lefttype)
castfunc = InvalidOid; /* simplest case */ else
{
pathtype = find_coercion_pathway(lefttype, typeid,
COERCION_IMPLICIT,
&castfunc); if (pathtype != COERCION_PATH_FUNC &&
pathtype != COERCION_PATH_RELABELTYPE)
{
/*
* The declared input type of the eq_opr might be a
* polymorphic type such as ANYARRAY or ANYENUM, or other
* special cases such as RECORD; find_coercion_pathway
* currently doesn't subsume these special cases.
*/ if (!IsBinaryCoercible(typeid, lefttype))
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.
*/
int
RI_FKey_trigger_type(Oid tgfoid)
{
switch (tgfoid)
{
case F_RI_FKEY_CASCADE_DEL:
case F_RI_FKEY_CASCADE_UPD:
case F_RI_FKEY_RESTRICT_DEL:
case F_RI_FKEY_RESTRICT_UPD:
case F_RI_FKEY_SETNULL_DEL:
case F_RI_FKEY_SETNULL_UPD:
case F_RI_FKEY_SETDEFAULT_DEL:
case F_RI_FKEY_SETDEFAULT_UPD:
case F_RI_FKEY_NOACTION_DEL:
case F_RI_FKEY_NOACTION_UPD:
return RI_TRIGGER_PK;
case F_RI_FKEY_CHECK_INS:
case F_RI_FKEY_CHECK_UPD:
return RI_TRIGGER_FK;
}
return RI_TRIGGER_NONE;
}
Messung V0.5 in Prozent
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.105Bemerkung:
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.