Commit 0afdecc1 authored by Peter Eisentraut's avatar Peter Eisentraut

Remove unnecessary unused MATCH PARTIAL code

ri_triggers.c spends a lot of space catering to a not-yet-implemented
MATCH PARTIAL option.  An actual implementation would probably not use
the existing code structure anyway, so let's just simplify this for
now.

First, have ri_FetchConstraintInfo() check that riinfo->confmatchtype
is valid.  Then we don't have to repeat that everywhere.

In the various referential action functions, we don't need to pay
attention to the match type at all right now, so remove all that code.
A future MATCH PARTIAL implementation would probably have some
conditions added to the present code, but it won't need an entirely
separate switch branch in each case.

In RI_FKey_fk_upd_check_required(), reorganize the code to make it
much simpler.
Reviewed-by: default avatarCorey Huinker <corey.huinker@gmail.com>
Discussion: https://www.postgresql.org/message-id/flat/0ccdd3e1-10b0-dd05-d8a7-183507c11eb1%402ndquadrant.com
parent 22c0d52b
...@@ -20,14 +20,6 @@ ...@@ -20,14 +20,6 @@
* ---------- * ----------
*/ */
/* ----------
* Internal TODO:
*
* Add MATCH PARTIAL logic.
* ----------
*/
#include "postgres.h" #include "postgres.h"
#include "access/heapam.h" #include "access/heapam.h"
...@@ -308,11 +300,6 @@ RI_FKey_check(TriggerData *trigdata) ...@@ -308,11 +300,6 @@ RI_FKey_check(TriggerData *trigdata)
fk_rel = trigdata->tg_relation; fk_rel = trigdata->tg_relation;
pk_rel = table_open(riinfo->pk_relid, RowShareLock); pk_rel = table_open(riinfo->pk_relid, RowShareLock);
if (riinfo->confmatchtype == FKCONSTR_MATCH_PARTIAL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
switch (ri_NullCheck(RelationGetDescr(fk_rel), newslot, riinfo, false)) switch (ri_NullCheck(RelationGetDescr(fk_rel), newslot, riinfo, false))
{ {
case RI_KEYS_ALL_NULL: case RI_KEYS_ALL_NULL:
...@@ -358,6 +345,7 @@ RI_FKey_check(TriggerData *trigdata) ...@@ -358,6 +345,7 @@ RI_FKey_check(TriggerData *trigdata)
table_close(pk_rel, RowShareLock); table_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL); return PointerGetDatum(NULL);
#ifdef NOT_USED
case FKCONSTR_MATCH_PARTIAL: case FKCONSTR_MATCH_PARTIAL:
/* /*
...@@ -366,16 +354,8 @@ RI_FKey_check(TriggerData *trigdata) ...@@ -366,16 +354,8 @@ RI_FKey_check(TriggerData *trigdata)
* to only include non-null columns, or by writing a * to only include non-null columns, or by writing a
* special version here) * special version here)
*/ */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
table_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
default:
elog(ERROR, "unrecognized confmatchtype: %d",
riinfo->confmatchtype);
break; break;
#endif
} }
case RI_KEYS_NONE_NULL: case RI_KEYS_NONE_NULL:
...@@ -716,123 +696,90 @@ ri_restrict(TriggerData *trigdata, bool is_no_action) ...@@ -716,123 +696,90 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
pk_rel = trigdata->tg_relation; pk_rel = trigdata->tg_relation;
old_slot = trigdata->tg_trigslot; old_slot = trigdata->tg_trigslot;
switch (riinfo->confmatchtype) /*
* If another PK row now exists providing the old key values, we
* should not do anything. However, this check should only be
* made in the NO ACTION case; in RESTRICT cases we don't wish to
* allow another row to be substituted.
*/
if (is_no_action &&
ri_Check_Pk_Match(pk_rel, fk_rel, old_slot, riinfo))
{ {
/* ---------- table_close(fk_rel, RowShareLock);
* SQL:2008 15.17 <Execution of referential actions> return PointerGetDatum(NULL);
* General rules 9) a) iv): }
* MATCH SIMPLE/FULL
* ... ON DELETE RESTRICT
* General rules 10) a) iv):
* MATCH SIMPLE/FULL
* ... ON UPDATE RESTRICT
* ----------
*/
case FKCONSTR_MATCH_SIMPLE:
case FKCONSTR_MATCH_FULL:
/*
* If another PK row now exists providing the old key values, we
* should not do anything. However, this check should only be
* made in the NO ACTION case; in RESTRICT cases we don't wish to
* allow another row to be substituted.
*/
if (is_no_action &&
ri_Check_Pk_Match(pk_rel, fk_rel, old_slot, riinfo))
{
table_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
}
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed");
/* if (SPI_connect() != SPI_OK_CONNECT)
* Fetch or prepare a saved plan for the restrict lookup (it's the elog(ERROR, "SPI_connect failed");
* same query for delete and update cases)
*/
ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_RESTRICT_CHECKREF);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) /*
{ * Fetch or prepare a saved plan for the restrict lookup (it's the
StringInfoData querybuf; * same query for delete and update cases)
char fkrelname[MAX_QUOTED_REL_NAME_LEN]; */
char attname[MAX_QUOTED_NAME_LEN]; ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_RESTRICT_CHECKREF);
char paramname[16];
const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
const char *fk_only;
int i;
/* ----------
* The query string built is
* SELECT 1 FROM [ONLY] <fktable> x WHERE $1 = fkatt1 [AND ...]
* FOR KEY SHARE OF x
* The type id's for the $ parameters are those of the
* corresponding PK attributes.
* ----------
*/
initStringInfo(&querybuf);
fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
"" : "ONLY ";
quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
fk_only, fkrelname);
querysep = "WHERE";
for (i = 0; i < riinfo->nkeys; i++)
{
Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
quoteOneName(attname,
RIAttName(fk_rel, riinfo->fk_attnums[i]));
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = "AND";
queryoids[i] = pk_type;
}
appendStringInfoString(&querybuf, " FOR KEY SHARE OF x");
/* Prepare and save the plan */ if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, {
&qkey, fk_rel, pk_rel, true); StringInfoData querybuf;
} char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
char paramname[16];
const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
const char *fk_only;
int i;
/* /* ----------
* We have a plan now. Run it to check for existing references. * The query string built is
*/ * SELECT 1 FROM [ONLY] <fktable> x WHERE $1 = fkatt1 [AND ...]
ri_PerformCheck(riinfo, &qkey, qplan, * FOR KEY SHARE OF x
fk_rel, pk_rel, * The type id's for the $ parameters are those of the
old_slot, NULL, * corresponding PK attributes.
true, /* must detect new rows */ * ----------
SPI_OK_SELECT); */
initStringInfo(&querybuf);
fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
"" : "ONLY ";
quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
fk_only, fkrelname);
querysep = "WHERE";
for (i = 0; i < riinfo->nkeys; i++)
{
Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
if (SPI_finish() != SPI_OK_FINISH) quoteOneName(attname,
elog(ERROR, "SPI_finish failed"); RIAttName(fk_rel, riinfo->fk_attnums[i]));
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = "AND";
queryoids[i] = pk_type;
}
appendStringInfoString(&querybuf, " FOR KEY SHARE OF x");
table_close(fk_rel, RowShareLock); /* Prepare and save the plan */
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
return PointerGetDatum(NULL); /*
* We have a plan now. Run it to check for existing references.
*/
ri_PerformCheck(riinfo, &qkey, qplan,
fk_rel, pk_rel,
old_slot, NULL,
true, /* must detect new rows */
SPI_OK_SELECT);
/* if (SPI_finish() != SPI_OK_FINISH)
* Handle MATCH PARTIAL restrict delete or update. elog(ERROR, "SPI_finish failed");
*/
case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
return PointerGetDatum(NULL);
default: table_close(fk_rel, RowShareLock);
elog(ERROR, "unrecognized confmatchtype: %d",
riinfo->confmatchtype);
break;
}
/* Never reached */
return PointerGetDatum(NULL); return PointerGetDatum(NULL);
} }
...@@ -876,103 +823,74 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) ...@@ -876,103 +823,74 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
pk_rel = trigdata->tg_relation; pk_rel = trigdata->tg_relation;
old_slot = trigdata->tg_trigslot; old_slot = trigdata->tg_trigslot;
switch (riinfo->confmatchtype) if (SPI_connect() != SPI_OK_CONNECT)
{ elog(ERROR, "SPI_connect failed");
/* ----------
* SQL:2008 15.17 <Execution of referential actions>
* General rules 9) a) i):
* MATCH SIMPLE/FULL
* ... ON DELETE CASCADE
* ----------
*/
case FKCONSTR_MATCH_SIMPLE:
case FKCONSTR_MATCH_FULL:
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed");
/*
* Fetch or prepare a saved plan for the cascaded delete
*/
ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_DEL_DODELETE);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) /*
{ * Fetch or prepare a saved plan for the cascaded delete
StringInfoData querybuf; */
char fkrelname[MAX_QUOTED_REL_NAME_LEN]; ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_DEL_DODELETE);
char attname[MAX_QUOTED_NAME_LEN];
char paramname[16];
const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
const char *fk_only;
/* ----------
* The query string built is
* DELETE FROM [ONLY] <fktable> WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
* corresponding PK attributes.
* ----------
*/
initStringInfo(&querybuf);
fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
"" : "ONLY ";
quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "DELETE FROM %s%s",
fk_only, fkrelname);
querysep = "WHERE";
for (i = 0; i < riinfo->nkeys; i++)
{
Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
quoteOneName(attname,
RIAttName(fk_rel, riinfo->fk_attnums[i]));
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = "AND";
queryoids[i] = pk_type;
}
/* Prepare and save the plan */ if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, {
&qkey, fk_rel, pk_rel, true); StringInfoData querybuf;
} char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
char paramname[16];
const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
const char *fk_only;
/* /* ----------
* We have a plan now. Build up the arguments from the key values * The query string built is
* in the deleted PK tuple and delete the referencing rows * DELETE FROM [ONLY] <fktable> WHERE $1 = fkatt1 [AND ...]
*/ * The type id's for the $ parameters are those of the
ri_PerformCheck(riinfo, &qkey, qplan, * corresponding PK attributes.
fk_rel, pk_rel, * ----------
old_slot, NULL, */
true, /* must detect new rows */ initStringInfo(&querybuf);
SPI_OK_DELETE); fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
"" : "ONLY ";
quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "DELETE FROM %s%s",
fk_only, fkrelname);
querysep = "WHERE";
for (i = 0; i < riinfo->nkeys; i++)
{
Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
if (SPI_finish() != SPI_OK_FINISH) quoteOneName(attname,
elog(ERROR, "SPI_finish failed"); RIAttName(fk_rel, riinfo->fk_attnums[i]));
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = "AND";
queryoids[i] = pk_type;
}
table_close(fk_rel, RowExclusiveLock); /* Prepare and save the plan */
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
return PointerGetDatum(NULL); /*
* 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,
fk_rel, pk_rel,
old_slot, NULL,
true, /* must detect new rows */
SPI_OK_DELETE);
/* if (SPI_finish() != SPI_OK_FINISH)
* Handle MATCH PARTIAL cascaded delete. elog(ERROR, "SPI_finish failed");
*/
case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
return PointerGetDatum(NULL);
default: table_close(fk_rel, RowExclusiveLock);
elog(ERROR, "unrecognized confmatchtype: %d",
riinfo->confmatchtype);
break;
}
/* Never reached */
return PointerGetDatum(NULL); return PointerGetDatum(NULL);
} }
...@@ -1020,115 +938,86 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) ...@@ -1020,115 +938,86 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
new_slot = trigdata->tg_newslot; new_slot = trigdata->tg_newslot;
old_slot = trigdata->tg_trigslot; old_slot = trigdata->tg_trigslot;
switch (riinfo->confmatchtype) if (SPI_connect() != SPI_OK_CONNECT)
{ elog(ERROR, "SPI_connect failed");
/* ----------
* SQL:2008 15.17 <Execution of referential actions>
* General rules 10) a) i):
* MATCH SIMPLE/FULL
* ... ON UPDATE CASCADE
* ----------
*/
case FKCONSTR_MATCH_SIMPLE:
case FKCONSTR_MATCH_FULL:
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed");
/*
* Fetch or prepare a saved plan for the cascaded update
*/
ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_UPD_DOUPDATE);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) /*
{ * Fetch or prepare a saved plan for the cascaded update
StringInfoData querybuf; */
StringInfoData qualbuf; ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_UPD_DOUPDATE);
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
char paramname[16];
const char *querysep;
const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS * 2];
const char *fk_only;
/* ----------
* The query string built is
* UPDATE [ONLY] <fktable> SET fkatt1 = $1 [, ...]
* WHERE $n = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
* corresponding PK attributes. Note that we are assuming
* there is an assignment cast from the PK to the FK type;
* else the parser will fail.
* ----------
*/
initStringInfo(&querybuf);
initStringInfo(&qualbuf);
fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
"" : "ONLY ";
quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "UPDATE %s%s SET",
fk_only, fkrelname);
querysep = "";
qualsep = "WHERE";
for (i = 0, j = riinfo->nkeys; i < riinfo->nkeys; i++, j++)
{
Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
quoteOneName(attname,
RIAttName(fk_rel, riinfo->fk_attnums[i]));
appendStringInfo(&querybuf,
"%s %s = $%d",
querysep, attname, i + 1);
sprintf(paramname, "$%d", j + 1);
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
queryoids[j] = pk_type;
}
appendStringInfoString(&querybuf, qualbuf.data);
/* Prepare and save the plan */ if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys * 2, queryoids, {
&qkey, fk_rel, pk_rel, true); StringInfoData querybuf;
} StringInfoData qualbuf;
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
char paramname[16];
const char *querysep;
const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS * 2];
const char *fk_only;
/* /* ----------
* We have a plan now. Run it to update the existing references. * The query string built is
*/ * UPDATE [ONLY] <fktable> SET fkatt1 = $1 [, ...]
ri_PerformCheck(riinfo, &qkey, qplan, * WHERE $n = fkatt1 [AND ...]
fk_rel, pk_rel, * The type id's for the $ parameters are those of the
old_slot, new_slot, * corresponding PK attributes. Note that we are assuming
true, /* must detect new rows */ * there is an assignment cast from the PK to the FK type;
SPI_OK_UPDATE); * else the parser will fail.
* ----------
*/
initStringInfo(&querybuf);
initStringInfo(&qualbuf);
fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
"" : "ONLY ";
quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "UPDATE %s%s SET",
fk_only, fkrelname);
querysep = "";
qualsep = "WHERE";
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]);
if (SPI_finish() != SPI_OK_FINISH) quoteOneName(attname,
elog(ERROR, "SPI_finish failed"); RIAttName(fk_rel, riinfo->fk_attnums[i]));
appendStringInfo(&querybuf,
"%s %s = $%d",
querysep, attname, i + 1);
sprintf(paramname, "$%d", j + 1);
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
queryoids[j] = pk_type;
}
appendStringInfoString(&querybuf, qualbuf.data);
table_close(fk_rel, RowExclusiveLock); /* Prepare and save the plan */
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys * 2, queryoids,
&qkey, fk_rel, pk_rel, true);
}
return PointerGetDatum(NULL); /*
* We have a plan now. Run it to update the existing references.
*/
ri_PerformCheck(riinfo, &qkey, qplan,
fk_rel, pk_rel,
old_slot, new_slot,
true, /* must detect new rows */
SPI_OK_UPDATE);
/* if (SPI_finish() != SPI_OK_FINISH)
* Handle MATCH PARTIAL cascade update. elog(ERROR, "SPI_finish failed");
*/
case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
return PointerGetDatum(NULL);
default: table_close(fk_rel, RowExclusiveLock);
elog(ERROR, "unrecognized confmatchtype: %d",
riinfo->confmatchtype);
break;
}
/* Never reached */
return PointerGetDatum(NULL); return PointerGetDatum(NULL);
} }
...@@ -1206,116 +1095,84 @@ ri_setnull(TriggerData *trigdata) ...@@ -1206,116 +1095,84 @@ ri_setnull(TriggerData *trigdata)
pk_rel = trigdata->tg_relation; pk_rel = trigdata->tg_relation;
old_slot = trigdata->tg_trigslot; old_slot = trigdata->tg_trigslot;
switch (riinfo->confmatchtype) if (SPI_connect() != SPI_OK_CONNECT)
{ elog(ERROR, "SPI_connect failed");
/* ----------
* SQL:2008 15.17 <Execution of referential actions>
* General rules 9) a) ii):
* MATCH SIMPLE/FULL
* ... ON DELETE SET NULL
* General rules 10) a) ii):
* MATCH SIMPLE/FULL
* ... ON UPDATE SET NULL
* ----------
*/
case FKCONSTR_MATCH_SIMPLE:
case FKCONSTR_MATCH_FULL:
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed");
/*
* Fetch or prepare a saved plan for the set null operation (it's
* the same query for delete and update cases)
*/
ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETNULL_DOUPDATE);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) /*
{ * Fetch or prepare a saved plan for the set null operation (it's
StringInfoData querybuf; * the same query for delete and update cases)
StringInfoData qualbuf; */
char fkrelname[MAX_QUOTED_REL_NAME_LEN]; ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETNULL_DOUPDATE);
char attname[MAX_QUOTED_NAME_LEN];
char paramname[16];
const char *querysep;
const char *qualsep;
const char *fk_only;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* The query string built is
* UPDATE [ONLY] <fktable> SET fkatt1 = NULL [, ...]
* WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
* corresponding PK attributes.
* ----------
*/
initStringInfo(&querybuf);
initStringInfo(&qualbuf);
fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
"" : "ONLY ";
quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "UPDATE %s%s SET",
fk_only, fkrelname);
querysep = "";
qualsep = "WHERE";
for (i = 0; i < riinfo->nkeys; i++)
{
Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
quoteOneName(attname,
RIAttName(fk_rel, riinfo->fk_attnums[i]));
appendStringInfo(&querybuf,
"%s %s = NULL",
querysep, attname);
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
}
appendStringInfoString(&querybuf, qualbuf.data);
/* Prepare and save the plan */ if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, {
&qkey, fk_rel, pk_rel, true); StringInfoData querybuf;
} StringInfoData qualbuf;
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
char paramname[16];
const char *querysep;
const char *qualsep;
const char *fk_only;
Oid queryoids[RI_MAX_NUMKEYS];
/* /* ----------
* We have a plan now. Run it to update the existing references. * The query string built is
*/ * UPDATE [ONLY] <fktable> SET fkatt1 = NULL [, ...]
ri_PerformCheck(riinfo, &qkey, qplan, * WHERE $1 = fkatt1 [AND ...]
fk_rel, pk_rel, * The type id's for the $ parameters are those of the
old_slot, NULL, * corresponding PK attributes.
true, /* must detect new rows */ * ----------
SPI_OK_UPDATE); */
initStringInfo(&querybuf);
initStringInfo(&qualbuf);
fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
"" : "ONLY ";
quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "UPDATE %s%s SET",
fk_only, fkrelname);
querysep = "";
qualsep = "WHERE";
for (i = 0; i < riinfo->nkeys; i++)
{
Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
if (SPI_finish() != SPI_OK_FINISH) quoteOneName(attname,
elog(ERROR, "SPI_finish failed"); RIAttName(fk_rel, riinfo->fk_attnums[i]));
appendStringInfo(&querybuf,
"%s %s = NULL",
querysep, attname);
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
}
appendStringInfoString(&querybuf, qualbuf.data);
table_close(fk_rel, RowExclusiveLock); /* Prepare and save the plan */
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
return PointerGetDatum(NULL); /*
* We have a plan now. Run it to update the existing references.
*/
ri_PerformCheck(riinfo, &qkey, qplan,
fk_rel, pk_rel,
old_slot, NULL,
true, /* must detect new rows */
SPI_OK_UPDATE);
/* if (SPI_finish() != SPI_OK_FINISH)
* Handle MATCH PARTIAL set null delete or update. elog(ERROR, "SPI_finish failed");
*/
case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
return PointerGetDatum(NULL);
default: table_close(fk_rel, RowExclusiveLock);
elog(ERROR, "unrecognized confmatchtype: %d",
riinfo->confmatchtype);
break;
}
/* Never reached */
return PointerGetDatum(NULL); return PointerGetDatum(NULL);
} }
...@@ -1392,132 +1249,100 @@ ri_setdefault(TriggerData *trigdata) ...@@ -1392,132 +1249,100 @@ ri_setdefault(TriggerData *trigdata)
pk_rel = trigdata->tg_relation; pk_rel = trigdata->tg_relation;
old_slot = trigdata->tg_trigslot; old_slot = trigdata->tg_trigslot;
switch (riinfo->confmatchtype) if (SPI_connect() != SPI_OK_CONNECT)
{ elog(ERROR, "SPI_connect failed");
/* ----------
* SQL:2008 15.17 <Execution of referential actions>
* General rules 9) a) iii):
* MATCH SIMPLE/FULL
* ... ON DELETE SET DEFAULT
* General rules 10) a) iii):
* MATCH SIMPLE/FULL
* ... ON UPDATE SET DEFAULT
* ----------
*/
case FKCONSTR_MATCH_SIMPLE:
case FKCONSTR_MATCH_FULL:
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed");
/*
* Fetch or prepare a saved plan for the set default operation
* (it's the same query for delete and update cases)
*/
ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETDEFAULT_DOUPDATE);
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) /*
{ * Fetch or prepare a saved plan for the set default operation
StringInfoData querybuf; * (it's the same query for delete and update cases)
StringInfoData qualbuf; */
char fkrelname[MAX_QUOTED_REL_NAME_LEN]; ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETDEFAULT_DOUPDATE);
char attname[MAX_QUOTED_NAME_LEN];
char paramname[16];
const char *querysep;
const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS];
const char *fk_only;
int i;
/* ----------
* The query string built is
* UPDATE [ONLY] <fktable> SET fkatt1 = DEFAULT [, ...]
* WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
* corresponding PK attributes.
* ----------
*/
initStringInfo(&querybuf);
initStringInfo(&qualbuf);
quoteRelationName(fkrelname, fk_rel);
fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
"" : "ONLY ";
appendStringInfo(&querybuf, "UPDATE %s%s SET",
fk_only, fkrelname);
querysep = "";
qualsep = "WHERE";
for (i = 0; i < riinfo->nkeys; i++)
{
Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
quoteOneName(attname,
RIAttName(fk_rel, riinfo->fk_attnums[i]));
appendStringInfo(&querybuf,
"%s %s = DEFAULT",
querysep, attname);
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
}
appendStringInfoString(&querybuf, qualbuf.data);
/* Prepare and save the plan */ if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, {
&qkey, fk_rel, pk_rel, true); StringInfoData querybuf;
} StringInfoData qualbuf;
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
char paramname[16];
const char *querysep;
const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS];
const char *fk_only;
int i;
/* /* ----------
* We have a plan now. Run it to update the existing references. * The query string built is
*/ * UPDATE [ONLY] <fktable> SET fkatt1 = DEFAULT [, ...]
ri_PerformCheck(riinfo, &qkey, qplan, * WHERE $1 = fkatt1 [AND ...]
fk_rel, pk_rel, * The type id's for the $ parameters are those of the
old_slot, NULL, * corresponding PK attributes.
true, /* must detect new rows */ * ----------
SPI_OK_UPDATE); */
initStringInfo(&querybuf);
initStringInfo(&qualbuf);
quoteRelationName(fkrelname, fk_rel);
fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
"" : "ONLY ";
appendStringInfo(&querybuf, "UPDATE %s%s SET",
fk_only, fkrelname);
querysep = "";
qualsep = "WHERE";
for (i = 0; i < riinfo->nkeys; i++)
{
Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
if (SPI_finish() != SPI_OK_FINISH) quoteOneName(attname,
elog(ERROR, "SPI_finish failed"); RIAttName(fk_rel, riinfo->fk_attnums[i]));
appendStringInfo(&querybuf,
"%s %s = DEFAULT",
querysep, attname);
sprintf(paramname, "$%d", i + 1);
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
attname, fk_type);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
}
appendStringInfoString(&querybuf, qualbuf.data);
table_close(fk_rel, RowExclusiveLock); /* Prepare and save the plan */
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
/* /*
* If we just deleted or updated the PK row whose key was equal to * We have a plan now. Run it to update the existing references.
* the FK columns' default values, and a referencing row exists in */
* the FK table, we would have updated that row to the same values ri_PerformCheck(riinfo, &qkey, qplan,
* it already had --- and RI_FKey_fk_upd_check_required would fk_rel, pk_rel,
* hence believe no check is necessary. So we need to do another old_slot, NULL,
* lookup now and in case a reference still exists, abort the true, /* must detect new rows */
* operation. That is already implemented in the NO ACTION SPI_OK_UPDATE);
* trigger, so just run it. (This recheck is only needed in the
* SET DEFAULT case, since CASCADE would remove such rows in case
* of a DELETE operation or would change the FK key values in case
* of an UPDATE, while SET NULL is certain to result in rows that
* satisfy the FK constraint.)
*/
return ri_restrict(trigdata, true);
/* if (SPI_finish() != SPI_OK_FINISH)
* Handle MATCH PARTIAL set default delete or update. elog(ERROR, "SPI_finish failed");
*/
case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
return PointerGetDatum(NULL);
default: table_close(fk_rel, RowExclusiveLock);
elog(ERROR, "unrecognized confmatchtype: %d",
riinfo->confmatchtype);
break;
}
/* Never reached */ /*
return PointerGetDatum(NULL); * If we just deleted or updated the PK row whose key was equal to
* the FK columns' default values, and a referencing row exists in
* the FK table, we would have updated that row to the same values
* it already had --- and RI_FKey_fk_upd_check_required would
* hence believe no check is necessary. So we need to do another
* lookup now and in case a reference still exists, abort the
* operation. That is already implemented in the NO ACTION
* trigger, so just run it. (This recheck is only needed in the
* SET DEFAULT case, since CASCADE would remove such rows in case
* of a DELETE operation or would change the FK key values in case
* of an UPDATE, while SET NULL is certain to result in rows that
* satisfy the FK constraint.)
*/
return ri_restrict(trigdata, true);
} }
...@@ -1544,40 +1369,19 @@ RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel, ...@@ -1544,40 +1369,19 @@ RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel,
*/ */
riinfo = ri_FetchConstraintInfo(trigger, pk_rel, true); riinfo = ri_FetchConstraintInfo(trigger, pk_rel, true);
switch (riinfo->confmatchtype) /*
{ * If any old key value is NULL, the row could not have been
case FKCONSTR_MATCH_SIMPLE: * referenced by an FK row, so no check is needed.
case FKCONSTR_MATCH_FULL: */
if (ri_NullCheck(RelationGetDescr(pk_rel), old_slot, riinfo, true) != RI_KEYS_NONE_NULL)
/* return false;
* 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(RelationGetDescr(pk_rel), old_slot, riinfo, true) != RI_KEYS_NONE_NULL)
return false;
/* If all old and new key values are equal, no check is needed */
if (new_slot && ri_KeysEqual(pk_rel, old_slot, new_slot, riinfo, true))
return false;
/* Else we need to fire the trigger. */
return true;
/* Handle MATCH PARTIAL check. */
case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
break;
default: /* If all old and new key values are equal, no check is needed */
elog(ERROR, "unrecognized confmatchtype: %d", if (new_slot && ri_KeysEqual(pk_rel, old_slot, new_slot, riinfo, true))
riinfo->confmatchtype); return false;
break;
}
/* Never reached */ /* Else we need to fire the trigger. */
return false; return true;
} }
/* ---------- /* ----------
...@@ -1595,6 +1399,7 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel, ...@@ -1595,6 +1399,7 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
TupleTableSlot *old_slot, TupleTableSlot *new_slot) TupleTableSlot *old_slot, TupleTableSlot *new_slot)
{ {
const RI_ConstraintInfo *riinfo; const RI_ConstraintInfo *riinfo;
int ri_nullcheck;
Datum xminDatum; Datum xminDatum;
TransactionId xmin; TransactionId xmin;
bool isnull; bool isnull;
...@@ -1604,94 +1409,71 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel, ...@@ -1604,94 +1409,71 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
*/ */
riinfo = ri_FetchConstraintInfo(trigger, fk_rel, false); riinfo = ri_FetchConstraintInfo(trigger, fk_rel, false);
switch (riinfo->confmatchtype) ri_nullcheck = ri_NullCheck(RelationGetDescr(fk_rel), new_slot, riinfo, false);
{
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(RelationGetDescr(fk_rel), new_slot, riinfo, false) != RI_KEYS_NONE_NULL)
return false;
/*
* If the original row was inserted by our own transaction, we
* must fire the trigger whether or not the keys are equal. This
* is because our UPDATE will invalidate the INSERT so that the
* INSERT RI trigger will not do anything; so we had better do the
* UPDATE check. (We could skip this if we knew the INSERT
* trigger already fired, but there is no easy way to know that.)
*/
xminDatum = slot_getsysattr(old_slot, MinTransactionIdAttributeNumber, &isnull);
Assert(!isnull);
xmin = DatumGetTransactionId(xminDatum);
if (TransactionIdIsCurrentTransactionId(xmin))
return true;
/* If all old and new key values are equal, no check is needed */ /*
if (ri_KeysEqual(fk_rel, old_slot, new_slot, riinfo, false)) * If all new key values are NULL, the row satisfies the constraint, so no
* check is needed.
*/
if (ri_nullcheck == RI_KEYS_ALL_NULL)
return false;
/*
* If some new key values are NULL, the behavior depends on the match type.
*/
else if (ri_nullcheck == RI_KEYS_SOME_NULL)
{
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.
*/
return false; return false;
/* Else we need to fire the trigger. */ case FKCONSTR_MATCH_PARTIAL:
return true; /*
* Don't know, must run full check.
case FKCONSTR_MATCH_FULL: */
break;
/*
* If all new key values are NULL, the row must satisfy the
* constraint, so no check is needed. On the other hand, if only
* some of them are NULL, the row must fail the constraint. We
* must not throw error here, because the row might get
* invalidated before the constraint is to be checked, but we
* should queue the event to apply the check later.
*/
switch (ri_NullCheck(RelationGetDescr(fk_rel), new_slot, riinfo, false))
{
case RI_KEYS_ALL_NULL:
return false;
case RI_KEYS_SOME_NULL:
return true;
case RI_KEYS_NONE_NULL:
break; /* continue with the check */
}
/* case FKCONSTR_MATCH_FULL:
* If the original row was inserted by our own transaction, we /*
* must fire the trigger whether or not the keys are equal. This * If some new key values are NULL, the row fails the
* is because our UPDATE will invalidate the INSERT so that the * constraint. We must not throw error here, because the row
* INSERT RI trigger will not do anything; so we had better do the * might get invalidated before the constraint is to be
* UPDATE check. (We could skip this if we knew the INSERT * checked, but we should queue the event to apply the check
* trigger already fired, but there is no easy way to know that.) * later.
*/ */
xminDatum = slot_getsysattr(old_slot, MinTransactionIdAttributeNumber, &isnull);
Assert(!isnull);
xmin = DatumGetTransactionId(xminDatum);
if (TransactionIdIsCurrentTransactionId(xmin))
return true; return true;
}
}
/* If all old and new key values are equal, no check is needed */ /*
if (ri_KeysEqual(fk_rel, old_slot, new_slot, riinfo, false)) * Continues here for no new key values are NULL, or we couldn't decide
return false; * yet.
*/
/* Else we need to fire the trigger. */
return true;
/* Handle MATCH PARTIAL check. */ /*
case FKCONSTR_MATCH_PARTIAL: * If the original row was inserted by our own transaction, we
ereport(ERROR, * must fire the trigger whether or not the keys are equal. This
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), * is because our UPDATE will invalidate the INSERT so that the
errmsg("MATCH PARTIAL not yet implemented"))); * INSERT RI trigger will not do anything; so we had better do the
break; * UPDATE check. (We could skip this if we knew the INSERT
* trigger already fired, but there is no easy way to know that.)
*/
xminDatum = slot_getsysattr(old_slot, MinTransactionIdAttributeNumber, &isnull);
Assert(!isnull);
xmin = DatumGetTransactionId(xminDatum);
if (TransactionIdIsCurrentTransactionId(xmin))
return true;
default: /* If all old and new key values are equal, no check is needed */
elog(ERROR, "unrecognized confmatchtype: %d", if (ri_KeysEqual(fk_rel, old_slot, new_slot, riinfo, false))
riinfo->confmatchtype); return false;
break;
}
/* Never reached */ /* Else we need to fire the trigger. */
return false; return true;
} }
/* ---------- /* ----------
...@@ -1860,15 +1642,6 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) ...@@ -1860,15 +1642,6 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
case FKCONSTR_MATCH_FULL: case FKCONSTR_MATCH_FULL:
sep = " OR "; sep = " OR ";
break; break;
case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
break;
default:
elog(ERROR, "unrecognized confmatchtype: %d",
riinfo->confmatchtype);
break;
} }
} }
appendStringInfoChar(&querybuf, ')'); appendStringInfoChar(&querybuf, ')');
...@@ -2199,6 +1972,17 @@ ri_FetchConstraintInfo(Trigger *trigger, Relation trig_rel, bool rel_is_pk) ...@@ -2199,6 +1972,17 @@ ri_FetchConstraintInfo(Trigger *trigger, Relation trig_rel, bool rel_is_pk)
trigger->tgname, RelationGetRelationName(trig_rel)); trigger->tgname, RelationGetRelationName(trig_rel));
} }
if (riinfo->confmatchtype != FKCONSTR_MATCH_FULL &&
riinfo->confmatchtype != FKCONSTR_MATCH_PARTIAL &&
riinfo->confmatchtype != FKCONSTR_MATCH_SIMPLE)
elog(ERROR, "unrecognized confmatchtype: %d",
riinfo->confmatchtype);
if (riinfo->confmatchtype == FKCONSTR_MATCH_PARTIAL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
return riinfo; return riinfo;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment