return None  
 
 $$;
-CREATE TRIGGER show_trigger_data_trig 
+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',)
+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"
 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',)
+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"
 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',)
+NOTICE:  ('TD[name] => show_trigger_data_trig_after',)
 CONTEXT:  PL/Python function "trigger_data"
 NOTICE:  ('TD[new] => None',)
 CONTEXT:  PL/Python function "trigger_data"
 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 TRIGGER show_trigger_data_trig on trigger_test;
-      
-DROP 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;
+-- 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:  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:  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:  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:  PL/Python function "stupid7"
+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)
+
 
 
 $$;
 
-CREATE TRIGGER show_trigger_data_trig 
+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');
 update trigger_test set v = 'update' where i = 1;
 delete from trigger_test;
-      
-DROP TRIGGER show_trigger_data_trig on trigger_test;
-      
-DROP FUNCTION trigger_data();
+truncate table trigger_test;
+
+DROP FUNCTION trigger_data() CASCADE;
+
+
+--
+-- 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');
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+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;
+
+DROP TRIGGER stupid_trigger7 ON trigger_test;
+
+
+-- calling a trigger function directly
+
+SELECT stupid7();
+
+
+--
+-- Null values
+--
+
+SELECT * FROM trigger_test;
+
+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;