*
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
*
- * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.46 2002/12/12 15:49:40 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.47 2003/03/15 21:19:40 tgl Exp $
*
* ----------
*/
#define RI_KEYS_SOME_NULL 1
#define RI_KEYS_NONE_NULL 2
-
+/* queryno values must be distinct for the convenience of ri_PerformCheck */
#define RI_PLAN_CHECK_LOOKUPPK_NOCOLS 1
#define RI_PLAN_CHECK_LOOKUPPK 2
-#define RI_PLAN_CASCADE_DEL_DODELETE 1
-#define RI_PLAN_CASCADE_UPD_DOUPDATE 1
-#define RI_PLAN_NOACTION_DEL_CHECKREF 1
-#define RI_PLAN_NOACTION_UPD_CHECKREF 1
-#define RI_PLAN_RESTRICT_DEL_CHECKREF 1
-#define RI_PLAN_RESTRICT_UPD_CHECKREF 1
-#define RI_PLAN_SETNULL_DEL_DOUPDATE 1
-#define RI_PLAN_SETNULL_UPD_DOUPDATE 1
+#define RI_PLAN_CASCADE_DEL_DODELETE 3
+#define RI_PLAN_CASCADE_UPD_DOUPDATE 4
+#define RI_PLAN_NOACTION_DEL_CHECKREF 5
+#define RI_PLAN_NOACTION_UPD_CHECKREF 6
+#define RI_PLAN_RESTRICT_DEL_CHECKREF 7
+#define RI_PLAN_RESTRICT_UPD_CHECKREF 8
+#define RI_PLAN_SETNULL_DEL_DOUPDATE 9
+#define RI_PLAN_SETNULL_UPD_DOUPDATE 10
+#define RI_PLAN_KEYEQUAL_UPD 11
#define MAX_QUOTED_NAME_LEN (NAMEDATALEN*2+3)
#define MAX_QUOTED_REL_NAME_LEN (MAX_QUOTED_NAME_LEN*2)
+#define RI_TRIGTYPE_INSERT 1
+#define RI_TRIGTYPE_UPDATE 2
+#define RI_TRIGTYPE_INUP 3
+#define RI_TRIGTYPE_DELETE 4
+
/* ----------
* RI_QueryKey
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);
-static bool ri_Check_Pk_Match(Relation pk_rel, HeapTuple old_row,
- Oid tgoid, int match_type, int tgnargs, char **tgargs);
+static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
+ HeapTuple old_row,
+ Oid tgoid, int match_type,
+ int tgnargs, char **tgargs);
static void ri_InitHashTables(void);
static void *ri_FetchPreparedPlan(RI_QueryKey *key);
static void ri_HashPreparedPlan(RI_QueryKey *key, void *plan);
+static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname,
+ int tgkind);
+static bool ri_PerformCheck(RI_QueryKey *qkey, void *qplan,
+ Relation fk_rel, Relation pk_rel,
+ HeapTuple old_tuple, HeapTuple new_tuple,
+ int expect_OK, const char *constrname);
+static void ri_ExtractValues(RI_QueryKey *qkey, int key_idx,
+ Relation rel, HeapTuple tuple,
+ Datum *vals, char *nulls);
+static void ri_ReportViolation(RI_QueryKey *qkey, const char *constrname,
+ Relation pk_rel, Relation fk_rel,
+ HeapTuple violator, bool spi_err);
+
+
/* ----------
* RI_FKey_check -
*
HeapTuple old_row;
RI_QueryKey qkey;
void *qplan;
- Datum check_values[RI_MAX_NUMKEYS];
- char check_nulls[RI_MAX_NUMKEYS + 1];
- bool isnull;
int i;
int match_type;
- AclId save_uid;
-
- save_uid = GetUserId();
ReferentialIntegritySnapshotOverride = true;
* Check that this is a valid trigger call on the right time and
* event.
*/
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_check() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_check() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_INSERT(trigdata->tg_event) &&
- !TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_check() must be fired for INSERT or UPDATE");
+ ri_CheckTrigger(fcinfo, "RI_FKey_check", RI_TRIGTYPE_INUP);
/*
* Check for the correct # of call arguments
* Execute the plan
*/
if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_check()");
-
- SetUserId(RelationGetForm(pk_rel)->relowner);
-
- if (SPI_execp(qplan, check_values, check_nulls, 1) != SPI_OK_SELECT)
- elog(ERROR, "SPI_execp() failed in RI_FKey_check()");
+ elog(ERROR, "SPI_connect() failed in RI_FKey_check()");
- SetUserId(save_uid);
-
- if (SPI_processed == 0)
- elog(ERROR, "%s referential integrity violation - "
- "no rows found in %s",
- tgargs[RI_CONSTRAINT_NAME_ARGNO],
- RelationGetRelationName(pk_rel));
+ ri_PerformCheck(&qkey, qplan,
+ fk_rel, pk_rel,
+ NULL, NULL,
+ SPI_OK_SELECT,
+ tgargs[RI_CONSTRAINT_NAME_ARGNO]);
if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_check()");
+ elog(ERROR, "SPI_finish() failed in RI_FKey_check()");
heap_close(pk_rel, RowShareLock);
* are the same. Otherwise, someone could DELETE the PK that consists
* of the DEFAULT values, and if there are any references, a ON DELETE
* SET DEFAULT action would update the references to exactly these
- * values but we wouldn't see that weired case (this is the only place
+ * values but we wouldn't see that weird case (this is the only place
* to see it).
*/
if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_check()");
-
+ elog(ERROR, "SPI_connect() failed in RI_FKey_check()");
/*
* Fetch or prepare a saved plan for the real check
ri_HashPreparedPlan(&qkey, qplan);
}
- /*
- * We have a plan now. Build up the arguments for SPI_execp() from the
- * key values in the new FK tuple.
- */
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- /*
- * We can implement MATCH PARTIAL by excluding this column from
- * the query if it is null. Simple! Unfortunately, the
- * referential actions aren't so I've not bothered to do so for
- * the moment.
- */
-
- check_values[i] = SPI_getbinval(new_row,
- fk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_FK_IDX],
- &isnull);
- if (isnull)
- check_nulls[i] = 'n';
- else
- check_nulls[i] = ' ';
- }
- check_nulls[i] = '\0';
-
/*
* Now check that foreign key exists in PK table
*/
-
- SetUserId(RelationGetForm(pk_rel)->relowner);
-
- if (SPI_execp(qplan, check_values, check_nulls, 1) != SPI_OK_SELECT)
- elog(ERROR, "SPI_execp() failed in RI_FKey_check()");
-
- SetUserId(save_uid);
-
- if (SPI_processed == 0)
- elog(ERROR, "%s referential integrity violation - "
- "key referenced from %s not found in %s",
- tgargs[RI_CONSTRAINT_NAME_ARGNO],
- RelationGetRelationName(fk_rel),
- RelationGetRelationName(pk_rel));
+ ri_PerformCheck(&qkey, qplan,
+ fk_rel, pk_rel,
+ NULL, new_row,
+ SPI_OK_SELECT,
+ tgargs[RI_CONSTRAINT_NAME_ARGNO]);
if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_check()");
+ elog(ERROR, "SPI_finish() failed in RI_FKey_check()");
heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
-
- /*
- * Never reached
- */
- elog(ERROR, "internal error #1 in ri_triggers.c");
- return PointerGetDatum(NULL);
}
* ----------
*/
static bool
-ri_Check_Pk_Match(Relation pk_rel, HeapTuple old_row, Oid tgoid, int match_type, int tgnargs, char **tgargs)
+ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
+ HeapTuple old_row,
+ Oid tgoid, int match_type,
+ int tgnargs, char **tgargs)
{
void *qplan;
RI_QueryKey qkey;
- bool isnull;
- Datum check_values[RI_MAX_NUMKEYS];
- char check_nulls[RI_MAX_NUMKEYS + 1];
int i;
- AclId save_uid;
bool result;
- save_uid = GetUserId();
-
ri_BuildQueryKeyPkCheck(&qkey, tgoid,
RI_PLAN_CHECK_LOOKUPPK, pk_rel,
tgnargs, tgargs);
}
if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_check()");
-
+ elog(ERROR, "SPI_connect() failed in RI_FKey_check()");
/*
* Fetch or prepare a saved plan for the real check
}
/*
- * We have a plan now. Build up the arguments for SPI_execp() from the
- * key values in the new FK tuple.
- */
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- check_values[i] = SPI_getbinval(old_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- check_nulls[i] = 'n';
- else
- check_nulls[i] = ' ';
- }
- check_nulls[i] = '\0';
-
- /*
- * Now check that foreign key exists in PK table
+ * We have a plan now. Run it.
*/
-
- SetUserId(RelationGetForm(pk_rel)->relowner);
-
- if (SPI_execp(qplan, check_values, check_nulls, 1) != SPI_OK_SELECT)
- elog(ERROR, "SPI_execp() failed in ri_Check_Pk_Match()");
-
- SetUserId(save_uid);
-
- result = (SPI_processed != 0);
+ result = ri_PerformCheck(&qkey, qplan,
+ fk_rel, pk_rel,
+ old_row, NULL,
+ SPI_OK_SELECT, NULL);
if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in ri_Check_Pk_Match()");
+ elog(ERROR, "SPI_finish() failed in ri_Check_Pk_Match()");
return result;
}
HeapTuple old_row;
RI_QueryKey qkey;
void *qplan;
- Datum del_values[RI_MAX_NUMKEYS];
- char del_nulls[RI_MAX_NUMKEYS + 1];
- bool isnull;
int i;
int match_type;
- AclId save_uid;
-
- save_uid = GetUserId();
ReferentialIntegritySnapshotOverride = true;
* Check that this is a valid trigger call on the right time and
* event.
*/
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_noaction_del() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_noaction_del() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_noaction_del() must be fired for DELETE");
+ ri_CheckTrigger(fcinfo, "RI_FKey_noaction_del", RI_TRIGTYPE_DELETE);
/*
* Check for the correct # of call arguments
old_row = trigdata->tg_trigtuple;
match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
- if (ri_Check_Pk_Match(pk_rel, old_row, trigdata->tg_trigger->tgoid,
+ if (ri_Check_Pk_Match(pk_rel, fk_rel,
+ old_row, trigdata->tg_trigger->tgoid,
match_type, tgnargs, tgargs))
{
/*
}
if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_noaction_del()");
+ elog(ERROR, "SPI_connect() failed in RI_FKey_noaction_del()");
/*
* Fetch or prepare a saved plan for the restrict delete
}
/*
- * We have a plan now. Build up the arguments for SPI_execp()
- * from the key values in the deleted PK tuple.
- */
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- del_values[i] = SPI_getbinval(old_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- del_nulls[i] = 'n';
- else
- del_nulls[i] = ' ';
- }
- del_nulls[i] = '\0';
-
- /*
- * Now check for existing references
+ * We have a plan now. Run it to check for existing references.
*/
- SetUserId(RelationGetForm(pk_rel)->relowner);
-
- if (SPI_execp(qplan, del_values, del_nulls, 1) != SPI_OK_SELECT)
- elog(ERROR, "SPI_execp() failed in RI_FKey_noaction_del()");
-
- SetUserId(save_uid);
-
- if (SPI_processed > 0)
- elog(ERROR, "%s referential integrity violation - "
- "key in %s still referenced from %s",
- tgargs[RI_CONSTRAINT_NAME_ARGNO],
- RelationGetRelationName(pk_rel),
- RelationGetRelationName(fk_rel));
+ ri_PerformCheck(&qkey, qplan,
+ fk_rel, pk_rel,
+ old_row, NULL,
+ SPI_OK_SELECT,
+ tgargs[RI_CONSTRAINT_NAME_ARGNO]);
if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_noaction_del()");
+ elog(ERROR, "SPI_finish() failed in RI_FKey_noaction_del()");
heap_close(fk_rel, RowShareLock);
HeapTuple old_row;
RI_QueryKey qkey;
void *qplan;
- Datum upd_values[RI_MAX_NUMKEYS];
- char upd_nulls[RI_MAX_NUMKEYS + 1];
- bool isnull;
int i;
int match_type;
- AclId save_uid;
-
- save_uid = GetUserId();
ReferentialIntegritySnapshotOverride = true;
* Check that this is a valid trigger call on the right time and
* event.
*/
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_noaction_upd() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_noaction_upd() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_noaction_upd() must be fired for UPDATE");
+ ri_CheckTrigger(fcinfo, "RI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
/*
* Check for the correct # of call arguments
old_row = trigdata->tg_trigtuple;
match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
- if (ri_Check_Pk_Match(pk_rel, old_row, trigdata->tg_trigger->tgoid,
+ if (ri_Check_Pk_Match(pk_rel, fk_rel,
+ old_row, trigdata->tg_trigger->tgoid,
match_type, tgnargs, tgargs))
{
/*
}
if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_noaction_upd()");
+ elog(ERROR, "SPI_connect() failed in RI_FKey_noaction_upd()");
/*
* Fetch or prepare a saved plan for the noaction update
}
/*
- * We have a plan now. Build up the arguments for SPI_execp()
- * from the key values in the updated PK tuple.
+ * We have a plan now. Run it to check for existing references.
*/
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- upd_values[i] = SPI_getbinval(old_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- upd_nulls[i] = 'n';
- else
- upd_nulls[i] = ' ';
- }
- upd_nulls[i] = '\0';
-
- /*
- * Now check for existing references
- */
- SetUserId(RelationGetForm(pk_rel)->relowner);
-
- if (SPI_execp(qplan, upd_values, upd_nulls, 1) != SPI_OK_SELECT)
- elog(ERROR, "SPI_execp() failed in RI_FKey_noaction_upd()");
-
- SetUserId(save_uid);
-
- if (SPI_processed > 0)
- elog(ERROR, "%s referential integrity violation - "
- "key in %s still referenced from %s",
- tgargs[RI_CONSTRAINT_NAME_ARGNO],
- RelationGetRelationName(pk_rel),
- RelationGetRelationName(fk_rel));
+ ri_PerformCheck(&qkey, qplan,
+ fk_rel, pk_rel,
+ old_row, NULL,
+ SPI_OK_SELECT,
+ tgargs[RI_CONSTRAINT_NAME_ARGNO]);
if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_noaction_upd()");
+ elog(ERROR, "SPI_finish() failed in RI_FKey_noaction_upd()");
heap_close(fk_rel, RowShareLock);
HeapTuple old_row;
RI_QueryKey qkey;
void *qplan;
- Datum del_values[RI_MAX_NUMKEYS];
- char del_nulls[RI_MAX_NUMKEYS + 1];
- bool isnull;
int i;
- AclId save_uid;
- AclId fk_owner;
ReferentialIntegritySnapshotOverride = true;
* Check that this is a valid trigger call on the right time and
* event.
*/
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_cascade_del() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_cascade_del() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_cascade_del() must be fired for DELETE");
+ ri_CheckTrigger(fcinfo, "RI_FKey_cascade_del", RI_TRIGTYPE_DELETE);
/*
* Check for the correct # of call arguments
fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
old_row = trigdata->tg_trigtuple;
- fk_owner = RelationGetForm(fk_rel)->relowner;
switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
{
}
if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_cascade_del()");
+ elog(ERROR, "SPI_connect() failed in RI_FKey_cascade_del()");
/*
* Fetch or prepare a saved plan for the cascaded delete
}
/*
- * We have a plan now. Build up the arguments for SPI_execp()
- * from the key values in the deleted PK tuple.
+ * We have a plan now. Build up the arguments
+ * from the key values in the deleted PK tuple and delete the
+ * referencing rows
*/
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- del_values[i] = SPI_getbinval(old_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- del_nulls[i] = 'n';
- else
- del_nulls[i] = ' ';
- }
- del_nulls[i] = '\0';
-
- /*
- * Now delete constraint
- */
- save_uid = GetUserId();
- SetUserId(fk_owner);
-
- if (SPI_execp(qplan, del_values, del_nulls, 0) != SPI_OK_DELETE)
- elog(ERROR, "SPI_execp() failed in RI_FKey_cascade_del()");
-
- SetUserId(save_uid);
+ ri_PerformCheck(&qkey, qplan,
+ fk_rel, pk_rel,
+ old_row, NULL,
+ SPI_OK_DELETE,
+ tgargs[RI_CONSTRAINT_NAME_ARGNO]);
if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_cascade_del()");
+ elog(ERROR, "SPI_finish() failed in RI_FKey_cascade_del()");
heap_close(fk_rel, RowExclusiveLock);
HeapTuple old_row;
RI_QueryKey qkey;
void *qplan;
- Datum upd_values[RI_MAX_NUMKEYS * 2];
- char upd_nulls[RI_MAX_NUMKEYS * 2 + 1];
- bool isnull;
int i;
int j;
- AclId save_uid;
- AclId fk_owner;
ReferentialIntegritySnapshotOverride = true;
* Check that this is a valid trigger call on the right time and
* event.
*/
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_cascade_upd() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_cascade_upd() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_cascade_upd() must be fired for UPDATE");
+ ri_CheckTrigger(fcinfo, "RI_FKey_cascade_upd", RI_TRIGTYPE_UPDATE);
/*
* Check for the correct # of call arguments
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
- fk_owner = RelationGetForm(fk_rel)->relowner;
switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
{
}
if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_cascade_upd()");
+ elog(ERROR, "SPI_connect() failed in RI_FKey_cascade_upd()");
/*
* Fetch or prepare a saved plan for the cascaded update of
}
/*
- * We have a plan now. Build up the arguments for SPI_execp()
- * from the key values in the updated PK tuple.
+ * We have a plan now. Run it to update the existing references.
*/
- for (i = 0, j = qkey.nkeypairs; i < qkey.nkeypairs; i++, j++)
- {
- upd_values[i] = SPI_getbinval(new_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- upd_nulls[i] = 'n';
- else
- upd_nulls[i] = ' ';
-
- upd_values[j] = SPI_getbinval(old_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- upd_nulls[j] = 'n';
- else
- upd_nulls[j] = ' ';
- }
- upd_nulls[j] = '\0';
-
- /*
- * Now update the existing references
- */
- save_uid = GetUserId();
- SetUserId(fk_owner);
-
- if (SPI_execp(qplan, upd_values, upd_nulls, 0) != SPI_OK_UPDATE)
- elog(ERROR, "SPI_execp() failed in RI_FKey_cascade_upd()");
-
- SetUserId(save_uid);
+ ri_PerformCheck(&qkey, qplan,
+ fk_rel, pk_rel,
+ old_row, new_row,
+ SPI_OK_UPDATE,
+ tgargs[RI_CONSTRAINT_NAME_ARGNO]);
if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_cascade_upd()");
+ elog(ERROR, "SPI_finish() failed in RI_FKey_cascade_upd()");
heap_close(fk_rel, RowExclusiveLock);
HeapTuple old_row;
RI_QueryKey qkey;
void *qplan;
- Datum del_values[RI_MAX_NUMKEYS];
- char del_nulls[RI_MAX_NUMKEYS + 1];
- bool isnull;
int i;
- AclId save_uid;
- AclId fk_owner;
ReferentialIntegritySnapshotOverride = true;
* Check that this is a valid trigger call on the right time and
* event.
*/
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_restrict_del() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_restrict_del() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_restrict_del() must be fired for DELETE");
+ ri_CheckTrigger(fcinfo, "RI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
/*
* Check for the correct # of call arguments
fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
pk_rel = trigdata->tg_relation;
old_row = trigdata->tg_trigtuple;
- fk_owner = RelationGetForm(fk_rel)->relowner;
switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
{
}
if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_restrict_del()");
+ elog(ERROR, "SPI_connect() failed in RI_FKey_restrict_del()");
/*
* Fetch or prepare a saved plan for the restrict delete
}
/*
- * We have a plan now. Build up the arguments for SPI_execp()
- * from the key values in the deleted PK tuple.
+ * We have a plan now. Run it to check for existing references.
*/
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- del_values[i] = SPI_getbinval(old_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- del_nulls[i] = 'n';
- else
- del_nulls[i] = ' ';
- }
- del_nulls[i] = '\0';
-
- /*
- * Now check for existing references
- */
- save_uid = GetUserId();
- SetUserId(fk_owner);
-
- if (SPI_execp(qplan, del_values, del_nulls, 1) != SPI_OK_SELECT)
- elog(ERROR, "SPI_execp() failed in RI_FKey_restrict_del()");
-
- SetUserId(save_uid);
-
- if (SPI_processed > 0)
- elog(ERROR, "%s referential integrity violation - "
- "key in %s still referenced from %s",
- tgargs[RI_CONSTRAINT_NAME_ARGNO],
- RelationGetRelationName(pk_rel),
- RelationGetRelationName(fk_rel));
+ ri_PerformCheck(&qkey, qplan,
+ fk_rel, pk_rel,
+ old_row, NULL,
+ SPI_OK_SELECT,
+ tgargs[RI_CONSTRAINT_NAME_ARGNO]);
if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_restrict_del()");
+ elog(ERROR, "SPI_finish() failed in RI_FKey_restrict_del()");
heap_close(fk_rel, RowShareLock);
HeapTuple old_row;
RI_QueryKey qkey;
void *qplan;
- Datum upd_values[RI_MAX_NUMKEYS];
- char upd_nulls[RI_MAX_NUMKEYS + 1];
- bool isnull;
int i;
- AclId save_uid;
- AclId fk_owner;
ReferentialIntegritySnapshotOverride = true;
* Check that this is a valid trigger call on the right time and
* event.
*/
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_restrict_upd() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_restrict_upd() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_restrict_upd() must be fired for UPDATE");
+ ri_CheckTrigger(fcinfo, "RI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
/*
* Check for the correct # of call arguments
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
- fk_owner = RelationGetForm(fk_rel)->relowner;
switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
{
}
if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_restrict_upd()");
+ elog(ERROR, "SPI_connect() failed in RI_FKey_restrict_upd()");
/*
* Fetch or prepare a saved plan for the restrict update
}
/*
- * We have a plan now. Build up the arguments for SPI_execp()
- * from the key values in the updated PK tuple.
- */
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- upd_values[i] = SPI_getbinval(old_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- upd_nulls[i] = 'n';
- else
- upd_nulls[i] = ' ';
- }
- upd_nulls[i] = '\0';
-
- /*
- * Now check for existing references
+ * We have a plan now. Run it to check for existing references.
*/
- save_uid = GetUserId();
- SetUserId(fk_owner);
-
- SetUserId(RelationGetForm(pk_rel)->relowner);
-
- if (SPI_execp(qplan, upd_values, upd_nulls, 1) != SPI_OK_SELECT)
- elog(ERROR, "SPI_execp() failed in RI_FKey_restrict_upd()");
-
- SetUserId(save_uid);
-
- if (SPI_processed > 0)
- elog(ERROR, "%s referential integrity violation - "
- "key in %s still referenced from %s",
- tgargs[RI_CONSTRAINT_NAME_ARGNO],
- RelationGetRelationName(pk_rel),
- RelationGetRelationName(fk_rel));
+ ri_PerformCheck(&qkey, qplan,
+ fk_rel, pk_rel,
+ old_row, NULL,
+ SPI_OK_SELECT,
+ tgargs[RI_CONSTRAINT_NAME_ARGNO]);
if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_restrict_upd()");
+ elog(ERROR, "SPI_finish() failed in RI_FKey_restrict_upd()");
heap_close(fk_rel, RowShareLock);
HeapTuple old_row;
RI_QueryKey qkey;
void *qplan;
- Datum upd_values[RI_MAX_NUMKEYS];
- char upd_nulls[RI_MAX_NUMKEYS + 1];
- bool isnull;
int i;
- AclId save_uid;
- AclId fk_owner;
ReferentialIntegritySnapshotOverride = true;
* Check that this is a valid trigger call on the right time and
* event.
*/
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_setnull_del() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_setnull_del() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_setnull_del() must be fired for DELETE");
+ ri_CheckTrigger(fcinfo, "RI_FKey_setnull_del", RI_TRIGTYPE_DELETE);
/*
* Check for the correct # of call arguments
fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
old_row = trigdata->tg_trigtuple;
- fk_owner = RelationGetForm(fk_rel)->relowner;
switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
{
}
if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_setnull_del()");
+ elog(ERROR, "SPI_connect() failed in RI_FKey_setnull_del()");
/*
* Fetch or prepare a saved plan for the set null delete
}
/*
- * We have a plan now. Build up the arguments for SPI_execp()
- * from the key values in the updated PK tuple.
- */
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- upd_values[i] = SPI_getbinval(old_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- upd_nulls[i] = 'n';
- else
- upd_nulls[i] = ' ';
- }
- upd_nulls[i] = '\0';
-
- /*
- * Now update the existing references
+ * We have a plan now. Run it to check for existing references.
*/
- save_uid = GetUserId();
- SetUserId(fk_owner);
-
- if (SPI_execp(qplan, upd_values, upd_nulls, 0) != SPI_OK_UPDATE)
- elog(ERROR, "SPI_execp() failed in RI_FKey_setnull_del()");
-
- SetUserId(save_uid);
+ ri_PerformCheck(&qkey, qplan,
+ fk_rel, pk_rel,
+ old_row, NULL,
+ SPI_OK_UPDATE,
+ tgargs[RI_CONSTRAINT_NAME_ARGNO]);
if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_setnull_del()");
+ elog(ERROR, "SPI_finish() failed in RI_FKey_setnull_del()");
heap_close(fk_rel, RowExclusiveLock);
HeapTuple old_row;
RI_QueryKey qkey;
void *qplan;
- Datum upd_values[RI_MAX_NUMKEYS];
- char upd_nulls[RI_MAX_NUMKEYS + 1];
- bool isnull;
int i;
int match_type;
bool use_cached_query;
- AclId save_uid;
- AclId fk_owner;
ReferentialIntegritySnapshotOverride = true;
* Check that this is a valid trigger call on the right time and
* event.
*/
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_setnull_upd() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_setnull_upd() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_setnull_upd() must be fired for UPDATE");
+ ri_CheckTrigger(fcinfo, "RI_FKey_setnull_upd", RI_TRIGTYPE_UPDATE);
/*
* Check for the correct # of call arguments
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
- fk_owner = RelationGetForm(fk_rel)->relowner;
switch (match_type)
{
}
if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_setnull_upd()");
+ elog(ERROR, "SPI_connect() failed in RI_FKey_setnull_upd()");
/*
* "MATCH <unspecified>" only changes columns corresponding to
}
/*
- * We have a plan now. Build up the arguments for SPI_execp()
- * from the key values in the updated PK tuple.
+ * We have a plan now. Run it to update the existing references.
*/
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- upd_values[i] = SPI_getbinval(old_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- upd_nulls[i] = 'n';
- else
- upd_nulls[i] = ' ';
- }
- upd_nulls[i] = '\0';
-
- /*
- * Now update the existing references
- */
- save_uid = GetUserId();
- SetUserId(fk_owner);
-
- if (SPI_execp(qplan, upd_values, upd_nulls, 0) != SPI_OK_UPDATE)
- elog(ERROR, "SPI_execp() failed in RI_FKey_setnull_upd()");
-
- SetUserId(save_uid);
+ ri_PerformCheck(&qkey, qplan,
+ fk_rel, pk_rel,
+ old_row, NULL,
+ SPI_OK_UPDATE,
+ tgargs[RI_CONSTRAINT_NAME_ARGNO]);
if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_setnull_upd()");
+ elog(ERROR, "SPI_finish() failed in RI_FKey_setnull_upd()");
heap_close(fk_rel, RowExclusiveLock);
HeapTuple old_row;
RI_QueryKey qkey;
void *qplan;
- Datum upd_values[RI_MAX_NUMKEYS];
- char upd_nulls[RI_MAX_NUMKEYS + 1];
- bool isnull;
- int i;
- AclId save_uid;
- AclId fk_owner;
ReferentialIntegritySnapshotOverride = true;
* Check that this is a valid trigger call on the right time and
* event.
*/
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_setdefault_del() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_setdefault_del() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_setdefault_del() must be fired for DELETE");
+ ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_del", RI_TRIGTYPE_DELETE);
/*
* Check for the correct # of call arguments
fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
old_row = trigdata->tg_trigtuple;
- fk_owner = RelationGetForm(fk_rel)->relowner;
switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
{
}
if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_setdefault_del()");
+ elog(ERROR, "SPI_connect() failed in RI_FKey_setdefault_del()");
/*
* Prepare a plan for the set default delete operation.
}
/*
- * We have a plan now. Build up the arguments for SPI_execp()
- * from the key values in the deleted PK tuple.
+ * We have a plan now. Run it to update the existing references.
*/
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- upd_values[i] = SPI_getbinval(old_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- upd_nulls[i] = 'n';
- else
- upd_nulls[i] = ' ';
- }
- upd_nulls[i] = '\0';
-
- /*
- * Now update the existing references
- */
- save_uid = GetUserId();
- SetUserId(fk_owner);
-
- if (SPI_execp(qplan, upd_values, upd_nulls, 0) != SPI_OK_UPDATE)
- elog(ERROR, "SPI_execp() failed in RI_FKey_setdefault_del()");
-
- SetUserId(save_uid);
+ ri_PerformCheck(&qkey, qplan,
+ fk_rel, pk_rel,
+ old_row, NULL,
+ SPI_OK_UPDATE,
+ tgargs[RI_CONSTRAINT_NAME_ARGNO]);
if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_setdefault_del()");
+ elog(ERROR, "SPI_finish() failed in RI_FKey_setdefault_del()");
heap_close(fk_rel, RowExclusiveLock);
HeapTuple old_row;
RI_QueryKey qkey;
void *qplan;
- Datum upd_values[RI_MAX_NUMKEYS];
- char upd_nulls[RI_MAX_NUMKEYS + 1];
- bool isnull;
- int i;
int match_type;
- AclId save_uid;
- AclId fk_owner;
ReferentialIntegritySnapshotOverride = true;
* Check that this is a valid trigger call on the right time and
* event.
*/
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_setdefault_upd() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_setdefault_upd() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_setdefault_upd() must be fired for UPDATE");
+ ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_upd", RI_TRIGTYPE_UPDATE);
/*
* Check for the correct # of call arguments
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
- fk_owner = RelationGetForm(fk_rel)->relowner;
match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
}
if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_setdefault_upd()");
+ elog(ERROR, "SPI_connect() failed in RI_FKey_setdefault_upd()");
/*
* Prepare a plan for the set default delete operation.
}
/*
- * We have a plan now. Build up the arguments for SPI_execp()
- * from the key values in the deleted PK tuple.
- */
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- upd_values[i] = SPI_getbinval(old_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- upd_nulls[i] = 'n';
- else
- upd_nulls[i] = ' ';
- }
- upd_nulls[i] = '\0';
-
- /*
- * Now update the existing references
+ * We have a plan now. Run it to update the existing references.
*/
- save_uid = GetUserId();
- SetUserId(fk_owner);
-
- if (SPI_execp(qplan, upd_values, upd_nulls, 0) != SPI_OK_UPDATE)
- elog(ERROR, "SPI_execp() failed in RI_FKey_setdefault_upd()");
-
- SetUserId(save_uid);
+ ri_PerformCheck(&qkey, qplan,
+ fk_rel, pk_rel,
+ old_row, NULL,
+ SPI_OK_UPDATE,
+ tgargs[RI_CONSTRAINT_NAME_ARGNO]);
if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_setdefault_upd()");
+ elog(ERROR, "SPI_finish() failed in RI_FKey_setdefault_upd()");
heap_close(fk_rel, RowExclusiveLock);
case RI_MATCH_TYPE_UNSPECIFIED:
case RI_MATCH_TYPE_FULL:
ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- 0,
+ RI_PLAN_KEYEQUAL_UPD,
fk_rel, pk_rel,
tgnargs, tgargs);
}
}
+/*
+ * Check that RI trigger function was called in expected context
+ */
+static void
+ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname, int tgkind)
+{
+ TriggerData *trigdata = (TriggerData *) fcinfo->context;
+
+ if (!CALLED_AS_TRIGGER(fcinfo))
+ elog(ERROR, "%s() not fired by trigger manager", funcname);
+ if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
+ !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
+ elog(ERROR, "%s() must be fired AFTER ROW", funcname);
+
+ switch (tgkind)
+ {
+ case RI_TRIGTYPE_INSERT:
+ if (!TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+ elog(ERROR, "%s() must be fired for INSERT", funcname);
+ break;
+ case RI_TRIGTYPE_UPDATE:
+ if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+ elog(ERROR, "%s() must be fired for UPDATE", funcname);
+ break;
+ case RI_TRIGTYPE_INUP:
+ if (!TRIGGER_FIRED_BY_INSERT(trigdata->tg_event) &&
+ !TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+ elog(ERROR, "%s() must be fired for INSERT or UPDATE",
+ funcname);
+ break;
+ case RI_TRIGTYPE_DELETE:
+ if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+ elog(ERROR, "%s() must be fired for DELETE", funcname);
+ break;
+ }
+}
+
+
+/*
+ * Perform a query to enforce an RI restriction
+ */
+static bool
+ri_PerformCheck(RI_QueryKey *qkey, void *qplan,
+ Relation fk_rel, Relation pk_rel,
+ HeapTuple old_tuple, HeapTuple new_tuple,
+ int expect_OK, const char *constrname)
+{
+ Relation query_rel,
+ source_rel;
+ int key_idx;
+ int limit;
+ int spi_result;
+ AclId save_uid;
+ Datum vals[RI_MAX_NUMKEYS * 2];
+ char nulls[RI_MAX_NUMKEYS * 2];
+
+ /*
+ * The query is always run against the FK table except
+ * when this is an update/insert trigger on the FK table itself -
+ * either RI_PLAN_CHECK_LOOKUPPK or RI_PLAN_CHECK_LOOKUPPK_NOCOLS
+ */
+ if (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK ||
+ qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK_NOCOLS)
+ query_rel = pk_rel;
+ else
+ query_rel = fk_rel;
+
+ /*
+ * The values for the query are taken from the table on which the trigger
+ * is called - it is normally the other one with respect to query_rel.
+ * An exception is ri_Check_Pk_Match(), which uses the PK table for both
+ * (the case when constrname == NULL)
+ */
+ if (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK && constrname != NULL)
+ {
+ source_rel = fk_rel;
+ key_idx = RI_KEYPAIR_FK_IDX;
+ }
+ else
+ {
+ source_rel = pk_rel;
+ key_idx = RI_KEYPAIR_PK_IDX;
+ }
+
+ /* Extract the parameters to be passed into the query */
+ if (new_tuple)
+ {
+ ri_ExtractValues(qkey, key_idx, source_rel, new_tuple,
+ vals, nulls);
+ if (old_tuple)
+ ri_ExtractValues(qkey, key_idx, source_rel, old_tuple,
+ vals + qkey->nkeypairs, nulls + qkey->nkeypairs);
+ }
+ else
+ {
+ ri_ExtractValues(qkey, key_idx, source_rel, old_tuple,
+ vals, nulls);
+ }
+
+ /* Switch to proper UID to perform check as */
+ save_uid = GetUserId();
+ SetUserId(RelationGetForm(query_rel)->relowner);
+
+ /*
+ * If this is a select query (e.g., for a 'no action' or 'restrict'
+ * trigger), we only need to see if there is a single row in the table,
+ * matching the key. Otherwise, limit = 0 - because we want the query to
+ * affect ALL the matching rows.
+ */
+ limit = (expect_OK == SPI_OK_SELECT) ? 1 : 0;
+
+ /* Run the plan */
+ spi_result = SPI_execp(qplan, vals, nulls, limit);
+
+ /* Restore UID */
+ SetUserId(save_uid);
+
+ /* Check result */
+ if (spi_result < 0)
+ elog(ERROR, "SPI_execp() failed in ri_PerformCheck()");
+
+ if (expect_OK >= 0 && spi_result != expect_OK)
+ ri_ReportViolation(qkey, constrname ? constrname : "",
+ pk_rel, fk_rel,
+ new_tuple ? new_tuple : old_tuple,
+ true);
+
+ /* XXX wouldn't it be clearer to do this part at the caller? */
+ if (constrname && expect_OK == SPI_OK_SELECT &&
+ (SPI_processed==0) == (qkey->constr_queryno==RI_PLAN_CHECK_LOOKUPPK))
+ ri_ReportViolation(qkey, constrname,
+ pk_rel, fk_rel,
+ new_tuple ? new_tuple : old_tuple,
+ false);
+
+ return SPI_processed != 0;
+}
+
+/*
+ * Extract fields from a tuple into Datum/nulls arrays
+ */
+static void
+ri_ExtractValues(RI_QueryKey *qkey, int key_idx,
+ Relation rel, HeapTuple tuple,
+ Datum *vals, char *nulls)
+{
+ int i;
+ bool isnull;
+
+ for (i = 0; i < qkey->nkeypairs; i++)
+ {
+ vals[i] = SPI_getbinval(tuple, rel->rd_att,
+ qkey->keypair[i][key_idx],
+ &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 referenced from FK not found in PK'.
+ * Otherwise, the attr names and values come from the PK table and the
+ * message looks like 'key blah in PK still referenced from FK'.
+ */
+static void
+ri_ReportViolation(RI_QueryKey *qkey, const char *constrname,
+ Relation pk_rel, Relation fk_rel,
+ HeapTuple violator, bool spi_err)
+{
+#define BUFLENGTH 512
+ char key_names[BUFLENGTH];
+ char key_values[BUFLENGTH];
+ char *name_ptr = key_names;
+ char *val_ptr = key_values;
+ bool onfk;
+ Relation rel,
+ other_rel;
+ int idx,
+ key_idx;
+
+ /*
+ * rel is set to where the tuple description is coming from, and it also
+ * is the first relation mentioned in the message, other_rel is
+ * respectively the other relation.
+ */
+ onfk = (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK);
+ if (onfk)
+ {
+ rel = fk_rel;
+ other_rel = pk_rel;
+ key_idx = RI_KEYPAIR_FK_IDX;
+ }
+ else
+ {
+ rel = pk_rel;
+ other_rel = fk_rel;
+ key_idx = RI_KEYPAIR_PK_IDX;
+ }
+
+ /*
+ * Special case - if there are no keys at all, this is a 'no column'
+ * constraint - no need to try to extract the values, and the message
+ * in this case looks different.
+ */
+ if (qkey->nkeypairs == 0)
+ {
+ if (spi_err)
+ elog(ERROR, "%s referential action on %s from %s rewritten by rule",
+ constrname,
+ RelationGetRelationName(fk_rel),
+ RelationGetRelationName(pk_rel));
+ else
+ elog(ERROR, "%s referential integrity violation - no rows found in %s",
+ constrname, RelationGetRelationName(pk_rel));
+ }
+
+ /* Get printable versions of the keys involved */
+ for (idx = 0; idx < qkey->nkeypairs; idx++)
+ {
+ int fnum = qkey->keypair[idx][key_idx];
+ char *name,
+ *val;
+
+ name = SPI_fname(rel->rd_att, fnum);
+ val = SPI_getvalue(violator, rel->rd_att, fnum);
+ if (!val)
+ val = "null";
+
+ /*
+ * Go to "..." if name or value doesn't fit in buffer. We reserve
+ * 5 bytes to ensure we can add comma, "...", null.
+ */
+ if (strlen(name) >= (key_names + BUFLENGTH - 5) - name_ptr ||
+ strlen(val) >= (key_values + BUFLENGTH - 5) - val_ptr)
+ {
+ sprintf(name_ptr, "...");
+ sprintf(val_ptr, "...");
+ break;
+ }
+
+ name_ptr += sprintf(name_ptr, "%s%s", idx > 0 ? "," : "", name);
+ val_ptr += sprintf(val_ptr, "%s%s", idx > 0 ? "," : "", val);
+ }
+
+ if (spi_err)
+ elog(ERROR, "%s referential action on %s from %s for (%s)=(%s) rewritten by rule",
+ constrname,
+ RelationGetRelationName(fk_rel),
+ RelationGetRelationName(pk_rel),
+ key_names, key_values);
+ else if (onfk)
+ elog(ERROR, "%s referential integrity violation - key (%s)=(%s) referenced from %s not found in %s",
+ constrname, key_names, key_values,
+ RelationGetRelationName(rel),
+ RelationGetRelationName(other_rel));
+ else
+ elog(ERROR, "%s referential integrity violation - key (%s)=(%s) in %s still referenced from %s",
+ constrname, key_names, key_values,
+ RelationGetRelationName(rel),
+ RelationGetRelationName(other_rel));
+}
+
/* ----------
* ri_BuildQueryKeyPkCheck -
*
INSERT INTO FKTABLE VALUES (NULL, 1);
-- Insert a failed row into FK TABLE
INSERT INTO FKTABLE VALUES (100, 2);
-ERROR: $1 referential integrity violation - key referenced from fktable not found in pktable
+ERROR: $1 referential integrity violation - key (ftest1)=(100) referenced from fktable not found in pktable
-- Check FKTABLE
SELECT * FROM FKTABLE;
ftest1 | ftest2
INSERT INTO FKTABLE VALUES (NULL, NULL, 0);
-- Insert failed rows into FK TABLE
INSERT INTO FKTABLE VALUES (100, 2, 4);
-ERROR: constrname referential integrity violation - key referenced from fktable not found in pktable
+ERROR: constrname referential integrity violation - key (ftest1,ftest2)=(100,2) referenced from fktable not found in pktable
INSERT INTO FKTABLE VALUES (2, 2, 4);
-ERROR: constrname referential integrity violation - key referenced from fktable not found in pktable
+ERROR: constrname referential integrity violation - key (ftest1,ftest2)=(2,2) referenced from fktable not found in pktable
INSERT INTO FKTABLE VALUES (NULL, 2, 4);
ERROR: constrname referential integrity violation - MATCH FULL doesn't allow mixing of NULL and NON-NULL key values
INSERT INTO FKTABLE VALUES (1, NULL, 4);
INSERT INTO FKTABLE VALUES (NULL, NULL, 0);
-- Insert failed rows into FK TABLE
INSERT INTO FKTABLE VALUES (100, 2, 4);
-ERROR: constrname2 referential integrity violation - key referenced from fktable not found in pktable
+ERROR: constrname2 referential integrity violation - key (ftest1,ftest2)=(100,2) referenced from fktable not found in pktable
INSERT INTO FKTABLE VALUES (2, 2, 4);
-ERROR: constrname2 referential integrity violation - key referenced from fktable not found in pktable
+ERROR: constrname2 referential integrity violation - key (ftest1,ftest2)=(2,2) referenced from fktable not found in pktable
INSERT INTO FKTABLE VALUES (NULL, 2, 4);
ERROR: constrname2 referential integrity violation - MATCH FULL doesn't allow mixing of NULL and NON-NULL key values
INSERT INTO FKTABLE VALUES (1, NULL, 4);
INSERT INTO FKTABLE VALUES (NULL, 1);
-- Insert a failed row into FK TABLE
INSERT INTO FKTABLE VALUES (100, 2);
-ERROR: $1 referential integrity violation - key referenced from fktable not found in pktable
+ERROR: $1 referential integrity violation - key (ftest1)=(100) referenced from fktable not found in pktable
-- Check FKTABLE
SELECT * FROM FKTABLE;
ftest1 | ftest2
-- Delete a row from PK TABLE (should fail)
DELETE FROM PKTABLE WHERE ptest1=1;
-ERROR: $1 referential integrity violation - key in pktable still referenced from fktable
+ERROR: $1 referential integrity violation - key (ptest1)=(1) in pktable still referenced from fktable
-- Delete a row from PK TABLE (should succeed)
DELETE FROM PKTABLE WHERE ptest1=5;
-- Check PKTABLE for deletes
-- Update a row from PK TABLE (should fail)
UPDATE PKTABLE SET ptest1=0 WHERE ptest1=2;
-ERROR: $1 referential integrity violation - key in pktable still referenced from fktable
+ERROR: $1 referential integrity violation - key (ptest1)=(2) in pktable still referenced from fktable
-- Update a row from PK TABLE (should succeed)
UPDATE PKTABLE SET ptest1=0 WHERE ptest1=4;
-- Check PKTABLE for updates
INSERT INTO FKTABLE VALUES (NULL, 3, 4, 5);
-- Insert a failed values
INSERT INTO FKTABLE VALUES (1, 2, 7, 6);
-ERROR: constrname3 referential integrity violation - key referenced from fktable not found in pktable
+ERROR: constrname3 referential integrity violation - key (ftest1,ftest2,ftest3)=(1,2,7) referenced from fktable not found in pktable
-- Show FKTABLE
SELECT * from FKTABLE;
ftest1 | ftest2 | ftest3 | ftest4
-- Try to update something that should fail
UPDATE PKTABLE set ptest2=5 where ptest2=2;
-ERROR: constrname3 referential integrity violation - key in pktable still referenced from fktable
+ERROR: constrname3 referential integrity violation - key (ptest1,ptest2,ptest3)=(1,2,3) in pktable still referenced from fktable
-- Try to update something that should succeed
UPDATE PKTABLE set ptest1=1 WHERE ptest2=3;
-- Try to delete something that should fail
DELETE FROM PKTABLE where ptest1=1 and ptest2=2 and ptest3=3;
-ERROR: constrname3 referential integrity violation - key in pktable still referenced from fktable
+ERROR: constrname3 referential integrity violation - key (ptest1,ptest2,ptest3)=(1,2,3) in pktable still referenced from fktable
-- Try to delete something that should work
DELETE FROM PKTABLE where ptest1=2;
-- Show PKTABLE and FKTABLE
INSERT INTO FKTABLE VALUES (NULL, 3, 4, 5);
-- Insert a failed values
INSERT INTO FKTABLE VALUES (1, 2, 7, 6);
-ERROR: constrname3 referential integrity violation - key referenced from fktable not found in pktable
+ERROR: constrname3 referential integrity violation - key (ftest1,ftest2,ftest3)=(1,2,7) referenced from fktable not found in pktable
-- Show FKTABLE
SELECT * from FKTABLE;
ftest1 | ftest2 | ftest3 | ftest4
INSERT INTO FKTABLE VALUES (NULL, 3, 4, 5);
-- Insert a failed values
INSERT INTO FKTABLE VALUES (1, 2, 7, 6);
-ERROR: constrname3 referential integrity violation - key referenced from fktable not found in pktable
+ERROR: constrname3 referential integrity violation - key (ftest1,ftest2,ftest3)=(1,2,7) referenced from fktable not found in pktable
-- Show FKTABLE
SELECT * from FKTABLE;
ftest1 | ftest2 | ftest3 | ftest4
INSERT INTO FKTABLE VALUES (NULL, 3, 4, 5);
-- Insert a failed values
INSERT INTO FKTABLE VALUES (1, 2, 7, 6);
-ERROR: constrname3 referential integrity violation - key referenced from fktable not found in pktable
+ERROR: constrname3 referential integrity violation - key (ftest1,ftest2,ftest3)=(1,2,7) referenced from fktable not found in pktable
-- Show FKTABLE
SELECT * from FKTABLE;
ftest1 | ftest2 | ftest3 | ftest4
-- Try to update something that will fail
UPDATE PKTABLE set ptest2=5 where ptest2=2;
-ERROR: constrname3 referential integrity violation - key referenced from fktable not found in pktable
+ERROR: constrname3 referential integrity violation - key (ftest1,ftest2,ftest3)=(1,-1,3) referenced from fktable not found in pktable
-- Try to update something that will set default
UPDATE PKTABLE set ptest1=0, ptest2=5, ptest3=10 where ptest2=2;
UPDATE PKTABLE set ptest2=10 where ptest2=4;
insert into pktable(base1) values (2);
-- let's insert a non-existant fktable value
insert into fktable(ftest1) values (3);
-ERROR: $1 referential integrity violation - key referenced from fktable not found in pktable
+ERROR: $1 referential integrity violation - key (ftest1)=(3) referenced from fktable not found in pktable
-- let's make a valid row for that
insert into pktable(base1) values (3);
insert into fktable(ftest1) values (3);
-- let's try removing a row that should fail from pktable
delete from pktable where base1>2;
-ERROR: $1 referential integrity violation - key in pktable still referenced from fktable
+ERROR: $1 referential integrity violation - key (base1)=(3) in pktable still referenced from fktable
-- okay, let's try updating all of the base1 values to *4
-- which should fail.
update pktable set base1=base1*4;
-ERROR: $1 referential integrity violation - key in pktable still referenced from fktable
+ERROR: $1 referential integrity violation - key (base1)=(3) in pktable still referenced from fktable
-- okay, let's try an update that should work.
update pktable set base1=base1*4 where base1<3;
-- and a delete that should work
insert into pktable(base1, ptest1) values (2, 2);
-- let's insert a non-existant fktable value
insert into fktable(ftest1, ftest2) values (3, 1);
-ERROR: $1 referential integrity violation - key referenced from fktable not found in pktable
+ERROR: $1 referential integrity violation - key (ftest1,ftest2)=(3,1) referenced from fktable not found in pktable
-- let's make a valid row for that
insert into pktable(base1,ptest1) values (3, 1);
insert into fktable(ftest1, ftest2) values (3, 1);
-- let's try removing a row that should fail from pktable
delete from pktable where base1>2;
-ERROR: $1 referential integrity violation - key in pktable still referenced from fktable
+ERROR: $1 referential integrity violation - key (base1,ptest1)=(3,1) in pktable still referenced from fktable
-- okay, let's try updating all of the base1 values to *4
-- which should fail.
update pktable set base1=base1*4;
-ERROR: $1 referential integrity violation - key in pktable still referenced from fktable
+ERROR: $1 referential integrity violation - key (base1,ptest1)=(3,1) in pktable still referenced from fktable
-- okay, let's try an update that should work.
update pktable set base1=base1*4 where base1<3;
-- and a delete that should work
insert into pktable (base1, ptest1, base2, ptest2) values (1, 3, 2, 2);
-- fails (3,2) isn't in base1, ptest1
insert into pktable (base1, ptest1, base2, ptest2) values (2, 3, 3, 2);
-ERROR: $1 referential integrity violation - key referenced from pktable not found in pktable
+ERROR: $1 referential integrity violation - key (base2,ptest2)=(3,2) referenced from pktable not found in pktable
-- fails (2,2) is being referenced
delete from pktable where base1=2;
-ERROR: $1 referential integrity violation - key in pktable still referenced from pktable
+ERROR: $1 referential integrity violation - key (base1,ptest1)=(2,2) in pktable still referenced from pktable
-- fails (1,1) is being referenced (twice)
update pktable set base1=3 where base1=1;
-ERROR: $1 referential integrity violation - key referenced from pktable not found in pktable
+ERROR: $1 referential integrity violation - key (base2,ptest2)=(1,1) referenced from pktable not found in pktable
-- this sequence of two deletes will work, since after the first there will be no (2,*) references
delete from pktable where base2=2;
delete from pktable where base1=2;
NOTICE: CREATE TABLE will create implicit trigger(s) for FOREIGN KEY check(s)
-- default to immediate: should fail
INSERT INTO fktable VALUES (5, 10);
-ERROR: $1 referential integrity violation - key referenced from fktable not found in pktable
+ERROR: $1 referential integrity violation - key (fk)=(10) referenced from fktable not found in pktable
-- explicitely defer the constraint
BEGIN;
SET CONSTRAINTS ALL DEFERRED;
SET CONSTRAINTS ALL IMMEDIATE;
-- should fail
INSERT INTO fktable VALUES (500, 1000);
-ERROR: $1 referential integrity violation - key referenced from fktable not found in pktable
+ERROR: $1 referential integrity violation - key (fk)=(1000) referenced from fktable not found in pktable
COMMIT;
DROP TABLE fktable, pktable;
-- tricky behavior: according to SQL99, if a deferred constraint is set
INSERT INTO fktable VALUES (1000, 2000);
-- should cause transaction abort, due to preceding error
SET CONSTRAINTS ALL IMMEDIATE;
-ERROR: $1 referential integrity violation - key referenced from fktable not found in pktable
+ERROR: $1 referential integrity violation - key (fk)=(2000) referenced from fktable not found in pktable
INSERT INTO pktable VALUES (2000, 3); -- too late
ERROR: current transaction is aborted, queries ignored until end of transaction block
COMMIT;