Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 578e71f

Browse files
committed
This should fix a bug where a row that was updated or
deleted that had another row inserted/updated to its old value during the same statement or other statements before the integrity check for noaction would incorrectly error. This could happen in deferred constraints or due to triggers or functions. It's effectively a reworking of the previous patch that did a not exists to instead do a separate check. Stephan Szabo
1 parent 9f1fc10 commit 578e71f

File tree

1 file changed

+243
-4
lines changed

1 file changed

+243
-4
lines changed

src/backend/utils/adt/ri_triggers.c

Lines changed: 243 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*
1818
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
1919
*
20-
* $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.39 2002/06/21 02:59:38 momjian Exp $
20+
* $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.40 2002/07/30 16:33:21 momjian Exp $
2121
*
2222
* ----------
2323
*/
@@ -130,19 +130,24 @@ static void ri_BuildQueryKeyFull(RI_QueryKey *key, Oid constr_id,
130130
int32 constr_queryno,
131131
Relation fk_rel, Relation pk_rel,
132132
int argc, char **argv);
133+
static void ri_BuildQueryKeyPkCheck(RI_QueryKey *key, Oid constr_id,
134+
int32 constr_queryno,
135+
Relation pk_rel,
136+
int argc, char **argv);
133137
static bool ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
134138
RI_QueryKey *key, int pairidx);
135139
static bool ri_AllKeysUnequal(Relation rel, HeapTuple oldtup, HeapTuple newtup,
136140
RI_QueryKey *key, int pairidx);
137141
static bool ri_OneKeyEqual(Relation rel, int column, HeapTuple oldtup,
138142
HeapTuple newtup, RI_QueryKey *key, int pairidx);
139143
static bool ri_AttributesEqual(Oid typeid, Datum oldvalue, Datum newvalue);
144+
static bool ri_Check_Pk_Match(Relation pk_rel, HeapTuple old_row,
145+
Oid tgoid, int match_type, int tgnargs, char **tgargs);
140146

141147
static void ri_InitHashTables(void);
142148
static void *ri_FetchPreparedPlan(RI_QueryKey *key);
143149
static void ri_HashPreparedPlan(RI_QueryKey *key, void *plan);
144150

145-
146151
/* ----------
147152
* RI_FKey_check -
148153
*
@@ -385,6 +390,7 @@ RI_FKey_check(PG_FUNCTION_ARGS)
385390
if (SPI_connect() != SPI_OK_CONNECT)
386391
elog(WARNING, "SPI_connect() failed in RI_FKey_check()");
387392

393+
388394
/*
389395
* Fetch or prepare a saved plan for the real check
390396
*/
@@ -512,6 +518,161 @@ RI_FKey_check_upd(PG_FUNCTION_ARGS)
512518
}
513519

514520

