Skip to content

Commit 035bacb

Browse files
author
Commitfest Bot
committed
[CF 5577] v6 - Invalid cached plan in check_foreign_key; cascade update in check_primary_key; refint.c
This branch was automatically generated by a robot using patches from an email thread registered at: https://commitfest.postgresql.org/patch/5577 The branch will be overwritten each time a new patch version is posted to the thread, and also periodically to check for bitrot caused by changes on the master branch. Patch(es): https://www.postgresql.org/message-id/[email protected] Author(s): Dmitrii Bondar
2 parents 898c131 + 7211c8a commit 035bacb

File tree

4 files changed

+91
-34
lines changed

4 files changed

+91
-34
lines changed

contrib/spi/refint.c

+19-7
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ check_primary_key(PG_FUNCTION_ARGS)
8484
/* internal error */
8585
elog(ERROR, "check_primary_key: must be fired for row");
8686

87+
if (!TRIGGER_FIRED_AFTER(trigdata->tg_event))
88+
/* internal error */
89+
elog(ERROR, "check_primary_key: must be fired by AFTER trigger");
90+
8791
/* If INSERTion then must check Tuple to being inserted */
8892
if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
8993
tuple = trigdata->tg_trigtuple;
@@ -267,7 +271,6 @@ check_foreign_key(PG_FUNCTION_ARGS)
267271
#ifdef DEBUG_QUERY
268272
elog(DEBUG4, "check_foreign_key: Enter Function");
269273
#endif
270-
271274
/*
272275
* Some checks first...
273276
*/
@@ -287,6 +290,10 @@ check_foreign_key(PG_FUNCTION_ARGS)
287290
/* internal error */
288291
elog(ERROR, "check_foreign_key: cannot process INSERT events");
289292

293+
if (!TRIGGER_FIRED_AFTER(trigdata->tg_event))
294+
/* internal error */
295+
elog(ERROR, "check_foreign_key: must be fired by AFTER trigger");
296+
290297
/* Have to check tg_trigtuple - tuple being deleted */
291298
trigtuple = trigdata->tg_trigtuple;
292299

@@ -338,10 +345,10 @@ check_foreign_key(PG_FUNCTION_ARGS)
338345
kvals = (Datum *) palloc(nkeys * sizeof(Datum));
339346

340347
/*
341-
* Construct ident string as TriggerName $ TriggeredRelationId and try to
342-
* find prepared execution plan(s).
348+
* Construct ident string as TriggerName $ TriggeredRelationId $ OperationType
349+
* and try to find prepared execution plan(s).
343350
*/
344-
snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id);
351+
snprintf(ident, sizeof(ident), "%s$%u$%c", trigger->tgname, rel->rd_id, is_update ? 'U' : 'D');
345352
plan = find_plan(ident, &FPlans, &nFPlans);
346353

347354
/* if there is no plan(s) then allocate argtypes for preparation */
@@ -573,7 +580,6 @@ check_foreign_key(PG_FUNCTION_ARGS)
573580

574581
relname = args[0];
575582

576-
snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id);
577583
plan = find_plan(ident, &FPlans, &nFPlans);
578584
ret = SPI_execp(plan->splan[r], kvals, NULL, tcount);
579585
/* we have no NULLs - so we pass ^^^^ here */
@@ -596,9 +602,15 @@ check_foreign_key(PG_FUNCTION_ARGS)
596602
else
597603
{
598604
#ifdef REFINT_VERBOSE
605+
const char* operation;
606+
607+
if (action == 'c')
608+
operation = is_update ? "updated" : "deleted";
609+
else
610+
operation = "set to null";
611+
599612
elog(NOTICE, "%s: " UINT64_FORMAT " tuple(s) of %s are %s",
600-
trigger->tgname, SPI_processed, relname,
601-
(action == 'c') ? "deleted" : "set to null");
613+
trigger->tgname, SPI_processed, relname, operation);
602614
#endif
603615
}
604616
args += nkeys + 1; /* to the next relation */

doc/src/sgml/contrib-spi.sgml

+10-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636

