* Portions Copyright (c) 2000-2001, PostgreSQL Global Development Group
* Copyright 1999 Jan Wieck
*
- * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.34 2002/04/01 02:02:34 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.35 2002/04/01 22:36:10 tgl Exp $
*
* ----------
*/
#include "postgres.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_operator.h"
#include "commands/trigger.h"
#include "executor/spi_priv.h"
#define RI_PLAN_SETNULL_DEL_DOUPDATE 1
#define RI_PLAN_SETNULL_UPD_DOUPDATE 1
+#define MAX_QUOTED_NAME_LEN (NAMEDATALEN*2+3)
+#define MAX_QUOTED_REL_NAME_LEN (MAX_QUOTED_NAME_LEN*2)
+
/* ----------
* RI_QueryKey
* Local function prototypes
* ----------
*/
+static void quoteOneName(char *buffer, const char *name);
+static void quoteRelationName(char *buffer, Relation rel);
static int ri_DetermineMatchType(char *str);
static int ri_NullCheck(Relation rel, HeapTuple tup,
RI_QueryKey *key, int pairidx);
static void ri_HashPreparedPlan(RI_QueryKey *key, void *plan);
-/*
- * very ugly, very temporary hack to allow RI triggers to find tables
- * anywhere in the current search path. This is just so that the regression
- * tests don't break with new search path code; we MUST find a more bullet
- * proof solution before release.
- */
-static Relation
-kluge_openr(char *relationName, LOCKMODE lockmode)
-{
- return heap_openrv(makeRangeVar(NULL, relationName), lockmode);
-}
-
-
/* ----------
* RI_FKey_check -
*
/*
* Get the relation descriptors of the FK and PK tables and the new
* tuple.
+ *
+ * pk_rel is opened in RowShareLock mode since that's what our
+ * eventual SELECT FOR UPDATE will get on it.
*/
fk_rel = trigdata->tg_relation;
- pk_rel = kluge_openr(tgargs[RI_PK_RELNAME_ARGNO], NoLock);
+ pk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
{
old_row = trigdata->tg_trigtuple;
new_row = trigdata->tg_trigtuple;
}
- /*
- * We should not even consider checking the row if it is no longer
- * valid since it was either deleted (doesn't matter) or updated
- * (in which case it'll be checked with its final values).
- */
- if (new_row) {
- if (!HeapTupleSatisfiesItself(new_row->t_data)) {
- return PointerGetDatum(NULL);
- }
- }
+ /*
+ * We should not even consider checking the row if it is no longer
+ * valid since it was either deleted (doesn't matter) or updated
+ * (in which case it'll be checked with its final values).
+ */
+ if (new_row) {
+ if (!HeapTupleSatisfiesItself(new_row->t_data)) {
+ heap_close(pk_rel, RowShareLock);
+ return PointerGetDatum(NULL);
+ }
+ }
/* ----------
* SQL3 11.9
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char querystr[8192];
+ char querystr[MAX_QUOTED_REL_NAME_LEN + 100];
+ char pkrelname[MAX_QUOTED_REL_NAME_LEN];
/* ---------
* The query string built is
* ----------
*/
- sprintf(querystr, "SELECT 1 FROM ONLY \"%s\" FOR UPDATE OF \"%s\"",
- tgargs[RI_PK_RELNAME_ARGNO],
- tgargs[RI_PK_RELNAME_ARGNO]);
+ quoteRelationName(pkrelname, pk_rel);
+ sprintf(querystr, "SELECT 1 FROM ONLY %s x FOR UPDATE OF x",
+ pkrelname);
/*
* Prepare, save and remember the new plan.
elog(WARNING, "SPI_connect() failed in RI_FKey_check()");
SetUserId(RelationGetForm(pk_rel)->relowner);
- /* pk_rel is no longer neede OK ? */
- heap_close(pk_rel, NoLock);
if (SPI_execp(qplan, check_values, check_nulls, 1) != SPI_OK_SELECT)
elog(ERROR, "SPI_execp() failed in RI_FKey_check()");
elog(ERROR, "%s referential integrity violation - "
"no rows found in %s",
tgargs[RI_CONSTRAINT_NAME_ARGNO],
- tgargs[RI_PK_RELNAME_ARGNO]);
+ RelationGetRelationName(pk_rel));
if (SPI_finish() != SPI_OK_FINISH)
elog(WARNING, "SPI_finish() failed in RI_FKey_check()");
+ heap_close(pk_rel, RowShareLock);
+
return PointerGetDatum(NULL);
}
* This is true for MATCH FULL, MATCH PARTIAL, and MATCH
*
*/
- heap_close(pk_rel, NoLock);
+ heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
case RI_KEYS_SOME_NULL:
"MATCH FULL doesn't allow mixing of NULL "
"and NON-NULL key values",
tgargs[RI_CONSTRAINT_NAME_ARGNO]);
- heap_close(pk_rel, NoLock);
+ heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
case RI_MATCH_TYPE_UNSPECIFIED:
* MATCH - if ANY column is null, we
* have a match.
*/
- heap_close(pk_rel, NoLock);
+ heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
case RI_MATCH_TYPE_PARTIAL:
* writing a special version here)
*/
elog(ERROR, "MATCH PARTIAL not yet implemented");
- heap_close(pk_rel, NoLock);
+ heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
}
*/
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char buf[256];
- char querystr[8192];
- char *querysep;
+ char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
+ (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ char pkrelname[MAX_QUOTED_REL_NAME_LEN];
+ char attname[MAX_QUOTED_NAME_LEN];
+ const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* how to compare these two types by '='.
* ----------
*/
- sprintf(querystr, "SELECT 1 FROM ONLY \"%s\"",
- tgargs[RI_PK_RELNAME_ARGNO]);
+ quoteRelationName(pkrelname, pk_rel);
+ sprintf(querystr, "SELECT 1 FROM ONLY %s x", pkrelname);
querysep = "WHERE";
for (i = 0; i < qkey.nkeypairs; i++)
{
- sprintf(buf, " %s \"%s\" = $%d", querysep,
- tgargs[5 + i * 2], i + 1);
- strcat(querystr, buf);
+ quoteOneName(attname,
+ tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_PK_IDX]);
+ sprintf(querystr + strlen(querystr), " %s %s = $%d",
+ querysep, attname, i+1);
querysep = "AND";
queryoids[i] = SPI_gettypeid(fk_rel->rd_att,
qkey.keypair[i][RI_KEYPAIR_FK_IDX]);
}
- sprintf(buf, " FOR UPDATE OF \"%s\"",
- tgargs[RI_PK_RELNAME_ARGNO]);
- strcat(querystr, buf);
+ strcat(querystr, " FOR UPDATE OF x");
/*
* Prepare, save and remember the new plan.
*/
SetUserId(RelationGetForm(pk_rel)->relowner);
- /* pk_rel is no longer needed OK ? */
- heap_close(pk_rel, NoLock);
if (SPI_execp(qplan, check_values, check_nulls, 1) != SPI_OK_SELECT)
elog(ERROR, "SPI_execp() failed in RI_FKey_check()");
elog(ERROR, "%s referential integrity violation - "
"key referenced from %s not found in %s",
tgargs[RI_CONSTRAINT_NAME_ARGNO],
- tgargs[RI_FK_RELNAME_ARGNO],
- tgargs[RI_PK_RELNAME_ARGNO]);
+ RelationGetRelationName(fk_rel),
+ RelationGetRelationName(pk_rel));
if (SPI_finish() != SPI_OK_FINISH)
elog(WARNING, "SPI_finish() failed in RI_FKey_check()");
+ heap_close(pk_rel, RowShareLock);
+
return PointerGetDatum(NULL);
/*
/*
* Get the relation descriptors of the FK and PK tables and the old
* tuple.
+ *
+ * fk_rel is opened in RowShareLock mode since that's what our
+ * eventual SELECT FOR UPDATE will get on it.
*/
- fk_rel = kluge_openr(tgargs[RI_FK_RELNAME_ARGNO], NoLock);
+ fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
pk_rel = trigdata->tg_relation;
old_row = trigdata->tg_trigtuple;
* No check - MATCH FULL means there cannot be any
* reference to old key if it contains NULL
*/
- heap_close(fk_rel, NoLock);
+ heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
case RI_KEYS_NONE_NULL:
*/
break;
}
- heap_close(fk_rel, NoLock);
if (SPI_connect() != SPI_OK_CONNECT)
elog(WARNING, "SPI_connect() failed in RI_FKey_noaction_del()");
*/
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char buf[256];
- char querystr[8192];
- char *querysep;
+ char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
+ (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ char fkrelname[MAX_QUOTED_REL_NAME_LEN];
+ char attname[MAX_QUOTED_NAME_LEN];
+ const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* how to compare these two types by '='.
* ----------
*/
- sprintf(querystr, "SELECT 1 FROM ONLY \"%s\"",
- tgargs[RI_FK_RELNAME_ARGNO]);
+ quoteRelationName(fkrelname, fk_rel);
+ sprintf(querystr, "SELECT 1 FROM ONLY %s x", fkrelname);
querysep = "WHERE";
for (i = 0; i < qkey.nkeypairs; i++)
{
- sprintf(buf, " %s \"%s\" = $%d", querysep,
- tgargs[4 + i * 2], i + 1);
- strcat(querystr, buf);
+ quoteOneName(attname,
+ tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
+ sprintf(querystr + strlen(querystr), " %s %s = $%d",
+ querysep, attname, i+1);
querysep = "AND";
queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
}
- sprintf(buf, " FOR UPDATE OF \"%s\"",
- tgargs[RI_FK_RELNAME_ARGNO]);
- strcat(querystr, buf);
+ strcat(querystr, " FOR UPDATE OF x");
/*
* Prepare, save and remember the new plan.
elog(ERROR, "%s referential integrity violation - "
"key in %s still referenced from %s",
tgargs[RI_CONSTRAINT_NAME_ARGNO],
- tgargs[RI_PK_RELNAME_ARGNO],
- tgargs[RI_FK_RELNAME_ARGNO]);
+ RelationGetRelationName(pk_rel),
+ RelationGetRelationName(fk_rel));
if (SPI_finish() != SPI_OK_FINISH)
elog(WARNING, "SPI_finish() failed in RI_FKey_noaction_del()");
+ heap_close(fk_rel, RowShareLock);
+
return PointerGetDatum(NULL);
/*
/*
* Get the relation descriptors of the FK and PK tables and the new
* and old tuple.
+ *
+ * fk_rel is opened in RowShareLock mode since that's what our
+ * eventual SELECT FOR UPDATE will get on it.
*/
- fk_rel = kluge_openr(tgargs[RI_FK_RELNAME_ARGNO], NoLock);
+ fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
* No check - MATCH FULL means there cannot be any
* reference to old key if it contains NULL
*/
- heap_close(fk_rel, NoLock);
+ heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
case RI_KEYS_NONE_NULL:
*/
break;
}
- heap_close(fk_rel, NoLock);
/*
* No need to check anything if old and new keys are equal
*/
if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
RI_KEYPAIR_PK_IDX))
+ {
+ heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
+ }
if (SPI_connect() != SPI_OK_CONNECT)
elog(WARNING, "SPI_connect() failed in RI_FKey_noaction_upd()");
*/
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char buf[256];
- char querystr[8192];
- char *querysep;
+ char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
+ (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ char fkrelname[MAX_QUOTED_REL_NAME_LEN];
+ char attname[MAX_QUOTED_NAME_LEN];
+ const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* how to compare these two types by '='.
* ----------
*/
- sprintf(querystr, "SELECT 1 FROM ONLY \"%s\"",
- tgargs[RI_FK_RELNAME_ARGNO]);
+ quoteRelationName(fkrelname, fk_rel);
+ sprintf(querystr, "SELECT 1 FROM ONLY %s x", fkrelname);
querysep = "WHERE";
for (i = 0; i < qkey.nkeypairs; i++)
{
- sprintf(buf, " %s \"%s\" = $%d", querysep,
- tgargs[4 + i * 2], i + 1);
- strcat(querystr, buf);
+ quoteOneName(attname,
+ tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
+ sprintf(querystr + strlen(querystr), " %s %s = $%d",
+ querysep, attname, i+1);
querysep = "AND";
queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
}
- sprintf(buf, " FOR UPDATE OF \"%s\"",
- tgargs[RI_FK_RELNAME_ARGNO]);
- strcat(querystr, buf);
+ strcat(querystr, " FOR UPDATE OF x");
/*
* Prepare, save and remember the new plan.
elog(ERROR, "%s referential integrity violation - "
"key in %s still referenced from %s",
tgargs[RI_CONSTRAINT_NAME_ARGNO],
- tgargs[RI_PK_RELNAME_ARGNO],
- tgargs[RI_FK_RELNAME_ARGNO]);
+ RelationGetRelationName(pk_rel),
+ RelationGetRelationName(fk_rel));
if (SPI_finish() != SPI_OK_FINISH)
elog(WARNING, "SPI_finish() failed in RI_FKey_noaction_upd()");
+ heap_close(fk_rel, RowShareLock);
+
return PointerGetDatum(NULL);
/*
/*
* Get the relation descriptors of the FK and PK tables and the old
* tuple.
+ *
+ * fk_rel is opened in RowExclusiveLock mode since that's what our
+ * eventual DELETE will get on it.
*/
- fk_rel = kluge_openr(tgargs[RI_FK_RELNAME_ARGNO], NoLock);
- fk_owner = RelationGetForm(fk_rel)->relowner;
+ 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]))
{
* No check - MATCH FULL means there cannot be any
* reference to old key if it contains NULL
*/
- heap_close(fk_rel, NoLock);
+ heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
case RI_KEYS_NONE_NULL:
*/
break;
}
- heap_close(fk_rel, NoLock);
if (SPI_connect() != SPI_OK_CONNECT)
elog(WARNING, "SPI_connect() failed in RI_FKey_cascade_del()");
*/
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char buf[256];
- char querystr[8192];
- char *querysep;
+ char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
+ (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ char fkrelname[MAX_QUOTED_REL_NAME_LEN];
+ char attname[MAX_QUOTED_NAME_LEN];
+ const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* how to compare these two types by '='.
* ----------
*/
- sprintf(querystr, "DELETE FROM ONLY \"%s\"",
- tgargs[RI_FK_RELNAME_ARGNO]);
+ quoteRelationName(fkrelname, fk_rel);
+ sprintf(querystr, "DELETE FROM ONLY %s", fkrelname);
querysep = "WHERE";
for (i = 0; i < qkey.nkeypairs; i++)
{
- sprintf(buf, " %s \"%s\" = $%d", querysep,
- tgargs[4 + i * 2], i + 1);
- strcat(querystr, buf);
+ quoteOneName(attname,
+ tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
+ sprintf(querystr + strlen(querystr), " %s %s = $%d",
+ querysep, attname, i+1);
querysep = "AND";
queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
if (SPI_finish() != SPI_OK_FINISH)
elog(WARNING, "SPI_finish() failed in RI_FKey_cascade_del()");
+ heap_close(fk_rel, RowExclusiveLock);
+
return PointerGetDatum(NULL);
/*
/*
* Get the relation descriptors of the FK and PK tables and the new
* and old tuple.
+ *
+ * fk_rel is opened in RowExclusiveLock mode since that's what our
+ * eventual UPDATE will get on it.
*/
- fk_rel = kluge_openr(tgargs[RI_FK_RELNAME_ARGNO], NoLock);
- fk_owner = RelationGetForm(fk_rel)->relowner;
+ fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
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]))
{
* No update - MATCH FULL means there cannot be any
* reference to old key if it contains NULL
*/
- heap_close(fk_rel, NoLock);
+ heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
case RI_KEYS_NONE_NULL:
*/
break;
}
- heap_close(fk_rel, NoLock);
/*
* No need to do anything if old and new keys are equal
*/
if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
RI_KEYPAIR_PK_IDX))
+ {
+ heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
+ }
if (SPI_connect() != SPI_OK_CONNECT)
elog(WARNING, "SPI_connect() failed in RI_FKey_cascade_upd()");
*/
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char buf[256];
- char querystr[8192];
- char qualstr[8192];
- char *querysep;
- char *qualsep;
+ char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
+ (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
+ char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ char fkrelname[MAX_QUOTED_REL_NAME_LEN];
+ char attname[MAX_QUOTED_NAME_LEN];
+ const char *querysep;
+ const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS * 2];
/* ----------
* how to compare these two types by '='.
* ----------
*/
- sprintf(querystr, "UPDATE ONLY \"%s\" SET",
- tgargs[RI_FK_RELNAME_ARGNO]);
+ quoteRelationName(fkrelname, fk_rel);
+ sprintf(querystr, "UPDATE ONLY %s SET", fkrelname);
qualstr[0] = '\0';
querysep = "";
qualsep = "WHERE";
for (i = 0, j = qkey.nkeypairs; i < qkey.nkeypairs; i++, j++)
{
- sprintf(buf, "%s \"%s\" = $%d", querysep,
- tgargs[4 + i * 2], i + 1);
- strcat(querystr, buf);
- sprintf(buf, " %s \"%s\" = $%d", qualsep,
- tgargs[4 + i * 2], j + 1);
- strcat(qualstr, buf);
+ quoteOneName(attname,
+ tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
+ sprintf(querystr + strlen(querystr), "%s %s = $%d",
+ querysep, attname, i+1);
+ sprintf(qualstr + strlen(qualstr), " %s %s = $%d",
+ qualsep, attname, j+1);
querysep = ",";
qualsep = "AND";
queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
if (SPI_finish() != SPI_OK_FINISH)
elog(WARNING, "SPI_finish() failed in RI_FKey_cascade_upd()");
+ heap_close(fk_rel, RowExclusiveLock);
+
return PointerGetDatum(NULL);
/*
/*
* Get the relation descriptors of the FK and PK tables and the old
* tuple.
+ *
+ * fk_rel is opened in RowShareLock mode since that's what our
+ * eventual SELECT FOR UPDATE will get on it.
*/
- fk_rel = kluge_openr(tgargs[RI_FK_RELNAME_ARGNO], NoLock);
- fk_owner = RelationGetForm(fk_rel)->relowner;
+ 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]))
{
* No check - MATCH FULL means there cannot be any
* reference to old key if it contains NULL
*/
- heap_close(fk_rel, NoLock);
+ heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
case RI_KEYS_NONE_NULL:
*/
break;
}
- heap_close(fk_rel, NoLock);
if (SPI_connect() != SPI_OK_CONNECT)
elog(WARNING, "SPI_connect() failed in RI_FKey_restrict_del()");
*/
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char buf[256];
- char querystr[8192];
- char *querysep;
+ char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
+ (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ char fkrelname[MAX_QUOTED_REL_NAME_LEN];
+ char attname[MAX_QUOTED_NAME_LEN];
+ const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* how to compare these two types by '='.
* ----------
*/
- sprintf(querystr, "SELECT 1 FROM ONLY \"%s\"",
- tgargs[RI_FK_RELNAME_ARGNO]);
+ quoteRelationName(fkrelname, fk_rel);
+ sprintf(querystr, "SELECT 1 FROM ONLY %s x", fkrelname);
querysep = "WHERE";
for (i = 0; i < qkey.nkeypairs; i++)
{
- sprintf(buf, " %s \"%s\" = $%d", querysep,
- tgargs[4 + i * 2], i + 1);
- strcat(querystr, buf);
+ quoteOneName(attname,
+ tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
+ sprintf(querystr + strlen(querystr), " %s %s = $%d",
+ querysep, attname, i+1);
querysep = "AND";
queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
}
- sprintf(buf, " FOR UPDATE OF \"%s\"",
- tgargs[RI_FK_RELNAME_ARGNO]);
- strcat(querystr, buf);
+ strcat(querystr, " FOR UPDATE OF x");
/*
* Prepare, save and remember the new plan.
elog(ERROR, "%s referential integrity violation - "
"key in %s still referenced from %s",
tgargs[RI_CONSTRAINT_NAME_ARGNO],
- tgargs[RI_PK_RELNAME_ARGNO],
- tgargs[RI_FK_RELNAME_ARGNO]);
+ RelationGetRelationName(pk_rel),
+ RelationGetRelationName(fk_rel));
if (SPI_finish() != SPI_OK_FINISH)
elog(WARNING, "SPI_finish() failed in RI_FKey_restrict_del()");
+ heap_close(fk_rel, RowShareLock);
+
return PointerGetDatum(NULL);
/*
/*
* Get the relation descriptors of the FK and PK tables and the new
* and old tuple.
+ *
+ * fk_rel is opened in RowShareLock mode since that's what our
+ * eventual SELECT FOR UPDATE will get on it.
*/
- fk_rel = kluge_openr(tgargs[RI_FK_RELNAME_ARGNO], NoLock);
- fk_owner = RelationGetForm(fk_rel)->relowner;
+ fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
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]))
{
* No check - MATCH FULL means there cannot be any
* reference to old key if it contains NULL
*/
- heap_close(fk_rel, NoLock);
+ heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
case RI_KEYS_NONE_NULL:
*/
break;
}
- heap_close(fk_rel, NoLock);
/*
* No need to check anything if old and new keys are equal
*/
if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
RI_KEYPAIR_PK_IDX))
+ {
+ heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
+ }
if (SPI_connect() != SPI_OK_CONNECT)
elog(WARNING, "SPI_connect() failed in RI_FKey_restrict_upd()");
*/
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char buf[256];
- char querystr[8192];
- char *querysep;
+ char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
+ (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ char fkrelname[MAX_QUOTED_REL_NAME_LEN];
+ char attname[MAX_QUOTED_NAME_LEN];
+ const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* how to compare these two types by '='.
* ----------
*/
- sprintf(querystr, "SELECT 1 FROM ONLY \"%s\"",
- tgargs[RI_FK_RELNAME_ARGNO]);
+ quoteRelationName(fkrelname, fk_rel);
+ sprintf(querystr, "SELECT 1 FROM ONLY %s x", fkrelname);
querysep = "WHERE";
for (i = 0; i < qkey.nkeypairs; i++)
{
- sprintf(buf, " %s \"%s\" = $%d", querysep,
- tgargs[4 + i * 2], i + 1);
- strcat(querystr, buf);
+ quoteOneName(attname,
+ tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
+ sprintf(querystr + strlen(querystr), " %s %s = $%d",
+ querysep, attname, i+1);
querysep = "AND";
queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
}
- sprintf(buf, " FOR UPDATE OF \"%s\"",
- tgargs[RI_FK_RELNAME_ARGNO]);
- strcat(querystr, buf);
+ strcat(querystr, " FOR UPDATE OF x");
/*
* Prepare, save and remember the new plan.
elog(ERROR, "%s referential integrity violation - "
"key in %s still referenced from %s",
tgargs[RI_CONSTRAINT_NAME_ARGNO],
- tgargs[RI_PK_RELNAME_ARGNO],
- tgargs[RI_FK_RELNAME_ARGNO]);
+ RelationGetRelationName(pk_rel),
+ RelationGetRelationName(fk_rel));
if (SPI_finish() != SPI_OK_FINISH)
elog(WARNING, "SPI_finish() failed in RI_FKey_restrict_upd()");
+ heap_close(fk_rel, RowShareLock);
+
return PointerGetDatum(NULL);
/*
/*
* Get the relation descriptors of the FK and PK tables and the old
* tuple.
+ *
+ * fk_rel is opened in RowExclusiveLock mode since that's what our
+ * eventual UPDATE will get on it.
*/
- fk_rel = kluge_openr(tgargs[RI_FK_RELNAME_ARGNO], NoLock);
- fk_owner = RelationGetForm(fk_rel)->relowner;
+ 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]))
{
* No update - MATCH FULL means there cannot be any
* reference to old key if it contains NULL
*/
- heap_close(fk_rel, NoLock);
+ heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
case RI_KEYS_NONE_NULL:
*/
break;
}
- heap_close(fk_rel, NoLock);
if (SPI_connect() != SPI_OK_CONNECT)
elog(WARNING, "SPI_connect() failed in RI_FKey_setnull_del()");
*/
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char buf[256];
- char querystr[8192];
- char qualstr[8192];
- char *querysep;
- char *qualsep;
+ char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
+ (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
+ char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ char fkrelname[MAX_QUOTED_REL_NAME_LEN];
+ char attname[MAX_QUOTED_NAME_LEN];
+ const char *querysep;
+ const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* how to compare these two types by '='.
* ----------
*/
- sprintf(querystr, "UPDATE ONLY \"%s\" SET",
- tgargs[RI_FK_RELNAME_ARGNO]);
+ quoteRelationName(fkrelname, fk_rel);
+ sprintf(querystr, "UPDATE ONLY %s SET", fkrelname);
qualstr[0] = '\0';
querysep = "";
qualsep = "WHERE";
for (i = 0; i < qkey.nkeypairs; i++)
{
- sprintf(buf, "%s \"%s\" = NULL", querysep,
- tgargs[4 + i * 2]);
- strcat(querystr, buf);
- sprintf(buf, " %s \"%s\" = $%d", qualsep,
- tgargs[4 + i * 2], i + 1);
- strcat(qualstr, buf);
+ quoteOneName(attname,
+ tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
+ sprintf(querystr + strlen(querystr), "%s %s = NULL",
+ querysep, attname);
+ sprintf(qualstr + strlen(qualstr), " %s %s = $%d",
+ qualsep, attname, i+1);
querysep = ",";
qualsep = "AND";
queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
if (SPI_finish() != SPI_OK_FINISH)
elog(WARNING, "SPI_finish() failed in RI_FKey_setnull_del()");
+ heap_close(fk_rel, RowExclusiveLock);
+
return PointerGetDatum(NULL);
/*
/*
* Get the relation descriptors of the FK and PK tables and the old
* tuple.
+ *
+ * fk_rel is opened in RowExclusiveLock mode since that's what our
+ * eventual UPDATE will get on it.
*/
- fk_rel = kluge_openr(tgargs[RI_FK_RELNAME_ARGNO], NoLock);
- fk_owner = RelationGetForm(fk_rel)->relowner;
+ fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
+ fk_owner = RelationGetForm(fk_rel)->relowner;
switch (match_type)
{
* No update - MATCH FULL means there cannot be any
* reference to old key if it contains NULL
*/
- heap_close(fk_rel, NoLock);
+ heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
case RI_KEYS_NONE_NULL:
*/
break;
}
- heap_close(fk_rel, NoLock);
-
/*
* No need to do anything if old and new keys are equal
*/
if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
RI_KEYPAIR_PK_IDX))
+ {
+ heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
+ }
if (SPI_connect() != SPI_OK_CONNECT)
elog(WARNING, "SPI_connect() failed in RI_FKey_setnull_upd()");
if (!use_cached_query ||
(qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char buf[256];
- char querystr[8192];
- char qualstr[8192];
- char *querysep;
- char *qualsep;
+ char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
+ (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
+ char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ char fkrelname[MAX_QUOTED_REL_NAME_LEN];
+ char attname[MAX_QUOTED_NAME_LEN];
+ const char *querysep;
+ const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* how to compare these two types by '='.
* ----------
*/
- sprintf(querystr, "UPDATE ONLY \"%s\" SET",
- tgargs[RI_FK_RELNAME_ARGNO]);
+ quoteRelationName(fkrelname, fk_rel);
+ sprintf(querystr, "UPDATE ONLY %s SET", fkrelname);
qualstr[0] = '\0';
querysep = "";
qualsep = "WHERE";
for (i = 0; i < qkey.nkeypairs; i++)
{
+ quoteOneName(attname,
+ tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
/*
* MATCH - only change columns
* corresponding to changed columns in pk_rel's key
!ri_OneKeyEqual(pk_rel, i, old_row, new_row, &qkey,
RI_KEYPAIR_PK_IDX))
{
- sprintf(buf, "%s \"%s\" = NULL", querysep,
- tgargs[4 + i * 2]);
- strcat(querystr, buf);
+ sprintf(querystr + strlen(querystr), "%s %s = NULL",
+ querysep, attname);
querysep = ",";
}
- sprintf(buf, " %s \"%s\" = $%d", qualsep,
- tgargs[4 + i * 2], i + 1);
- strcat(qualstr, buf);
+ sprintf(qualstr + strlen(qualstr), " %s %s = $%d",
+ qualsep, attname, i+1);
qualsep = "AND";
queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
if (SPI_finish() != SPI_OK_FINISH)
elog(WARNING, "SPI_finish() failed in RI_FKey_setnull_upd()");
+ heap_close(fk_rel, RowExclusiveLock);
+
return PointerGetDatum(NULL);
/*
/*
* Get the relation descriptors of the FK and PK tables and the old
* tuple.
+ *
+ * fk_rel is opened in RowExclusiveLock mode since that's what our
+ * eventual UPDATE will get on it.
*/
- fk_rel = kluge_openr(tgargs[RI_FK_RELNAME_ARGNO], NoLock);
- fk_owner = RelationGetForm(fk_rel)->relowner;
+ 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]))
{
* No update - MATCH FULL means there cannot be any
* reference to old key if it contains NULL
*/
- heap_close(fk_rel, NoLock);
+ heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
case RI_KEYS_NONE_NULL:
elog(WARNING, "SPI_connect() failed in RI_FKey_setdefault_del()");
/*
- * Prepare a plan for the set defalt delete operation.
+ * Prepare a plan for the set default delete operation.
* Unfortunately we need to do it on every invocation because
* the default value could potentially change between calls.
*/
{
- char buf[256];
- char querystr[8192];
- char qualstr[8192];
- char *querysep;
- char *qualsep;
+ char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
+ (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
+ char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ char fkrelname[MAX_QUOTED_REL_NAME_LEN];
+ char attname[MAX_QUOTED_NAME_LEN];
+ const char *querysep;
+ const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS];
Plan *spi_plan;
AttrDefault *defval;
* how to compare these two types by '='.
* ----------
*/
- sprintf(querystr, "UPDATE ONLY \"%s\" SET",
- tgargs[RI_FK_RELNAME_ARGNO]);
+ quoteRelationName(fkrelname, fk_rel);
+ sprintf(querystr, "UPDATE ONLY %s SET", fkrelname);
qualstr[0] = '\0';
querysep = "";
qualsep = "WHERE";
for (i = 0; i < qkey.nkeypairs; i++)
{
- sprintf(buf, "%s \"%s\" = NULL", querysep,
- tgargs[4 + i * 2]);
- strcat(querystr, buf);
- sprintf(buf, " %s \"%s\" = $%d", qualsep,
- tgargs[4 + i * 2], i + 1);
- strcat(qualstr, buf);
+ quoteOneName(attname,
+ tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
+ sprintf(querystr + strlen(querystr), "%s %s = NULL",
+ querysep, attname);
+ sprintf(qualstr + strlen(qualstr), " %s %s = $%d",
+ qualsep, attname, i+1);
querysep = ",";
qualsep = "AND";
queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
}
}
}
- /* fk_rel is no longer needed OK ? */
- heap_close(fk_rel, NoLock);
/*
* We have a plan now. Build up the arguments for SPI_execp()
if (SPI_finish() != SPI_OK_FINISH)
elog(WARNING, "SPI_finish() failed in RI_FKey_setdefault_del()");
+ heap_close(fk_rel, RowExclusiveLock);
+
return PointerGetDatum(NULL);
/*
/*
* Get the relation descriptors of the FK and PK tables and the old
* tuple.
+ *
+ * fk_rel is opened in RowExclusiveLock mode since that's what our
+ * eventual UPDATE will get on it.
*/
- fk_rel = kluge_openr(tgargs[RI_FK_RELNAME_ARGNO], NoLock);
- fk_owner = RelationGetForm(fk_rel)->relowner;
+ fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
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]);
* No update - MATCH FULL means there cannot be any
* reference to old key if it contains NULL
*/
- heap_close(fk_rel, NoLock);
+ heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
case RI_KEYS_NONE_NULL:
*/
if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
RI_KEYPAIR_PK_IDX))
+ {
+ heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
+ }
if (SPI_connect() != SPI_OK_CONNECT)
elog(WARNING, "SPI_connect() failed in RI_FKey_setdefault_upd()");
/*
- * Prepare a plan for the set defalt delete operation.
+ * Prepare a plan for the set default delete operation.
* Unfortunately we need to do it on every invocation because
* the default value could potentially change between calls.
*/
{
- char buf[256];
- char querystr[8192];
- char qualstr[8192];
- char *querysep;
- char *qualsep;
+ char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
+ (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
+ char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ char fkrelname[MAX_QUOTED_REL_NAME_LEN];
+ char attname[MAX_QUOTED_NAME_LEN];
+ const char *querysep;
+ const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS];
Plan *spi_plan;
AttrDefault *defval;
* how to compare these two types by '='.
* ----------
*/
- sprintf(querystr, "UPDATE ONLY \"%s\" SET",
- tgargs[RI_FK_RELNAME_ARGNO]);
+ quoteRelationName(fkrelname, fk_rel);
+ sprintf(querystr, "UPDATE ONLY %s SET", fkrelname);
qualstr[0] = '\0';
querysep = "";
qualsep = "WHERE";
for (i = 0; i < qkey.nkeypairs; i++)
{
+ quoteOneName(attname,
+ tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
/*
* MATCH - only change columns
* corresponding to changed columns in pk_rel's key
!ri_OneKeyEqual(pk_rel, i, old_row,
new_row, &qkey, RI_KEYPAIR_PK_IDX))
{
- sprintf(buf, "%s \"%s\" = NULL", querysep,
- tgargs[4 + i * 2]);
- strcat(querystr, buf);
+ sprintf(querystr + strlen(querystr), "%s %s = NULL",
+ querysep, attname);
querysep = ",";
}
- sprintf(buf, " %s \"%s\" = $%d", qualsep,
- tgargs[4 + i * 2], i + 1);
- strcat(qualstr, buf);
+ sprintf(qualstr + strlen(qualstr), " %s %s = $%d",
+ qualsep, attname, i+1);
qualsep = "AND";
queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
}
}
}
- /* fk_rel is no longer needed OK ? */
- heap_close(fk_rel, NoLock);
/*
* We have a plan now. Build up the arguments for SPI_execp()
if (SPI_finish() != SPI_OK_FINISH)
elog(WARNING, "SPI_finish() failed in RI_FKey_setdefault_upd()");
+ heap_close(fk_rel, RowExclusiveLock);
+
return PointerGetDatum(NULL);
/*
/*
* Get the relation descriptors of the FK and PK tables and the new
* and old tuple.
+ *
+ * Use minimal locking for fk_rel here.
*/
- fk_rel = kluge_openr(tgargs[RI_FK_RELNAME_ARGNO], NoLock);
+ fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, AccessShareLock);
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
0,
fk_rel, pk_rel,
tgnargs, tgargs);
- heap_close(fk_rel, NoLock);
+
+ heap_close(fk_rel, AccessShareLock);
/*
* Return if key's are equal
*/
+/*
+ * quoteOneName --- safely quote a single SQL name
+ *
+ * buffer must be MAX_QUOTED_NAME_LEN long (includes room for \0)
+ */
+static void
+quoteOneName(char *buffer, const char *name)
+{
+ /* Rather than trying to be smart, just always quote it. */
+ *buffer++ = '"';
+ while (*name)
+ {
+ if (*name == '"')
+ *buffer++ = '"';
+ *buffer++ = *name++;
+ }
+ *buffer++ = '"';
+ *buffer = '\0';
+}
+/*
+ * quoteRelationName --- safely quote a fully qualified relation name
+ *
+ * buffer must be MAX_QUOTED_REL_NAME_LEN long (includes room for \0)
+ */
+static void
+quoteRelationName(char *buffer, Relation rel)
+{
+ HeapTuple tuple;
+ char *nsname;
+
+ tuple = SearchSysCache(NAMESPACEOID,
+ ObjectIdGetDatum(RelationGetNamespace(rel)),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "Failed to lookup namespace %u of relation %s",
+ RelationGetNamespace(rel), RelationGetRelationName(rel));
+ nsname = NameStr(((Form_pg_namespace) GETSTRUCT(tuple))->nspname);
+ quoteOneName(buffer, nsname);
+ ReleaseSysCache(tuple);
+ buffer += strlen(buffer);
+ *buffer++ = '.';
+ quoteOneName(buffer, RelationGetRelationName(rel));
+}
/* ----------
static int
ri_DetermineMatchType(char *str)
{
- if (!strcmp(str, "UNSPECIFIED"))
+ if (strcmp(str, "UNSPECIFIED") == 0)
return RI_MATCH_TYPE_UNSPECIFIED;
- if (!strcmp(str, "FULL"))
+ if (strcmp(str, "FULL") == 0)
return RI_MATCH_TYPE_FULL;
- if (!strcmp(str, "PARTIAL"))
+ if (strcmp(str, "PARTIAL") == 0)
return RI_MATCH_TYPE_PARTIAL;
elog(ERROR, "unrecognized referential integrity MATCH type '%s'", str);
if (fno == SPI_ERROR_NOATTRIBUTE)
elog(ERROR, "constraint %s: table %s does not have an attribute %s",
argv[RI_CONSTRAINT_NAME_ARGNO],
- argv[RI_FK_RELNAME_ARGNO],
+ RelationGetRelationName(fk_rel),
argv[j]);
key->keypair[i][RI_KEYPAIR_FK_IDX] = fno;
if (fno == SPI_ERROR_NOATTRIBUTE)
elog(ERROR, "constraint %s: table %s does not have an attribute %s",
argv[RI_CONSTRAINT_NAME_ARGNO],
- argv[RI_PK_RELNAME_ARGNO],
+ RelationGetRelationName(pk_rel),
argv[j + 1]);
key->keypair[i][RI_KEYPAIR_PK_IDX] = fno;
}
-
- return;
}