521+
/* ----------
522+
* ri_Check_Pk_Match
523+
*
524+
* Check for matching value of old pk row in current state for
525+
* noaction triggers. Returns false if no row was found and a fk row
526+
* could potentially be referencing this row, true otherwise.
527+
* ----------
528+
*/
529+
static bool
530+
ri_Check_Pk_Match(Relation pk_rel, HeapTuple old_row, Oid tgoid, int match_type, int tgnargs, char **tgargs) {
531+
void *qplan;
532+
RI_QueryKey qkey;
533+
bool isnull;
534+
Datum check_values[RI_MAX_NUMKEYS];
535+
char check_nulls[RI_MAX_NUMKEYS + 1];
536+
int i;
537+
Oid save_uid;
538+
bool result;
539+
save_uid = GetUserId();
540+
541+
ri_BuildQueryKeyPkCheck(&qkey, tgoid,
542+
RI_PLAN_CHECK_LOOKUPPK, pk_rel,
543+
tgnargs, tgargs);
544+
545+
switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
546+
{
547+
case RI_KEYS_ALL_NULL:
548+
/*
549+
* No check - nothing could have been referencing this row anyway.
550+
*/
551+
return true;
552+
553+
case RI_KEYS_SOME_NULL:
554+
555+
/*
556+
* This is the only case that differs between the three kinds
557+
* of MATCH.
558+
*/
559+
switch (match_type)
560+
{
561+
case RI_MATCH_TYPE_FULL:
562+
case RI_MATCH_TYPE_UNSPECIFIED:
563+
564+
/*
565+
* MATCH <unspecified>/FULL - if ANY column is null, we
566+
* can't be matching to this row already.
567+
*/
568+
return true;
569+
570+
case RI_MATCH_TYPE_PARTIAL:
571+
572+
/*
573+
* MATCH PARTIAL - all non-null columns must match.
574+
* (not implemented, can be done by modifying the
575+
* query below to only include non-null columns, or by
576+
* writing a special version here)
577+
*/
578+
elog(ERROR, "MATCH PARTIAL not yet implemented");
579+
break;
580+
}
581+
582+
case RI_KEYS_NONE_NULL:
583+
584+
/*
585+
* Have a full qualified key - continue below for all three
586+
* kinds of MATCH.
587+
*/
588+
break;
589+
}
590+
591+
if (SPI_connect() != SPI_OK_CONNECT)
592+
elog(WARNING, "SPI_connect() failed in RI_FKey_check()");
593+
594+
595+
/*
596+
* Fetch or prepare a saved plan for the real check
597+
*/
598+
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
599+
{
600+
char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
601+
(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
602+
char pkrelname[MAX_QUOTED_REL_NAME_LEN];
603+
char attname[MAX_QUOTED_NAME_LEN];
604+
const char *querysep;
605+
Oid queryoids[RI_MAX_NUMKEYS];
606+
607+
/* ----------
608+
* The query string built is
609+
* SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...]
610+
* The type id's for the $ parameters are those of the
611+
* corresponding FK attributes. Thus, SPI_prepare could
612+
* eventually fail if the parser cannot identify some way
613+
* how to compare these two types by '='.
614+
* ----------
615+
*/
616+
quoteRelationName(pkrelname, pk_rel);
617+
sprintf(querystr, "SELECT 1 FROM ONLY %s x", pkrelname);
618+
querysep = "WHERE";
619+
for (i = 0; i < qkey.nkeypairs; i++)
620+
{
621+
quoteOneName(attname,
622+
tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_PK_IDX]);
623+
sprintf(querystr + strlen(querystr), " %s %s = $%d",
624+
querysep, attname, i+1);
625+
querysep = "AND";
626+
queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
627+
qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
628+
}
629+
strcat(querystr, " FOR UPDATE OF x");
630+
631+
/*
632+
* Prepare, save and remember the new plan.
633+
*/
634+
qplan = SPI_prepare(querystr, qkey.nkeypairs, queryoids);
635+
qplan = SPI_saveplan(qplan);
636+
ri_HashPreparedPlan(&qkey, qplan);
637+
}
638+
639+
/*
640+
* We have a plan now. Build up the arguments for SPI_execp() from the
641+
* key values in the new FK tuple.
642+
*/
643+
for (i = 0; i < qkey.nkeypairs; i++)
644+
{
645+
check_values[i] = SPI_getbinval(old_row,
646+
pk_rel->rd_att,
647+
qkey.keypair[i][RI_KEYPAIR_PK_IDX],
648+
&isnull);
649+
if (isnull)
650+
check_nulls[i] = 'n';
651+
else
652+
check_nulls[i] = ' ';
653+
}
654+
check_nulls[i] = '\0';
655+
656+
/*
657+
* Now check that foreign key exists in PK table
658+
*/
659+
660+
SetUserId(RelationGetForm(pk_rel)->relowner);
661+
662+
if (SPI_execp(qplan, check_values, check_nulls, 1) != SPI_OK_SELECT)
663+
elog(ERROR, "SPI_execp() failed in ri_Check_Pk_Match()");
664+
665+
SetUserId(save_uid);
666+
667+
result = (SPI_processed!=0);
668+
669+
if (SPI_finish() != SPI_OK_FINISH)
670+
elog(WARNING, "SPI_finish() failed in ri_Check_Pk_Match()");
671+
672+
return result;
673+
}
674+
675+
515676
/* ----------
516677
* RI_FKey_noaction_del -
517678
*
@@ -535,6 +696,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
535696
char del_nulls[RI_MAX_NUMKEYS + 1];
536697
bool isnull;
537698
int i;
699+
int match_type;
538700
Oid save_uid;
539701

540702
save_uid = GetUserId();
@@ -581,7 +743,18 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
581743
pk_rel = trigdata->tg_relation;
582744
old_row = trigdata->tg_trigtuple;
583745

584-
switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
746+
match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
747+
if (ri_Check_Pk_Match(pk_rel, old_row, trigdata->tg_trigger->tgoid,
748+
match_type, tgnargs, tgargs)) {
749+
/*
750+
* There's either another row, or no row could match this
751+
* one. In either case, we don't need to do the check.
752+
*/
753+
heap_close(fk_rel, RowShareLock);
754+
return PointerGetDatum(NULL);
755+
}
756+
757+
switch (match_type)
585758
{
586759
/* ----------
587760
* SQL3 11.9 <referential constraint definition>
@@ -746,6 +919,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
746919
char upd_nulls[RI_MAX_NUMKEYS + 1];
747920
bool isnull;
748921
int i;
922+
int match_type;
749923
Oid save_uid;
750924

751925
save_uid = GetUserId();
@@ -793,7 +967,18 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
793967
new_row = trigdata->tg_newtuple;
794968
old_row = trigdata->tg_trigtuple;
795969

796-
switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
970+
match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
971+
if (ri_Check_Pk_Match(pk_rel, old_row, trigdata->tg_trigger->tgoid,
972+
match_type, tgnargs, tgargs)) {
973+
/*
974+
* There's either another row, or no row could match this
975+
* one. In either case, we don't need to do the check.
976+
*/
977+
heap_close(fk_rel, RowShareLock);
978+
return PointerGetDatum(NULL);
979+
}
980+
981+
switch (match_type)
797982
{
798983
/* ----------
799984
* SQL3 11.9 <referential constraint definition>
@@ -3042,6 +3227,59 @@ ri_BuildQueryKeyFull(RI_QueryKey *key, Oid constr_id, int32 constr_queryno,
30423227
}
30433228
}
30443229

3230+
/* ----------
3231+
* ri_BuildQueryKeyPkCheck -
3232+
*
3233+
* Build up a new hashtable key for a prepared SPI plan of a
3234+
* check for PK rows in noaction triggers.
3235+
*
3236+
* constr_type is FULL
3237+
* constr_id is the OID of the pg_trigger row that invoked us
3238+
* constr_queryno is an internal number of the query inside the proc
3239+
* pk_relid is the OID of referenced relation
3240+
* nkeypairs is the number of keypairs
3241+
* following are the attribute number keypairs of the trigger invocation
3242+
*
3243+
* At least for MATCH FULL this builds a unique key per plan.
3244+
* ----------
3245+
*/
3246+
static void
3247+
ri_BuildQueryKeyPkCheck(RI_QueryKey *key, Oid constr_id, int32 constr_queryno,
3248+
Relation pk_rel,
3249+
int argc, char **argv)
3250+
{
3251+
int i;
3252+
int j;
3253+
int fno;
3254+
3255+
/*
3256+
* Initialize the key and fill in type, oid's and number of keypairs
3257+
*/
3258+
memset((void *) key, 0, sizeof(RI_QueryKey));
3259+
key->constr_type = RI_MATCH_TYPE_FULL;
3260+
key->constr_id = constr_id;
3261+
key->constr_queryno = constr_queryno;
3262+
key->fk_relid = 0;
3263+
key->pk_relid = pk_rel->rd_id;
3264+
key->nkeypairs = (argc - RI_FIRST_ATTNAME_ARGNO) / 2;
3265+
3266+
/*
3267+
* Lookup the attribute numbers of the arguments to the trigger call
3268+
* and fill in the keypairs.
3269+
*/
3270+
for (i = 0, j = RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_PK_IDX; j < argc; i++, j += 2)
3271+
{
3272+
fno = SPI_fnumber(pk_rel->rd_att, argv[j]);
3273+
if (fno == SPI_ERROR_NOATTRIBUTE)
3274+
elog(ERROR, "constraint %s: table %s does not have an attribute %s",
3275+
argv[RI_CONSTRAINT_NAME_ARGNO],
3276+
RelationGetRelationName(pk_rel),
3277+
argv[j + 1]);
3278+
key->keypair[i][RI_KEYPAIR_PK_IDX] = fno;
3279+
key->keypair[i][RI_KEYPAIR_FK_IDX] = 0;
3280+
}
3281+
}
3282+
30453283

30463284
/* ----------
30473285
* ri_NullCheck -
@@ -3378,3 +3616,4 @@ ri_AttributesEqual(Oid typeid, Datum oldvalue, Datum newvalue)
33783616
return DatumGetBool(FunctionCall2(&(entry->oprfmgrinfo),
33793617
oldvalue, newvalue));
33803618
}
3619+

0 commit comments

Comments
 (0)