Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit8cfbdf8

Browse files
committed
Fix some issues in contrib/spi/refint.c.
check_foreign_key incorrectly used a single cache entry for its savedplans for a 'c' (cascade) trigger, although there are two differentqueries to execute depending on whether it fires for an update or adelete. This caused the wrong things to be done if both types ofevent occur in one session. (This was indeed visible in the triggersregression test, but apparently nobody ever questioned it.) To fix,add the operation type to the cache key.Its debug log output failed to distinguish update from deleteevents, too.Also, change the intended trigger usage from BEFORE ROW to AFTER ROW,and add checks insisting on that usage. BEFORE is really ratherunsafe, since if there are other BEFORE triggers they might change orcancel the operation we are trying to check. AFTER triggers are thestandard way to propagate changes to other rows, so we should followthat way here.In passing, remove a useless duplicate lookup of the cache entry.This code is mostly intended as a documentation example, so wewon't consider a back-patch.Author: Dmitrii Bondar <d.bondar@postgrespro.ru>Reviewed-by: Paul Jungwirth <pj@illuminatedcomputing.com>Reviewed-by: Lilian Ontowhee <ontowhee@gmail.com>Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>Discussion:https://postgr.es/m/79755a2b18ed4fe5e29da6a87a1e00d1@postgrespro.ru
1 parent8e293e6 commit8cfbdf8

File tree

4 files changed

+55
-34
lines changed

4 files changed

+55
-34
lines changed

‎contrib/spi/refint.c

Lines changed: 19 additions & 7 deletions
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;
@@ -287,6 +291,10 @@ check_foreign_key(PG_FUNCTION_ARGS)
287291
/* internal error */
288292
elog(ERROR,"check_foreign_key: cannot process INSERT events");
289293

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

@@ -338,10 +346,10 @@ check_foreign_key(PG_FUNCTION_ARGS)
338346
kvals= (Datum*)palloc(nkeys*sizeof(Datum));
339347

340348
/*
341-
* Construct ident string as TriggerName $ TriggeredRelationIdand try to
342-
* find prepared execution plan(s).
349+
* Construct ident string as TriggerName $ TriggeredRelationId$
350+
*OperationType and try tofind prepared execution plan(s).
343351
*/
344-
snprintf(ident,sizeof(ident),"%s$%u",trigger->tgname,rel->rd_id);
352+
snprintf(ident,sizeof(ident),"%s$%u$%c",trigger->tgname,rel->rd_id,is_update ?'U' :'D');
345353
plan=find_plan(ident,&FPlans,&nFPlans);
346354

347355
/* if there is no plan(s) then allocate argtypes for preparation */
@@ -573,8 +581,6 @@ check_foreign_key(PG_FUNCTION_ARGS)
573581

574582
relname=args[0];
575583

576-
snprintf(ident,sizeof(ident),"%s$%u",trigger->tgname,rel->rd_id);
577-
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 */
580586

@@ -596,9 +602,15 @@ check_foreign_key(PG_FUNCTION_ARGS)
596602
else
597603
{
598604
#ifdefREFINT_VERBOSE
605+
constchar*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

Lines changed: 12 additions & 2 deletions
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, createa <literal>BEFORE INSERT OR UPDATE</literal> trigger using this
39+
To use, createan <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, createa <literal>BEFORE DELETE OR UPDATE</literal> trigger using this
49+
To use, createan <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
@@ -60,6 +60,16 @@
6060
unique index.
6161
</para>
6262

63+
<para>
64+
Note that if these triggers are executed from
65+
another <literal>BEFORE</literal> trigger, they can fail unexpectedly. For
66+
example, if a user inserts row1 and then the <literal>BEFORE</literal>
67+
trigger inserts row2 and calls a trigger with the
68+
<function>check_foreign_key()</function>,
69+
the <function>check_foreign_key()</function>
70+
function will not see row1 and will fail.
71+
</para>
72+
6373
<para>
6474
There are examples in <filename>refint.example</filename>.
6575
</para>

‎src/test/regress/expected/triggers.out

Lines changed: 19 additions & 20 deletions
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,7 +85,7 @@ 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');
9191
insert into fkeys2 values (10, '1', 1);
@@ -116,12 +116,11 @@ delete from pkeys where pkey1 = 40 and pkey2 = '4';
116116
NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys are deleted
117117
NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys2 are deleted
118118
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 "
119+
NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys are updated
120+
NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys2 are updated
122121
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
122+
ERROR:duplicate key value violates unique constraint "pkeys_i"
123+
DETAIL:Key (pkey1, pkey2)=(7, 70) already exists.
125124
SELECT trigger_name, event_manipulation, event_object_schema, event_object_table,
126125
action_order, action_condition, action_orientation, action_timing,
127126
action_reference_old_table, action_reference_new_table
@@ -130,16 +129,16 @@ SELECT trigger_name, event_manipulation, event_object_schema, event_object_table
130129
ORDER BY trigger_name COLLATE "C", 2;
131130
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
132131
----------------------------+--------------------+---------------------+--------------------+--------------+------------------+--------------------+---------------+----------------------------+----------------------------
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 | |
132+
check_fkeys2_fkey_restrict | DELETE | public | fkeys2 | 1 | | ROW |AFTER | |
133+
check_fkeys2_fkey_restrict | UPDATE | public | fkeys2 | 1 | | ROW |AFTER | |
134+
check_fkeys2_pkey_exist | INSERT | public | fkeys2 | 1 | | ROW |AFTER | |
135+
check_fkeys2_pkey_exist | UPDATE | public | fkeys2 | 2 | | ROW |AFTER | |
136+
check_fkeys_pkey2_exist | INSERT | public | fkeys | 1 | | ROW |AFTER | |
137+
check_fkeys_pkey2_exist | UPDATE | public | fkeys | 1 | | ROW |AFTER | |
138+
check_fkeys_pkey_exist | INSERT | public | fkeys | 2 | | ROW |AFTER | |
139+
check_fkeys_pkey_exist | UPDATE | public | fkeys | 2 | | ROW |AFTER | |
140+
check_pkeys_fkey_cascade | DELETE | public | pkeys | 1 | | ROW |AFTER | |
141+
check_pkeys_fkey_cascade | UPDATE | public | pkeys | 1 | | ROW |AFTER | |
143142
(10 rows)
144143

145144
DROP TABLE pkeys;

‎src/test/regress/sql/triggers.sql

Lines changed: 5 additions & 5 deletions
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
createtriggercheck_fkeys_pkey_exist
60-
before insertorupdateon fkeys
60+
after insertorupdateon fkeys
6161
for each row
6262
execute function
6363
check_primary_key ('fkey1','fkey2','pkeys','pkey1','pkey2');
6464

6565
createtriggercheck_fkeys_pkey2_exist
66-
before insertorupdateon fkeys
66+
after insertorupdateon 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
createtriggercheck_fkeys2_pkey_exist
75-
before insertorupdateon fkeys2
75+
after insertorupdateon 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
createtriggercheck_pkeys_fkey_cascade
91-
beforedeleteorupdateon pkeys
91+
afterdeleteorupdateon pkeys
9292
for each row
9393
execute procedure
9494
check_foreign_key (2,'cascade','pkey1','pkey2',
@@ -100,7 +100,7 @@ create trigger check_pkeys_fkey_cascade
100100
-- fkeys (fkey3)
101101
--
102102
createtriggercheck_fkeys2_fkey_restrict
103-
beforedeleteorupdateon fkeys2
103+
afterdeleteorupdateon fkeys2
104104
for each row
105105
execute procedure check_foreign_key (1,'restrict','pkey23','fkeys','fkey3');
106106

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp