-- 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 plpythonu; 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 plpythonu; CREATE FUNCTION users_delete() RETURNS trigger AS 'if TD["old"]["fname"] == TD["args"][0]: return "SKIP" return None' LANGUAGE plpythonu; 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 FUNCTION trigger_data() RETURNS trigger LANGUAGE plpythonu AS $$ if 'relid' in TD: TD['relid'] = "bogus:12345" skeys = list(TD.keys()) skeys.sort() for key in skeys: val = TD[key] plpy.notice("TD[" + key + "] => " + str(val)) 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'] CONTEXT: PL/Python function "trigger_data" NOTICE: TD[event] => INSERT CONTEXT: PL/Python function "trigger_data" NOTICE: TD[level] => STATEMENT CONTEXT: PL/Python function "trigger_data" NOTICE: TD[name] => show_trigger_data_trig_stmt CONTEXT: PL/Python function "trigger_data" NOTICE: TD[new] => None CONTEXT: PL/Python function "trigger_data" NOTICE: TD[old] => None CONTEXT: PL/Python function "trigger_data" NOTICE: TD[relid] => bogus:12345 CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_name] => trigger_test CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_schema] => public CONTEXT: PL/Python function "trigger_data" NOTICE: TD[when] => BEFORE CONTEXT: PL/Python function "trigger_data" NOTICE: TD[args] => ['23', 'skidoo'] CONTEXT: PL/Python function "trigger_data" NOTICE: TD[event] => INSERT CONTEXT: PL/Python function "trigger_data" NOTICE: TD[level] => ROW CONTEXT: PL/Python function "trigger_data" NOTICE: TD[name] => show_trigger_data_trig_before CONTEXT: PL/Python function "trigger_data" NOTICE: TD[new] => {'i': 1, 'v': 'insert'} CONTEXT: PL/Python function "trigger_data" NOTICE: TD[old] => None CONTEXT: PL/Python function "trigger_data" NOTICE: TD[relid] => bogus:12345 CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_name] => trigger_test CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_schema] => public CONTEXT: PL/Python function "trigger_data" NOTICE: TD[when] => BEFORE CONTEXT: PL/Python function "trigger_data" NOTICE: TD[args] => ['23', 'skidoo'] CONTEXT: PL/Python function "trigger_data" NOTICE: TD[event] => INSERT CONTEXT: PL/Python function "trigger_data" NOTICE: TD[level] => ROW CONTEXT: PL/Python function "trigger_data" NOTICE: TD[name] => show_trigger_data_trig_after CONTEXT: PL/Python function "trigger_data" NOTICE: TD[new] => {'i': 1, 'v': 'insert'} CONTEXT: PL/Python function "trigger_data" NOTICE: TD[old] => None CONTEXT: PL/Python function "trigger_data" NOTICE: TD[relid] => bogus:12345 CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_name] => trigger_test CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_schema] => public CONTEXT: PL/Python function "trigger_data" NOTICE: TD[when] => AFTER CONTEXT: PL/Python function "trigger_data" update trigger_test set v = 'update' where i = 1; NOTICE: TD[args] => ['23', 'skidoo'] CONTEXT: PL/Python function "trigger_data" NOTICE: TD[event] => UPDATE CONTEXT: PL/Python function "trigger_data" NOTICE: TD[level] => STATEMENT CONTEXT: PL/Python function "trigger_data" NOTICE: TD[name] => show_trigger_data_trig_stmt CONTEXT: PL/Python function "trigger_data" NOTICE: TD[new] => None CONTEXT: PL/Python function "trigger_data" NOTICE: TD[old] => None CONTEXT: PL/Python function "trigger_data" NOTICE: TD[relid] => bogus:12345 CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_name] => trigger_test CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_schema] => public CONTEXT: PL/Python function "trigger_data" NOTICE: TD[when] => BEFORE CONTEXT: PL/Python function "trigger_data" NOTICE: TD[args] => ['23', 'skidoo'] CONTEXT: PL/Python function "trigger_data" NOTICE: TD[event] => UPDATE CONTEXT: PL/Python function "trigger_data" NOTICE: TD[level] => ROW CONTEXT: PL/Python function "trigger_data" NOTICE: TD[name] => show_trigger_data_trig_before CONTEXT: PL/Python function "trigger_data" NOTICE: TD[new] => {'i': 1, 'v': 'update'} CONTEXT: PL/Python function "trigger_data" NOTICE: TD[old] => {'i': 1, 'v': 'insert'} CONTEXT: PL/Python function "trigger_data" NOTICE: TD[relid] => bogus:12345 CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_name] => trigger_test CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_schema] => public CONTEXT: PL/Python function "trigger_data" NOTICE: TD[when] => BEFORE CONTEXT: PL/Python function "trigger_data" NOTICE: TD[args] => ['23', 'skidoo'] CONTEXT: PL/Python function "trigger_data" NOTICE: TD[event] => UPDATE CONTEXT: PL/Python function "trigger_data" NOTICE: TD[level] => ROW CONTEXT: PL/Python function "trigger_data" NOTICE: TD[name] => show_trigger_data_trig_after CONTEXT: PL/Python function "trigger_data" NOTICE: TD[new] => {'i': 1, 'v': 'update'} CONTEXT: PL/Python function "trigger_data" NOTICE: TD[old] => {'i': 1, 'v': 'insert'} CONTEXT: PL/Python function "trigger_data" NOTICE: TD[relid] => bogus:12345 CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_name] => trigger_test CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_schema] => public CONTEXT: PL/Python function "trigger_data" NOTICE: TD[when] => AFTER CONTEXT: PL/Python function "trigger_data" delete from trigger_test; NOTICE: TD[args] => ['23', 'skidoo'] CONTEXT: PL/Python function "trigger_data" NOTICE: TD[event] => DELETE CONTEXT: PL/Python function "trigger_data" NOTICE: TD[level] => STATEMENT CONTEXT: PL/Python function "trigger_data" NOTICE: TD[name] => show_trigger_data_trig_stmt CONTEXT: PL/Python function "trigger_data" NOTICE: TD[new] => None CONTEXT: PL/Python function "trigger_data" NOTICE: TD[old] => None CONTEXT: PL/Python function "trigger_data" NOTICE: TD[relid] => bogus:12345 CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_name] => trigger_test CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_schema] => public CONTEXT: PL/Python function "trigger_data" NOTICE: TD[when] => BEFORE CONTEXT: PL/Python function "trigger_data" NOTICE: TD[args] => ['23', 'skidoo'] CONTEXT: PL/Python function "trigger_data" NOTICE: TD[event] => DELETE CONTEXT: PL/Python function "trigger_data" NOTICE: TD[level] => ROW CONTEXT: PL/Python function "trigger_data" NOTICE: TD[name] => show_trigger_data_trig_before CONTEXT: PL/Python function "trigger_data" NOTICE: TD[new] => None CONTEXT: PL/Python function "trigger_data" NOTICE: TD[old] => {'i': 1, 'v': 'update'} CONTEXT: PL/Python function "trigger_data" NOTICE: TD[relid] => bogus:12345 CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_name] => trigger_test CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_schema] => public CONTEXT: PL/Python function "trigger_data" NOTICE: TD[when] => BEFORE CONTEXT: PL/Python function "trigger_data" NOTICE: TD[args] => ['23', 'skidoo'] CONTEXT: PL/Python function "trigger_data" NOTICE: TD[event] => DELETE CONTEXT: PL/Python function "trigger_data" NOTICE: TD[level] => ROW CONTEXT: PL/Python function "trigger_data" NOTICE: TD[name] => show_trigger_data_trig_after CONTEXT: PL/Python function "trigger_data" NOTICE: TD[new] => None CONTEXT: PL/Python function "trigger_data" NOTICE: TD[old] => {'i': 1, 'v': 'update'} CONTEXT: PL/Python function "trigger_data" NOTICE: TD[relid] => bogus:12345 CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_name] => trigger_test CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_schema] => public CONTEXT: PL/Python function "trigger_data" NOTICE: TD[when] => AFTER CONTEXT: PL/Python function "trigger_data" truncate table trigger_test; NOTICE: TD[args] => ['23', 'skidoo'] CONTEXT: PL/Python function "trigger_data" NOTICE: TD[event] => TRUNCATE CONTEXT: PL/Python function "trigger_data" NOTICE: TD[level] => STATEMENT CONTEXT: PL/Python function "trigger_data" NOTICE: TD[name] => show_trigger_data_trig_stmt CONTEXT: PL/Python function "trigger_data" NOTICE: TD[new] => None CONTEXT: PL/Python function "trigger_data" NOTICE: TD[old] => None CONTEXT: PL/Python function "trigger_data" NOTICE: TD[relid] => bogus:12345 CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_name] => trigger_test CONTEXT: PL/Python function "trigger_data" NOTICE: TD[table_schema] => public CONTEXT: PL/Python function "trigger_data" NOTICE: TD[when] => BEFORE CONTEXT: PL/Python function "trigger_data" DROP FUNCTION trigger_data() CASCADE; NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to trigger show_trigger_data_trig_before on table trigger_test drop cascades to trigger show_trigger_data_trig_after on table trigger_test drop cascades to trigger show_trigger_data_trig_stmt on table 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 plpythonu; 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 plpythonu; 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 CONTEXT: PL/Python function "stupid2" 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 plpythonu; 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 u"foo" $$ LANGUAGE plpythonu; 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 plpythonu; 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 plpythonu; 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 plpythonu; 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"] = {'a': 'foo', 'b': 'bar'} return "MODIFY"; $$ LANGUAGE plpythonu; 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"] = {u'a': 'foo', u'b': 'bar'} return "MODIFY" $$ LANGUAGE plpythonu; 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 plpythonu; 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)