3737
<para>
3838
<function>check_primary_key()</function> checks the referencing table.
39-
To use, create a <literal>BEFORE INSERT OR UPDATE</literal> trigger using this
39+
To use, create an <literal>AFTER INSERT OR UPDATE</literal> trigger using this
4040
function on a table referencing another table. Specify as the trigger
4141
arguments: the referencing table's column name(s) which form the foreign
4242
key, the referenced table name, and the column names in the referenced table
@@ -46,7 +46,7 @@
4646

4747
<para>
4848
<function>check_foreign_key()</function> checks the referenced table.
49-
To use, create a <literal>BEFORE DELETE OR UPDATE</literal> trigger using this
49+
To use, create an <literal>AFTER DELETE OR UPDATE</literal> trigger using this
5050
function on a table referenced by other table(s). Specify as the trigger
5151
arguments: the number of referencing tables for which the function has to
5252
perform checking, the action if a referencing key is found
@@ -63,6 +63,14 @@
6363
<para>
6464
There are examples in <filename>refint.example</filename>.
6565
</para>
66+
67+
<para>
68+
Note that if these triggers are executed from another <literal>BEFORE</literal>
69+
trigger, they can fail unexpectedly. For example, if a user inserts row1 and then
70+
the <literal>BEFORE</literal> trigger inserts row2 and calls a trigger with the
71+
<function>check_foreign_key()</function>, the <function>check_foreign_key()</function>
72+
function will not see row1 and will fail.
73+
</para>
6674
</sect2>
6775

6876
<sect2 id="contrib-spi-autoinc">

src/test/regress/expected/triggers.out

+37-20
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,20 @@ create unique index pkeys_i on pkeys (pkey1, pkey2);
4646
-- (fkey3) --> fkeys2 (pkey23)
4747
--
4848
create trigger check_fkeys_pkey_exist
49-
before insert or update on fkeys
49+
after insert or update on fkeys
5050
for each row
5151
execute function
5252
check_primary_key ('fkey1', 'fkey2', 'pkeys', 'pkey1', 'pkey2');
5353
create trigger check_fkeys_pkey2_exist
54-
before insert or update on fkeys
54+
after insert or update on fkeys
5555
for each row
5656
execute function check_primary_key ('fkey3', 'fkeys2', 'pkey23');
5757
--
5858
-- For fkeys2:
5959
-- (fkey21, fkey22) --> pkeys (pkey1, pkey2)
6060
--
6161
create trigger check_fkeys2_pkey_exist
62-
before insert or update on fkeys2
62+
after insert or update on fkeys2
6363
for each row
6464
execute procedure
6565
check_primary_key ('fkey21', 'fkey22', 'pkeys', 'pkey1', 'pkey2');
@@ -74,7 +74,7 @@ COMMENT ON TRIGGER check_fkeys2_pkey_exist ON fkeys2 IS NULL;
7474
-- fkeys (fkey1, fkey2) and fkeys2 (fkey21, fkey22)
7575
--
7676
create trigger check_pkeys_fkey_cascade
77-
before delete or update on pkeys
77+
after delete or update on pkeys
7878
for each row
7979
execute procedure
8080
check_foreign_key (2, 'cascade', 'pkey1', 'pkey2',
@@ -85,9 +85,27 @@ create trigger check_pkeys_fkey_cascade
8585
-- fkeys (fkey3)
8686
--
8787
create trigger check_fkeys2_fkey_restrict
88-
before delete or update on fkeys2
88+
after delete or update on fkeys2
8989
for each row
9090
execute procedure check_foreign_key (1, 'restrict', 'pkey23', 'fkeys', 'fkey3');
91+
-- BEFORE triggers must raise an error
92+
create trigger check_fkeys2_pkey_exist_before
93+
before insert or update on fkeys2
94+
for each row
95+
execute procedure
96+
check_primary_key ('fkey21', 'fkey22', 'pkeys', 'pkey1', 'pkey2');
97+
create trigger check_pkeys_fkey_cascade_before
98+
before delete or update on pkeys
99+
for each row
100+
execute procedure
101+
check_foreign_key (2, 'cascade', 'pkey1', 'pkey2',
102+
'fkeys', 'fkey1', 'fkey2', 'fkeys2', 'fkey21', 'fkey22');
103+
update pkeys set pkey1 = 7, pkey2 = '70' where pkey1 = 10 and pkey2 = '1';
104+
ERROR: check_foreign_key: must be fired by AFTER trigger
105+
insert into fkeys2 values (10, '1', 1);
106+
ERROR: check_primary_key: must be fired by AFTER trigger
107+
drop trigger check_fkeys2_pkey_exist_before on fkeys2;
108+
drop trigger check_pkeys_fkey_cascade_before on pkeys;
91109
insert into fkeys2 values (10, '1', 1);
92110
insert into fkeys2 values (30, '3', 2);
93111
insert into fkeys2 values (40, '4', 5);
@@ -116,12 +134,11 @@ delete from pkeys where pkey1 = 40 and pkey2 = '4';
116134
NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys are deleted
117135
NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys2 are deleted
118136
update pkeys set pkey1 = 7, pkey2 = '70' where pkey1 = 50 and pkey2 = '5';
119-
NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys are deleted
120-
ERROR: "check_fkeys2_fkey_restrict": tuple is referenced in "fkeys"
121-
CONTEXT: SQL statement "delete from fkeys2 where fkey21 = $1 and fkey22 = $2 "
137+
NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys are updated
138+
NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys2 are updated
122139
update pkeys set pkey1 = 7, pkey2 = '70' where pkey1 = 10 and pkey2 = '1';
123-
NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys are deleted
124-
NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys2 are deleted
140+
ERROR: duplicate key value violates unique constraint "pkeys_i"
141+
DETAIL: Key (pkey1, pkey2)=(7, 70) already exists.
125142
SELECT trigger_name, event_manipulation, event_object_schema, event_object_table,
126143
action_order, action_condition, action_orientation, action_timing,
127144
action_reference_old_table, action_reference_new_table
@@ -130,16 +147,16 @@ SELECT trigger_name, event_manipulation, event_object_schema, event_object_table
130147
ORDER BY trigger_name COLLATE "C", 2;
131148
trigger_name | event_manipulation | event_object_schema | event_object_table | action_order | action_condition | action_orientation | action_timing | action_reference_old_table | action_reference_new_table
132149
----------------------------+--------------------+---------------------+--------------------+--------------+------------------+--------------------+---------------+----------------------------+----------------------------
133-
check_fkeys2_fkey_restrict | DELETE | public | fkeys2 | 1 | | ROW | BEFORE | |
134-
check_fkeys2_fkey_restrict | UPDATE | public | fkeys2 | 1 | | ROW | BEFORE | |
135-
check_fkeys2_pkey_exist | INSERT | public | fkeys2 | 1 | | ROW | BEFORE | |
136-
check_fkeys2_pkey_exist | UPDATE | public | fkeys2 | 2 | | ROW | BEFORE | |
137-
check_fkeys_pkey2_exist | INSERT | public | fkeys | 1 | | ROW | BEFORE | |
138-
check_fkeys_pkey2_exist | UPDATE | public | fkeys | 1 | | ROW | BEFORE | |
139-
check_fkeys_pkey_exist | INSERT | public | fkeys | 2 | | ROW | BEFORE | |
140-
check_fkeys_pkey_exist | UPDATE | public | fkeys | 2 | | ROW | BEFORE | |
141-
check_pkeys_fkey_cascade | DELETE | public | pkeys | 1 | | ROW | BEFORE | |
142-
check_pkeys_fkey_cascade | UPDATE | public | pkeys | 1 | | ROW | BEFORE | |
150+
check_fkeys2_fkey_restrict | DELETE | public | fkeys2 | 1 | | ROW | AFTER | |
151+
check_fkeys2_fkey_restrict | UPDATE | public | fkeys2 | 1 | | ROW | AFTER | |
152+
check_fkeys2_pkey_exist | INSERT | public | fkeys2 | 1 | | ROW | AFTER | |
153+
check_fkeys2_pkey_exist | UPDATE | public | fkeys2 | 2 | | ROW | AFTER | |
154+
check_fkeys_pkey2_exist | INSERT | public | fkeys | 1 | | ROW | AFTER | |
155+
check_fkeys_pkey2_exist | UPDATE | public | fkeys | 1 | | ROW | AFTER | |
156+
check_fkeys_pkey_exist | INSERT | public | fkeys | 2 | | ROW | AFTER | |
157+
check_fkeys_pkey_exist | UPDATE | public | fkeys | 2 | | ROW | AFTER | |
158+
check_pkeys_fkey_cascade | DELETE | public | pkeys | 1 | | ROW | AFTER | |
159+
check_pkeys_fkey_cascade | UPDATE | public | pkeys | 1 | | ROW | AFTER | |
143160
(10 rows)
144161

145162
DROP TABLE pkeys;

src/test/regress/sql/triggers.sql

+25-5
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ create unique index pkeys_i on pkeys (pkey1, pkey2);
5757
-- (fkey3) --> fkeys2 (pkey23)
5858
--
5959
create trigger check_fkeys_pkey_exist
60-
before insert or update on fkeys
60+
after insert or update on fkeys
6161
for each row
6262
execute function
6363
check_primary_key ('fkey1', 'fkey2', 'pkeys', 'pkey1', 'pkey2');
6464

6565
create trigger check_fkeys_pkey2_exist
66-
before insert or update on fkeys
66+
after insert or update on fkeys
6767
for each row
6868
execute function check_primary_key ('fkey3', 'fkeys2', 'pkey23');
6969

@@ -72,7 +72,7 @@ create trigger check_fkeys_pkey2_exist
7272
-- (fkey21, fkey22) --> pkeys (pkey1, pkey2)
7373
--
7474
create trigger check_fkeys2_pkey_exist
75-
before insert or update on fkeys2
75+
after insert or update on fkeys2
7676
for each row
7777
execute procedure
7878
check_primary_key ('fkey21', 'fkey22', 'pkeys', 'pkey1', 'pkey2');
@@ -88,7 +88,7 @@ COMMENT ON TRIGGER check_fkeys2_pkey_exist ON fkeys2 IS NULL;
8888
-- fkeys (fkey1, fkey2) and fkeys2 (fkey21, fkey22)
8989
--
9090
create trigger check_pkeys_fkey_cascade
91-
before delete or update on pkeys
91+
after delete or update on pkeys
9292
for each row
9393
execute procedure
9494
check_foreign_key (2, 'cascade', 'pkey1', 'pkey2',
@@ -100,10 +100,30 @@ create trigger check_pkeys_fkey_cascade
100100
-- fkeys (fkey3)
101101
--
102102
create trigger check_fkeys2_fkey_restrict
103-
before delete or update on fkeys2
103+
after delete or update on fkeys2
104104
for each row
105105
execute procedure check_foreign_key (1, 'restrict', 'pkey23', 'fkeys', 'fkey3');
106106

107+
-- BEFORE triggers must raise an error
108+
create trigger check_fkeys2_pkey_exist_before
109+
before insert or update on fkeys2
110+
for each row
111+
execute procedure
112+
check_primary_key ('fkey21', 'fkey22', 'pkeys', 'pkey1', 'pkey2');
113+
114+
create trigger check_pkeys_fkey_cascade_before
115+
before delete or update on pkeys
116+
for each row
117+
execute procedure
118+
check_foreign_key (2, 'cascade', 'pkey1', 'pkey2',
119+
'fkeys', 'fkey1', 'fkey2', 'fkeys2', 'fkey21', 'fkey22');
120+
121+
update pkeys set pkey1 = 7, pkey2 = '70' where pkey1 = 10 and pkey2 = '1';
122+
insert into fkeys2 values (10, '1', 1);
123+
124+
drop trigger check_fkeys2_pkey_exist_before on fkeys2;
125+
drop trigger check_pkeys_fkey_cascade_before on pkeys;
126+
107127
insert into fkeys2 values (10, '1', 1);
108128
insert into fkeys2 values (30, '3', 2);
109129
insert into fkeys2 values (40, '4', 5);

0 commit comments

Comments
 (0)