summaryrefslogtreecommitdiffstats
path: root/src/pl/plpython/expected/plpython_trigger.out
blob: dd1ca32fa49538dcd0fec18ee45a94d9864f7c33 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
-- these triggers are dedicated to HPHC of RI who
-- decided that my kid's name was william not willem, and
-- vigorously resisted all efforts at correction.  they have
-- since gone bankrupt...
CREATE FUNCTION users_insert() returns trigger
	AS
'if TD["new"]["fname"] == None or TD["new"]["lname"] == None:
	return "SKIP"
if TD["new"]["username"] == None:
	TD["new"]["username"] = TD["new"]["fname"][:1] + "_" + TD["new"]["lname"]
	rv = "MODIFY"
else:
	rv = None
if TD["new"]["fname"] == "william":
	TD["new"]["fname"] = TD["args"][0]
	rv = "MODIFY"
return rv'
	LANGUAGE plpython3u;
CREATE FUNCTION users_update() returns trigger
	AS
'if TD["event"] == "UPDATE":
	if TD["old"]["fname"] != TD["new"]["fname"] and TD["old"]["fname"] == TD["args"][0]:
		return "SKIP"
return None'
	LANGUAGE plpython3u;
CREATE FUNCTION users_delete() RETURNS trigger
	AS
'if TD["old"]["fname"] == TD["args"][0]:
	return "SKIP"
return None'
	LANGUAGE plpython3u;
CREATE TRIGGER users_insert_trig BEFORE INSERT ON users FOR EACH ROW
	EXECUTE PROCEDURE users_insert ('willem');
CREATE TRIGGER users_update_trig BEFORE UPDATE ON users FOR EACH ROW
	EXECUTE PROCEDURE users_update ('willem');
CREATE TRIGGER users_delete_trig BEFORE DELETE ON users FOR EACH ROW
	EXECUTE PROCEDURE users_delete ('willem');
-- quick peek at the table
--
SELECT * FROM users;
 fname  | lname | username | userid 
--------+-------+----------+--------
 jane   | doe   | j_doe    |      1
 john   | doe   | johnd    |      2
 willem | doe   | w_doe    |      3
 rick   | smith | slash    |      4
(4 rows)

-- should fail
--
UPDATE users SET fname = 'william' WHERE fname = 'willem';
-- should modify william to willem and create username
--
INSERT INTO users (fname, lname) VALUES ('william', 'smith');
INSERT INTO users (fname, lname, username) VALUES ('charles', 'darwin', 'beagle');
SELECT * FROM users;
  fname  | lname  | username | userid 
---------+--------+----------+--------
 jane    | doe    | j_doe    |      1
 john    | doe    | johnd    |      2
 willem  | doe    | w_doe    |      3
 rick    | smith  | slash    |      4
 willem  | smith  | w_smith  |      5
 charles | darwin | beagle   |      6
(6 rows)

-- dump trigger data
CREATE TABLE trigger_test
	(i int, v text );
CREATE TABLE trigger_test_generated (
	i int,
        j int GENERATED ALWAYS AS (i * 2) STORED
);
CREATE FUNCTION trigger_data() RETURNS trigger LANGUAGE plpython3u AS $$

if 'relid' in TD:
	TD['relid'] = "bogus:12345"

skeys = list(TD.keys())
skeys.sort()
for key in skeys:
    val = TD[key]
    if not isinstance(val, dict):
        plpy.notice("TD[" + key + "] => " + str(val))
    else:
        # print dicts the hard way because otherwise the order is implementation-dependent
        valkeys = list(val.keys())
        valkeys.sort()
        plpy.notice("TD[" + key + "] => " + '{' + ', '.join([repr(k) + ': ' + repr(val[k]) for k in valkeys]) + '}')

return None

