1 -- test plperl triggers
2 CREATE TYPE rowcomp as (i int);
3 CREATE TYPE rowcompnest as (rfoo rowcomp);
4 CREATE TABLE trigger_test (
9 CREATE TABLE trigger_test_generated (
11 j int GENERATED ALWAYS AS (i * 2) STORED
13 CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger LANGUAGE plperl AS $$
15 # make sure keys are sorted for consistent results - perl no longer
16 # hashes in repeatable fashion across runs
25 elsif (ref $val eq 'HASH')
28 foreach my $rowkey (sort keys %$val)
31 my $rowval = str($val->{$rowkey});
32 $str .= "'$rowkey' => $rowval";
34 return '{'. $str .'}';
36 elsif (ref $val eq 'ARRAY')
39 for my $argval (@$val)
44 return '['. $str .']';
52 foreach my $key (sort keys %$_TD)
55 my $val = $_TD->{$key};
57 # relid is variable, so we can not use it repeatably
58 $val = "bogus:12345" if $key eq 'relid';
60 elog(NOTICE, "\$_TD->\{$key\} = ". str($val));
62 return undef; # allow statement to proceed;
64 CREATE TRIGGER show_trigger_data_trig
65 BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
66 FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
67 insert into trigger_test values(1,'insert', '("(1)")');
68 NOTICE: $_TD->{argc} = '2'
69 NOTICE: $_TD->{args} = ['23', 'skidoo']
70 NOTICE: $_TD->{event} = 'INSERT'
71 NOTICE: $_TD->{level} = 'ROW'
72 NOTICE: $_TD->{name} = 'show_trigger_data_trig'
73 NOTICE: $_TD->{new} = {'foo' => {'rfoo' => {'i' => '1'}}, 'i' => '1', 'v' => 'insert'}
74 NOTICE: $_TD->{relid} = 'bogus:12345'
75 NOTICE: $_TD->{relname} = 'trigger_test'
76 NOTICE: $_TD->{table_name} = 'trigger_test'
77 NOTICE: $_TD->{table_schema} = 'public'
78 NOTICE: $_TD->{when} = 'BEFORE'
79 update trigger_test set v = 'update' where i = 1;
80 NOTICE: $_TD->{argc} = '2'
81 NOTICE: $_TD->{args} = ['23', 'skidoo']
82 NOTICE: $_TD->{event} = 'UPDATE'
83 NOTICE: $_TD->{level} = 'ROW'
84 NOTICE: $_TD->{name} = 'show_trigger_data_trig'
85 NOTICE: $_TD->{new} = {'foo' => {'rfoo' => {'i' => '1'}}, 'i' => '1', 'v' => 'update'}
86 NOTICE: $_TD->{old} = {'foo' => {'rfoo' => {'i' => '1'}}, 'i' => '1', 'v' => 'insert'}
87 NOTICE: $_TD->{relid} = 'bogus:12345'
88 NOTICE: $_TD->{relname} = 'trigger_test'
89 NOTICE: $_TD->{table_name} = 'trigger_test'
90 NOTICE: $_TD->{table_schema} = 'public'
91 NOTICE: $_TD->{when} = 'BEFORE'
92 delete from trigger_test;
93 NOTICE: $_TD->{argc} = '2'
94 NOTICE: $_TD->{args} = ['23', 'skidoo']
95 NOTICE: $_TD->{event} = 'DELETE'
96 NOTICE: $_TD->{level} = 'ROW'
97 NOTICE: $_TD->{name} = 'show_trigger_data_trig'
98 NOTICE: $_TD->{old} = {'foo' => {'rfoo' => {'i' => '1'}}, 'i' => '1', 'v' => 'update'}
99 NOTICE: $_TD->{relid} = 'bogus:12345'
100 NOTICE: $_TD->{relname} = 'trigger_test'
101 NOTICE: $_TD->{table_name} = 'trigger_test'
102 NOTICE: $_TD->{table_schema} = 'public'
103 NOTICE: $_TD->{when} = 'BEFORE'
104 DROP TRIGGER show_trigger_data_trig on trigger_test;
105 CREATE TRIGGER show_trigger_data_trig_before
106 BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated
107 FOR EACH ROW EXECUTE PROCEDURE trigger_data();
108 CREATE TRIGGER show_trigger_data_trig_after
109 AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated
110 FOR EACH ROW EXECUTE PROCEDURE trigger_data();
111 insert into trigger_test_generated (i) values (1);
112 NOTICE: $_TD->{argc} = '0'
113 NOTICE: $_TD->{event} = 'INSERT'
114 NOTICE: $_TD->{level} = 'ROW'
115 NOTICE: $_TD->{name} = 'show_trigger_data_trig_before'
116 NOTICE: $_TD->{new} = {'i' => '1'}
117 NOTICE: $_TD->{relid} = 'bogus:12345'
118 NOTICE: $_TD->{relname} = 'trigger_test_generated'
119 NOTICE: $_TD->{table_name} = 'trigger_test_generated'
120 NOTICE: $_TD->{table_schema} = 'public'
121 NOTICE: $_TD->{when} = 'BEFORE'
122 NOTICE: $_TD->{argc} = '0'
123 NOTICE: $_TD->{event} = 'INSERT'
124 NOTICE: $_TD->{level} = 'ROW'
125 NOTICE: $_TD->{name} = 'show_trigger_data_trig_after'
126 NOTICE: $_TD->{new} = {'i' => '1', 'j' => '2'}
127 NOTICE: $_TD->{relid} = 'bogus:12345'
128 NOTICE: $_TD->{relname} = 'trigger_test_generated'
129 NOTICE: $_TD->{table_name} = 'trigger_test_generated'
130 NOTICE: $_TD->{table_schema} = 'public'
131 NOTICE: $_TD->{when} = 'AFTER'
132 update trigger_test_generated set i = 11 where i = 1;
133 NOTICE: $_TD->{argc} = '0'
134 NOTICE: $_TD->{event} = 'UPDATE'
135 NOTICE: $_TD->{level} = 'ROW'
136 NOTICE: $_TD->{name} = 'show_trigger_data_trig_before'
137 NOTICE: $_TD->{new} = {'i' => '11'}
138 NOTICE: $_TD->{old} = {'i' => '1', 'j' => '2'}
139 NOTICE: $_TD->{relid} = 'bogus:12345'
140 NOTICE: $_TD->{relname} = 'trigger_test_generated'
141 NOTICE: $_TD->{table_name} = 'trigger_test_generated'
142 NOTICE: $_TD->{table_schema} = 'public'
143 NOTICE: $_TD->{when} = 'BEFORE'
144 NOTICE: $_TD->{argc} = '0'
145 NOTICE: $_TD->{event} = 'UPDATE'
146 NOTICE: $_TD->{level} = 'ROW'
147 NOTICE: $_TD->{name} = 'show_trigger_data_trig_after'
148 NOTICE: $_TD->{new} = {'i' => '11', 'j' => '22'}
149 NOTICE: $_TD->{old} = {'i' => '1', 'j' => '2'}
150 NOTICE: $_TD->{relid} = 'bogus:12345'
151 NOTICE: $_TD->{relname} = 'trigger_test_generated'
152 NOTICE: $_TD->{table_name} = 'trigger_test_generated'
153 NOTICE: $_TD->{table_schema} = 'public'
154 NOTICE: $_TD->{when} = 'AFTER'
155 delete from trigger_test_generated;
156 NOTICE: $_TD->{argc} = '0'
157 NOTICE: $_TD->{event} = 'DELETE'
158 NOTICE: $_TD->{level} = 'ROW'
159 NOTICE: $_TD->{name} = 'show_trigger_data_trig_before'
160 NOTICE: $_TD->{old} = {'i' => '11', 'j' => '22'}
161 NOTICE: $_TD->{relid} = 'bogus:12345'
162 NOTICE: $_TD->{relname} = 'trigger_test_generated'
163 NOTICE: $_TD->{table_name} = 'trigger_test_generated'
164 NOTICE: $_TD->{table_schema} = 'public'
165 NOTICE: $_TD->{when} = 'BEFORE'
166 NOTICE: $_TD->{argc} = '0'
167 NOTICE: $_TD->{event} = 'DELETE'
168 NOTICE: $_TD->{level} = 'ROW'
169 NOTICE: $_TD->{name} = 'show_trigger_data_trig_after'
170 NOTICE: $_TD->{old} = {'i' => '11', 'j' => '22'}
171 NOTICE: $_TD->{relid} = 'bogus:12345'
172 NOTICE: $_TD->{relname} = 'trigger_test_generated'
173 NOTICE: $_TD->{table_name} = 'trigger_test_generated'
174 NOTICE: $_TD->{table_schema} = 'public'
175 NOTICE: $_TD->{when} = 'AFTER'
176 DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated;
177 DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated;
178 insert into trigger_test values(1,'insert', '("(1)")');
179 CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
180 CREATE TRIGGER show_trigger_data_trig
181 INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
182 FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
183 insert into trigger_test_view values(2,'insert', '("(2)")');
184 NOTICE: $_TD->{argc} = '2'
185 NOTICE: $_TD->{args} = ['24', 'skidoo view']
186 NOTICE: $_TD->{event} = 'INSERT'
187 NOTICE: $_TD->{level} = 'ROW'
188 NOTICE: $_TD->{name} = 'show_trigger_data_trig'
189 NOTICE: $_TD->{new} = {'foo' => {'rfoo' => {'i' => '2'}}, 'i' => '2', 'v' => 'insert'}
190 NOTICE: $_TD->{relid} = 'bogus:12345'
191 NOTICE: $_TD->{relname} = 'trigger_test_view'
192 NOTICE: $_TD->{table_name} = 'trigger_test_view'
193 NOTICE: $_TD->{table_schema} = 'public'
194 NOTICE: $_TD->{when} = 'INSTEAD OF'
195 update trigger_test_view set v = 'update', foo = '("(3)")' where i = 1;
196 NOTICE: $_TD->{argc} = '2'
197 NOTICE: $_TD->{args} = ['24', 'skidoo view']
198 NOTICE: $_TD->{event} = 'UPDATE'
199 NOTICE: $_TD->{level} = 'ROW'
200 NOTICE: $_TD->{name} = 'show_trigger_data_trig'
201 NOTICE: $_TD->{new} = {'foo' => {'rfoo' => {'i' => '3'}}, 'i' => '1', 'v' => 'update'}
202 NOTICE: $_TD->{old} = {'foo' => {'rfoo' => {'i' => '1'}}, 'i' => '1', 'v' => 'insert'}
203 NOTICE: $_TD->{relid} = 'bogus:12345'
204 NOTICE: $_TD->{relname} = 'trigger_test_view'
205 NOTICE: $_TD->{table_name} = 'trigger_test_view'
206 NOTICE: $_TD->{table_schema} = 'public'
207 NOTICE: $_TD->{when} = 'INSTEAD OF'
208 delete from trigger_test_view;
209 NOTICE: $_TD->{argc} = '2'
210 NOTICE: $_TD->{args} = ['24', 'skidoo view']
211 NOTICE: $_TD->{event} = 'DELETE'
212 NOTICE: $_TD->{level} = 'ROW'
213 NOTICE: $_TD->{name} = 'show_trigger_data_trig'
214 NOTICE: $_TD->{old} = {'foo' => {'rfoo' => {'i' => '1'}}, 'i' => '1', 'v' => 'insert'}
215 NOTICE: $_TD->{relid} = 'bogus:12345'
216 NOTICE: $_TD->{relname} = 'trigger_test_view'
217 NOTICE: $_TD->{table_name} = 'trigger_test_view'
218 NOTICE: $_TD->{table_schema} = 'public'
219 NOTICE: $_TD->{when} = 'INSTEAD OF'
220 DROP VIEW trigger_test_view;
221 delete from trigger_test;
222 DROP FUNCTION trigger_data();
223 CREATE OR REPLACE FUNCTION valid_id() RETURNS trigger AS $$
225 if (($_TD->{new}{i}>=100) || ($_TD->{new}{i}<=0))
227 return "SKIP"; # Skip INSERT/UPDATE command
229 elsif ($_TD->{new}{v} ne "immortal")
231 $_TD->{new}{v} .= "(modified by trigger)";
232 $_TD->{new}{foo}{rfoo}{i}++;
233 return "MODIFY"; # Modify tuple and proceed INSERT/UPDATE command
237 return; # Proceed INSERT/UPDATE command
240 CREATE TRIGGER "test_valid_id_trig" BEFORE INSERT OR UPDATE ON trigger_test
241 FOR EACH ROW EXECUTE PROCEDURE "valid_id"();
242 INSERT INTO trigger_test (i, v, foo) VALUES (1,'first line', '("(1)")');
243 INSERT INTO trigger_test (i, v, foo) VALUES (2,'second line', '("(2)")');
244 INSERT INTO trigger_test (i, v, foo) VALUES (3,'third line', '("(3)")');
245 INSERT INTO trigger_test (i, v, foo) VALUES (4,'immortal', '("(4)")');
246 INSERT INTO trigger_test (i, v) VALUES (101,'bad id');
247 SELECT * FROM trigger_test;
249 ---+----------------------------------+---------
250 1 | first line(modified by trigger) | ("(2)")
251 2 | second line(modified by trigger) | ("(3)")
252 3 | third line(modified by trigger) | ("(4)")
253 4 | immortal | ("(4)")
256 UPDATE trigger_test SET i = 5 where i=3;
257 UPDATE trigger_test SET i = 100 where i=1;
258 SELECT * FROM trigger_test;
260 ---+------------------------------------------------------+---------
261 1 | first line(modified by trigger) | ("(2)")
262 2 | second line(modified by trigger) | ("(3)")
263 4 | immortal | ("(4)")
264 5 | third line(modified by trigger)(modified by trigger) | ("(5)")
267 DROP TRIGGER "test_valid_id_trig" ON trigger_test;
268 CREATE OR REPLACE FUNCTION trigger_recurse() RETURNS trigger AS $$
271 if ($_TD->{new}{i} == 10000)
273 spi_exec_query("insert into trigger_test (i, v) values (20000, 'child');");
275 if ($_TD->{new}{i} != 10000)
277 die "recursive trigger modified: ". $_TD->{new}{i};
282 CREATE TRIGGER "test_trigger_recurse" BEFORE INSERT ON trigger_test
283 FOR EACH ROW EXECUTE PROCEDURE "trigger_recurse"();
284 INSERT INTO trigger_test (i, v) values (10000, 'top');
285 SELECT * FROM trigger_test;
287 -------+------------------------------------------------------+---------
288 1 | first line(modified by trigger) | ("(2)")
289 2 | second line(modified by trigger) | ("(3)")
290 4 | immortal | ("(4)")
291 5 | third line(modified by trigger)(modified by trigger) | ("(5)")
296 CREATE OR REPLACE FUNCTION immortal() RETURNS trigger AS $$
297 if ($_TD->{old}{v} eq $_TD->{args}[0])
299 return "SKIP"; # Skip DELETE command
303 return; # Proceed DELETE command
306 CREATE TRIGGER "immortal_trig" BEFORE DELETE ON trigger_test
307 FOR EACH ROW EXECUTE PROCEDURE immortal('immortal');
308 DELETE FROM trigger_test;
309 SELECT * FROM trigger_test;
311 ---+----------+---------
312 4 | immortal | ("(4)")
315 CREATE FUNCTION direct_trigger() RETURNS trigger AS $$
318 SELECT direct_trigger();
319 ERROR: trigger functions can only be called as triggers
320 CONTEXT: compilation of PL/Perl function "direct_trigger"
321 -- check that SQL run in trigger code can see transition tables
322 CREATE TABLE transition_table_test (id int, name text);
323 INSERT INTO transition_table_test VALUES (1, 'a');
324 CREATE FUNCTION transition_table_test_f() RETURNS trigger LANGUAGE plperl AS
326 my $cursor = spi_query("SELECT * FROM old_table");
327 my $row = spi_fetchrow($cursor);
328 defined($row) || die "expected a row";
329 elog(INFO, "old: " . $row->{id} . " -> " . $row->{name});
330 my $row = spi_fetchrow($cursor);
331 !defined($row) || die "expected no more rows";
333 my $cursor = spi_query("SELECT * FROM new_table");
334 my $row = spi_fetchrow($cursor);
335 defined($row) || die "expected a row";
336 elog(INFO, "new: " . $row->{id} . " -> " . $row->{name});
337 my $row = spi_fetchrow($cursor);
338 !defined($row) || die "expected no more rows";
342 CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test
343 REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
344 FOR EACH STATEMENT EXECUTE PROCEDURE transition_table_test_f();
345 UPDATE transition_table_test SET name = 'b';
348 DROP TABLE transition_table_test;
349 DROP FUNCTION transition_table_test_f();
350 -- test plperl command triggers
351 create or replace function perlsnitch() returns event_trigger language plperl as $$
352 elog(NOTICE, "perlsnitch: " . $_TD->{event} . " " . $_TD->{tag} . " ");
354 create event trigger perl_a_snitch on ddl_command_start
355 execute procedure perlsnitch();
356 create event trigger perl_b_snitch on ddl_command_end
357 execute procedure perlsnitch();
358 create or replace function foobar() returns int language sql as $$select 1;$$;
359 NOTICE: perlsnitch: ddl_command_start CREATE FUNCTION
360 NOTICE: perlsnitch: ddl_command_end CREATE FUNCTION
361 alter function foobar() cost 77;
362 NOTICE: perlsnitch: ddl_command_start ALTER FUNCTION
363 NOTICE: perlsnitch: ddl_command_end ALTER FUNCTION
364 drop function foobar();
365 NOTICE: perlsnitch: ddl_command_start DROP FUNCTION
366 NOTICE: perlsnitch: ddl_command_end DROP FUNCTION
368 NOTICE: perlsnitch: ddl_command_start CREATE TABLE
369 NOTICE: perlsnitch: ddl_command_end CREATE TABLE
371 NOTICE: perlsnitch: ddl_command_start DROP TABLE
372 NOTICE: perlsnitch: ddl_command_end DROP TABLE
373 drop event trigger perl_a_snitch;
374 drop event trigger perl_b_snitch;
375 -- dealing with generated columns
376 CREATE FUNCTION generated_test_func1() RETURNS trigger
379 $_TD->{new}{j} = 5; # not allowed
382 CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated
383 FOR EACH ROW EXECUTE PROCEDURE generated_test_func1();
384 TRUNCATE trigger_test_generated;
385 INSERT INTO trigger_test_generated (i) VALUES (1);
386 ERROR: cannot set generated column "j"
387 CONTEXT: PL/Perl function "generated_test_func1"
388 SELECT * FROM trigger_test_generated;