$$;
CREATE TRIGGER show_trigger_data_trig_before
BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
CREATE TRIGGER show_trigger_data_trig_after
AFTER INSERT OR UPDATE OR DELETE ON trigger_test
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
CREATE TRIGGER show_trigger_data_trig_stmt
BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(23,'skidoo');
insert into trigger_test values(1,'insert');
NOTICE:  TD[args] => ['23', 'skidoo']
NOTICE:  TD[event] => INSERT
NOTICE:  TD[level] => STATEMENT
NOTICE:  TD[name] => show_trigger_data_trig_stmt
NOTICE:  TD[new] => None
NOTICE:  TD[old] => None
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => BEFORE
NOTICE:  TD[args] => ['23', 'skidoo']
NOTICE:  TD[event] => INSERT
NOTICE:  TD[level] => ROW
NOTICE:  TD[name] => show_trigger_data_trig_before
NOTICE:  TD[new] => {'i': 1, 'v': 'insert'}
NOTICE:  TD[old] => None
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => BEFORE
NOTICE:  TD[args] => ['23', 'skidoo']
NOTICE:  TD[event] => INSERT
NOTICE:  TD[level] => ROW
NOTICE:  TD[name] => show_trigger_data_trig_after
NOTICE:  TD[new] => {'i': 1, 'v': 'insert'}
NOTICE:  TD[old] => None
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => AFTER
update trigger_test set v = 'update' where i = 1;
NOTICE:  TD[args] => ['23', 'skidoo']
NOTICE:  TD[event] => UPDATE
NOTICE:  TD[level] => STATEMENT
NOTICE:  TD[name] => show_trigger_data_trig_stmt
NOTICE:  TD[new] => None
NOTICE:  TD[old] => None
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => BEFORE
NOTICE:  TD[args] => ['23', 'skidoo']
NOTICE:  TD[event] => UPDATE
NOTICE:  TD[level] => ROW
NOTICE:  TD[name] => show_trigger_data_trig_before
NOTICE:  TD[new] => {'i': 1, 'v': 'update'}
NOTICE:  TD[old] => {'i': 1, 'v': 'insert'}
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => BEFORE
NOTICE:  TD[args] => ['23', 'skidoo']
NOTICE:  TD[event] => UPDATE
NOTICE:  TD[level] => ROW
NOTICE:  TD[name] => show_trigger_data_trig_after
NOTICE:  TD[new] => {'i': 1, 'v': 'update'}
NOTICE:  TD[old] => {'i': 1, 'v': 'insert'}
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => AFTER
delete from trigger_test;
NOTICE:  TD[args] => ['23', 'skidoo']
NOTICE:  TD[event] => DELETE
NOTICE:  TD[level] => STATEMENT
NOTICE:  TD[name] => show_trigger_data_trig_stmt
NOTICE:  TD[new] => None
NOTICE:  TD[old] => None
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => BEFORE
NOTICE:  TD[args] => ['23', 'skidoo']
NOTICE:  TD[event] => DELETE
NOTICE:  TD[level] => ROW
NOTICE:  TD[name] => show_trigger_data_trig_before
NOTICE:  TD[new] => None
NOTICE:  TD[old] => {'i': 1, 'v': 'update'}
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => BEFORE
NOTICE:  TD[args] => ['23', 'skidoo']
NOTICE:  TD[event] => DELETE
NOTICE:  TD[level] => ROW
NOTICE:  TD[name] => show_trigger_data_trig_after
NOTICE:  TD[new] => None
NOTICE:  TD[old] => {'i': 1, 'v': 'update'}
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => AFTER
truncate table trigger_test;
NOTICE:  TD[args] => ['23', 'skidoo']
NOTICE:  TD[event] => TRUNCATE
NOTICE:  TD[level] => STATEMENT
NOTICE:  TD[name] => show_trigger_data_trig_stmt
NOTICE:  TD[new] => None
NOTICE:  TD[old] => None
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => BEFORE
DROP TRIGGER show_trigger_data_trig_stmt on trigger_test;
DROP TRIGGER show_trigger_data_trig_before on trigger_test;
DROP TRIGGER show_trigger_data_trig_after on trigger_test;
CREATE TRIGGER show_trigger_data_trig_before
BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated
FOR EACH ROW EXECUTE PROCEDURE trigger_data();
CREATE TRIGGER show_trigger_data_trig_after
AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated
FOR EACH ROW EXECUTE PROCEDURE trigger_data();
insert into trigger_test_generated (i) values (1);
NOTICE:  TD[args] => None
NOTICE:  TD[event] => INSERT
NOTICE:  TD[level] => ROW
NOTICE:  TD[name] => show_trigger_data_trig_before
NOTICE:  TD[new] => {'i': 1}
NOTICE:  TD[old] => None
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test_generated
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => BEFORE
NOTICE:  TD[args] => None
NOTICE:  TD[event] => INSERT
NOTICE:  TD[level] => ROW
NOTICE:  TD[name] => show_trigger_data_trig_after
NOTICE:  TD[new] => {'i': 1, 'j': 2}
NOTICE:  TD[old] => None
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test_generated
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => AFTER
update trigger_test_generated set i = 11 where i = 1;
NOTICE:  TD[args] => None
NOTICE:  TD[event] => UPDATE
NOTICE:  TD[level] => ROW
NOTICE:  TD[name] => show_trigger_data_trig_before
NOTICE:  TD[new] => {'i': 11}
NOTICE:  TD[old] => {'i': 1, 'j': 2}
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test_generated
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => BEFORE
NOTICE:  TD[args] => None
NOTICE:  TD[event] => UPDATE
NOTICE:  TD[level] => ROW
NOTICE:  TD[name] => show_trigger_data_trig_after
NOTICE:  TD[new] => {'i': 11, 'j': 22}
NOTICE:  TD[old] => {'i': 1, 'j': 2}
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test_generated
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => AFTER
delete from trigger_test_generated;
NOTICE:  TD[args] => None
NOTICE:  TD[event] => DELETE
NOTICE:  TD[level] => ROW
NOTICE:  TD[name] => show_trigger_data_trig_before
NOTICE:  TD[new] => None
NOTICE:  TD[old] => {'i': 11, 'j': 22}
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test_generated
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => BEFORE
NOTICE:  TD[args] => None
NOTICE:  TD[event] => DELETE
NOTICE:  TD[level] => ROW
NOTICE:  TD[name] => show_trigger_data_trig_after
NOTICE:  TD[new] => None
NOTICE:  TD[old] => {'i': 11, 'j': 22}
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test_generated
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => AFTER
DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated;
DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated;
insert into trigger_test values(1,'insert');
CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
CREATE TRIGGER show_trigger_data_trig
INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
insert into trigger_test_view values(2,'insert');
NOTICE:  TD[args] => ['24', 'skidoo view']
NOTICE:  TD[event] => INSERT
NOTICE:  TD[level] => ROW
NOTICE:  TD[name] => show_trigger_data_trig
NOTICE:  TD[new] => {'i': 2, 'v': 'insert'}
NOTICE:  TD[old] => None
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test_view
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => INSTEAD OF
update trigger_test_view set v = 'update' where i = 1;
NOTICE:  TD[args] => ['24', 'skidoo view']
NOTICE:  TD[event] => UPDATE
NOTICE:  TD[level] => ROW
NOTICE:  TD[name] => show_trigger_data_trig
NOTICE:  TD[new] => {'i': 1, 'v': 'update'}
NOTICE:  TD[old] => {'i': 1, 'v': 'insert'}
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test_view
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => INSTEAD OF
delete from trigger_test_view;
NOTICE:  TD[args] => ['24', 'skidoo view']
NOTICE:  TD[event] => DELETE
NOTICE:  TD[level] => ROW
NOTICE:  TD[name] => show_trigger_data_trig
NOTICE:  TD[new] => None
NOTICE:  TD[old] => {'i': 1, 'v': 'insert'}
NOTICE:  TD[relid] => bogus:12345
NOTICE:  TD[table_name] => trigger_test_view
NOTICE:  TD[table_schema] => public
NOTICE:  TD[when] => INSTEAD OF
DROP FUNCTION trigger_data() CASCADE;
NOTICE:  drop cascades to trigger show_trigger_data_trig on view trigger_test_view
DROP VIEW trigger_test_view;
delete from trigger_test;
--
-- trigger error handling
--
INSERT INTO trigger_test VALUES (0, 'zero');
-- returning non-string from trigger function
CREATE FUNCTION stupid1() RETURNS trigger
AS $$
    return 37
$$ LANGUAGE plpython3u;
CREATE TRIGGER stupid_trigger1
BEFORE INSERT ON trigger_test
FOR EACH ROW EXECUTE PROCEDURE stupid1();
INSERT INTO trigger_test VALUES (1, 'one');
ERROR:  unexpected return value from trigger procedure
DETAIL:  Expected None or a string.
CONTEXT:  PL/Python function "stupid1"
DROP TRIGGER stupid_trigger1 ON trigger_test;
-- returning MODIFY from DELETE trigger
CREATE FUNCTION stupid2() RETURNS trigger
AS $$
    return "MODIFY"
$$ LANGUAGE plpython3u;
CREATE TRIGGER stupid_trigger2
BEFORE DELETE ON trigger_test
FOR EACH ROW EXECUTE PROCEDURE stupid2();
DELETE FROM trigger_test WHERE i = 0;
WARNING:  PL/Python trigger function returned "MODIFY" in a DELETE trigger -- ignored
DROP TRIGGER stupid_trigger2 ON trigger_test;
INSERT INTO trigger_test VALUES (0, 'zero');
-- returning unrecognized string from trigger function
CREATE FUNCTION stupid3() RETURNS trigger
AS $$
    return "foo"
$$ LANGUAGE plpython3u;
CREATE TRIGGER stupid_trigger3
BEFORE UPDATE ON trigger_test
FOR EACH ROW EXECUTE PROCEDURE stupid3();
UPDATE trigger_test SET v = 'null' WHERE i = 0;
ERROR:  unexpected return value from trigger procedure
DETAIL:  Expected None, "OK", "SKIP", or "MODIFY".
CONTEXT:  PL/Python function "stupid3"
DROP TRIGGER stupid_trigger3 ON trigger_test;
-- Unicode variant
CREATE FUNCTION stupid3u() RETURNS trigger
AS $$
    return "foo"
$$ LANGUAGE plpython3u;
CREATE TRIGGER stupid_trigger3
BEFORE UPDATE ON trigger_test
FOR EACH ROW EXECUTE PROCEDURE stupid3u();
UPDATE trigger_test SET v = 'null' WHERE i = 0;
ERROR:  unexpected return value from trigger procedure
DETAIL:  Expected None, "OK", "SKIP", or "MODIFY".
CONTEXT:  PL/Python function "stupid3u"
DROP TRIGGER stupid_trigger3 ON trigger_test;
-- deleting the TD dictionary
CREATE FUNCTION stupid4() RETURNS trigger
AS $$
    del TD["new"]
    return "MODIFY";
$$ LANGUAGE plpython3u;
CREATE TRIGGER stupid_trigger4
BEFORE UPDATE ON trigger_test
FOR EACH ROW EXECUTE PROCEDURE stupid4();
UPDATE trigger_test SET v = 'null' WHERE i = 0;
ERROR:  TD["new"] deleted, cannot modify row
CONTEXT:  while modifying trigger row
PL/Python function "stupid4"
DROP TRIGGER stupid_trigger4 ON trigger_test;
-- TD not a dictionary
CREATE FUNCTION stupid5() RETURNS trigger
AS $$
    TD["new"] = ['foo', 'bar']
    return "MODIFY";
$$ LANGUAGE plpython3u;
CREATE TRIGGER stupid_trigger5
BEFORE UPDATE ON trigger_test
FOR EACH ROW EXECUTE PROCEDURE stupid5();
UPDATE trigger_test SET v = 'null' WHERE i = 0;
ERROR:  TD["new"] is not a dictionary
CONTEXT:  while modifying trigger row
PL/Python function "stupid5"
DROP TRIGGER stupid_trigger5 ON trigger_test;
-- TD not having string keys
CREATE FUNCTION stupid6() RETURNS trigger
AS $$
    TD["new"] = {1: 'foo', 2: 'bar'}
    return "MODIFY";
$$ LANGUAGE plpython3u;
CREATE TRIGGER stupid_trigger6
BEFORE UPDATE ON trigger_test
FOR EACH ROW EXECUTE PROCEDURE stupid6();
UPDATE trigger_test SET v = 'null' WHERE i = 0;
ERROR:  TD["new"] dictionary key at ordinal position 0 is not a string
CONTEXT:  while modifying trigger row
PL/Python function "stupid6"
DROP TRIGGER stupid_trigger6 ON trigger_test;
-- TD keys not corresponding to row columns
CREATE FUNCTION stupid7() RETURNS trigger
AS $$
    TD["new"] = {'v': 'foo', 'a': 'bar'}
    return "MODIFY";
$$ LANGUAGE plpython3u;
CREATE TRIGGER stupid_trigger7
BEFORE UPDATE ON trigger_test
FOR EACH ROW EXECUTE PROCEDURE stupid7();
UPDATE trigger_test SET v = 'null' WHERE i = 0;
ERROR:  key "a" found in TD["new"] does not exist as a column in the triggering row
CONTEXT:  while modifying trigger row
PL/Python function "stupid7"
DROP TRIGGER stupid_trigger7 ON trigger_test;
-- Unicode variant
CREATE FUNCTION stupid7u() RETURNS trigger
AS $$
    TD["new"] = {'v': 'foo', 'a': 'bar'}
    return "MODIFY"
$$ LANGUAGE plpython3u;
CREATE TRIGGER stupid_trigger7
BEFORE UPDATE ON trigger_test
FOR EACH ROW EXECUTE PROCEDURE stupid7u();
UPDATE trigger_test SET v = 'null' WHERE i = 0;
ERROR:  key "a" found in TD["new"] does not exist as a column in the triggering row
CONTEXT:  while modifying trigger row
PL/Python function "stupid7u"
DROP TRIGGER stupid_trigger7 ON trigger_test;
-- calling a trigger function directly
SELECT stupid7();
ERROR:  trigger functions can only be called as triggers
--
-- Null values
--
SELECT * FROM trigger_test;
 i |  v   
---+------
 0 | zero
(1 row)

CREATE FUNCTION test_null() RETURNS trigger
AS $$
    TD["new"]['v'] = None
    return "MODIFY"
$$ LANGUAGE plpython3u;
CREATE TRIGGER test_null_trigger
BEFORE UPDATE ON trigger_test
FOR EACH ROW EXECUTE PROCEDURE test_null();
UPDATE trigger_test SET v = 'null' WHERE i = 0;
DROP TRIGGER test_null_trigger ON trigger_test;
SELECT * FROM trigger_test;
 i | v 
---+---
 0 | 
(1 row)

--
-- Test that triggers honor typmod when assigning to tuple fields,
-- as per an early 9.0 bug report
--
SET DateStyle = 'ISO';
CREATE FUNCTION set_modif_time() RETURNS trigger AS $$
    TD['new']['modif_time'] = '2010-10-13 21:57:28.930486'
    return 'MODIFY'
$$ LANGUAGE plpython3u;
CREATE TABLE pb (a TEXT, modif_time TIMESTAMP(0) WITHOUT TIME ZONE);
CREATE TRIGGER set_modif_time BEFORE UPDATE ON pb
  FOR EACH ROW EXECUTE PROCEDURE set_modif_time();
INSERT INTO pb VALUES ('a', '2010-10-09 21:57:33.930486');
SELECT * FROM pb;
 a |     modif_time      
---+---------------------
 a | 2010-10-09 21:57:34
(1 row)

UPDATE pb SET a = 'b';
SELECT * FROM pb;
 a |     modif_time      
---+---------------------
 b | 2010-10-13 21:57:29
(1 row)

-- triggers for tables with composite types
CREATE TABLE comp1 (i integer, j boolean);
CREATE TYPE comp2 AS (k integer, l boolean);
CREATE TABLE composite_trigger_test (f1 comp1, f2 comp2);
CREATE FUNCTION composite_trigger_f() RETURNS trigger AS $$
    TD['new']['f1'] = (3, False)
    TD['new']['f2'] = {'k': 7, 'l': 'yes', 'ignored': 10}
    return 'MODIFY'
$$ LANGUAGE plpython3u;
CREATE TRIGGER composite_trigger BEFORE INSERT ON composite_trigger_test
  FOR EACH ROW EXECUTE PROCEDURE composite_trigger_f();
INSERT INTO composite_trigger_test VALUES (NULL, NULL);
SELECT * FROM composite_trigger_test;
  f1   |  f2   
-------+-------
 (3,f) | (7,t)
(1 row)

-- triggers with composite type columns (bug #6559)
CREATE TABLE composite_trigger_noop_test (f1 comp1, f2 comp2);
CREATE FUNCTION composite_trigger_noop_f() RETURNS trigger AS $$
    return 'MODIFY'
$$ LANGUAGE plpython3u;
CREATE TRIGGER composite_trigger_noop BEFORE INSERT ON composite_trigger_noop_test
  FOR EACH ROW EXECUTE PROCEDURE composite_trigger_noop_f();
INSERT INTO composite_trigger_noop_test VALUES (NULL, NULL);
INSERT INTO composite_trigger_noop_test VALUES (ROW(1, 'f'), NULL);
INSERT INTO composite_trigger_noop_test VALUES (ROW(NULL, 't'), ROW(1, 'f'));
SELECT * FROM composite_trigger_noop_test;
  f1   |  f2   
-------+-------
       | 
 (1,f) | 
 (,t)  | (1,f)
(3 rows)

-- nested composite types
CREATE TYPE comp3 AS (c1 comp1, c2 comp2, m integer);
CREATE TABLE composite_trigger_nested_test(c comp3);
CREATE FUNCTION composite_trigger_nested_f() RETURNS trigger AS $$
    return 'MODIFY'
$$ LANGUAGE plpython3u;
CREATE TRIGGER composite_trigger_nested BEFORE INSERT ON composite_trigger_nested_test
  FOR EACH ROW EXECUTE PROCEDURE composite_trigger_nested_f();
INSERT INTO composite_trigger_nested_test VALUES (NULL);
INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(1, 'f'), NULL, 3));
INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(NULL, 't'), ROW(1, 'f'), NULL));
SELECT * FROM composite_trigger_nested_test;
         c         
-------------------
 
 ("(1,f)",,3)
 ("(,t)","(1,f)",)
(3 rows)

-- check that using a function as a trigger over two tables works correctly
CREATE FUNCTION trig1234() RETURNS trigger LANGUAGE plpython3u AS $$
    TD["new"]["data"] = '1234'
    return 'MODIFY'
$$;
CREATE TABLE a(data text);
CREATE TABLE b(data int); -- different type conversion
CREATE TRIGGER a_t BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE trig1234();
CREATE TRIGGER b_t BEFORE INSERT ON b FOR EACH ROW EXECUTE PROCEDURE trig1234();
INSERT INTO a DEFAULT VALUES;
SELECT * FROM a;
 data 
------
 1234
(1 row)

DROP TABLE a;
INSERT INTO b DEFAULT VALUES;
SELECT * FROM b;
 data 
------
 1234
(1 row)

-- check that SQL run in trigger code can see transition tables
CREATE TABLE transition_table_test (id int, name text);
INSERT INTO transition_table_test VALUES (1, 'a');
CREATE FUNCTION transition_table_test_f() RETURNS trigger LANGUAGE plpython3u AS
$$
    rv = plpy.execute("SELECT * FROM old_table")
    assert(rv.nrows() == 1)
    plpy.info("old: " + str(rv[0]["id"]) + " -> " + rv[0]["name"])
    rv = plpy.execute("SELECT * FROM new_table")
    assert(rv.nrows() == 1)
    plpy.info("new: " + str(rv[0]["id"]) + " -> " + rv[0]["name"])
    return None
$$;
CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test
  REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
  FOR EACH STATEMENT EXECUTE PROCEDURE transition_table_test_f();
UPDATE transition_table_test SET name = 'b';
INFO:  old: 1 -> a
INFO:  new: 1 -> b
DROP TABLE transition_table_test;
DROP FUNCTION transition_table_test_f();
-- dealing with generated columns
CREATE FUNCTION generated_test_func1() RETURNS trigger
LANGUAGE plpython3u
AS $$
TD['new']['j'] = 5  # not allowed
return 'MODIFY'
$$;
CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated
FOR EACH ROW EXECUTE PROCEDURE generated_test_func1();
TRUNCATE trigger_test_generated;
INSERT INTO trigger_test_generated (i) VALUES (1);
ERROR:  cannot set generated column "j"
CONTEXT:  while modifying trigger row
PL/Python function "generated_test_func1"
SELECT * FROM trigger_test_generated;
 i | j 
---+---
(0 rows)