Augment test coverage in PL/Python, especially for error conditions.
authorPeter Eisentraut <peter_e@gmx.net>
Thu, 13 Aug 2009 20:50:05 +0000 (20:50 +0000)
committerPeter Eisentraut <peter_e@gmx.net>
Thu, 13 Aug 2009 20:50:05 +0000 (20:50 +0000)
14 files changed:
src/pl/plpython/expected/plpython_params.out
src/pl/plpython/expected/plpython_record.out
src/pl/plpython/expected/plpython_schema.out
src/pl/plpython/expected/plpython_setof.out
src/pl/plpython/expected/plpython_test.out
src/pl/plpython/expected/plpython_trigger.out
src/pl/plpython/plpython.c
src/pl/plpython/sql/plpython_params.sql
src/pl/plpython/sql/plpython_record.sql
src/pl/plpython/sql/plpython_schema.sql
src/pl/plpython/sql/plpython_setof.sql
src/pl/plpython/sql/plpython_spi.sql
src/pl/plpython/sql/plpython_test.sql
src/pl/plpython/sql/plpython_trigger.sql

index 2b609890c41d87acb9d8cefe88787c4663d2e306..3ec3396dc686518d3428fa9604467ba45c1b3f09 100644 (file)
@@ -43,6 +43,12 @@ SELECT test_param_names2(users) from users;
  {'lname': 'smith', 'username': 'slash', 'userid': 4, 'fname': 'rick'}
 (4 rows)
 
+SELECT test_param_names2(NULL);
+ test_param_names2 
+-------------------
+ None
+(1 row)
+
 SELECT test_param_names3(1);
  test_param_names3 
 -------------------
index d5cf0fa592bbe71495dd8267e3285000dd8ed4b7..9e4645d56cb49c3c771f4046bfd6e16ca0636419 100644 (file)
@@ -1,6 +1,14 @@
 --
 -- Test returning tuples
 --
+CREATE TABLE table_record (
+       first text,
+       second int4
+       ) ;
+CREATE TYPE type_record AS (
+       first text,
+       second int4
+       ) ;
 CREATE FUNCTION test_table_record_as(typ text, first text, second integer, retnull boolean) RETURNS table_record AS $$
 if retnull:
        return None
@@ -298,3 +306,26 @@ SELECT * FROM test_inout_params('test_in');
  test_in_inout
 (1 row)
 
+-- errors cases
+CREATE FUNCTION test_type_record_error1() RETURNS type_record AS $$
+    return { 'first': 'first' }
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_record_error1();
+ERROR:  key "second" not found in mapping
+HINT:  To return null in a column, add the value None to the mapping with the key named after the column.
+CONTEXT:  PL/Python function "test_type_record_error1"
+CREATE FUNCTION test_type_record_error2() RETURNS type_record AS $$
+    return [ 'first' ]
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_record_error2();
+ERROR:  length of returned sequence did not match number of columns in row
+CONTEXT:  PL/Python function "test_type_record_error2"
+CREATE FUNCTION test_type_record_error3() RETURNS type_record AS $$
+    class type_record: pass
+    type_record.first = 'first'
+    return type_record
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_record_error3();
+ERROR:  attribute "second" does not exist in Python object
+HINT:  To return null in a column, let the returned object have an attribute named after column with value None.
+CONTEXT:  PL/Python function "test_type_record_error3"
index 91930ceabf9f5cc9dc241f2499f7e010c9b60953..e94e7bbcf8818bc15a70c85da3a224b00d1c3cb9 100644 (file)
@@ -41,11 +41,3 @@ CREATE TABLE xsequences (
        sequence text not null
        ) ;
 CREATE INDEX xsequences_pid_idx ON xsequences(pid) ;
-CREATE TABLE table_record (
-       first text,
-       second int4
-       ) ;
-CREATE TYPE type_record AS (
-       first text,
-       second int4
-       ) ;
index 797c142aa6a4ff16ea8da0b6069b39ca103d88dd..03a97194c827e49614cf6ad3b13e20e22d73cb4f 100644 (file)
@@ -1,6 +1,13 @@
 --
 -- Test returning SETOF
 --
+CREATE FUNCTION test_setof_error() RETURNS SETOF text AS $$
+return 37
+$$ LANGUAGE plpythonu;
+SELECT test_setof_error();
+ERROR:  returned object cannot be iterated
+DETAIL:  PL/Python set-returning functions must return an iterable object.
+CONTEXT:  PL/Python function "test_setof_error"
 CREATE FUNCTION test_setof_as_list(count integer, content text) RETURNS SETOF text AS $$
 return [ content ]*count
 $$ LANGUAGE plpythonu;
index 5c12a7322685395969506ff1e9a827d3ec0aba58..5cce4e290f036131ba04af8995e6724adae5be17 100644 (file)
@@ -26,3 +26,27 @@ select argument_test_one(users, fname, lname) from users where lname = 'doe' ord
  willem doe => {fname: willem, lname: doe, userid: 3, username: w_doe}
 (3 rows)
 
+CREATE FUNCTION elog_test() RETURNS void
+AS $$
+plpy.debug('debug')
+plpy.log('log')
+plpy.info('info')
+plpy.info(37)
+plpy.info('info', 37, [1, 2, 3])
+plpy.notice('notice')
+plpy.warning('warning')
+plpy.error('error')
+$$ LANGUAGE plpythonu;
+SELECT elog_test();
+INFO:  ('info',)
+CONTEXT:  PL/Python function "elog_test"
+INFO:  (37,)
+CONTEXT:  PL/Python function "elog_test"
+INFO:  ('info', 37, [1, 2, 3])
+CONTEXT:  PL/Python function "elog_test"
+NOTICE:  ('notice',)
+CONTEXT:  PL/Python function "elog_test"
+WARNING:  ('warning',)
+CONTEXT:  PL/Python function "elog_test"
+ERROR:  ('error',)
+CONTEXT:  PL/Python function "elog_test"
index ac23b9c64853e80b709513f828c223b64e5d99be..75914047cea17ff0d44ef4a80da48a656cb9aee0 100644 (file)
@@ -81,17 +81,43 @@ for key in skeys:
 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"
@@ -105,14 +131,54 @@ 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',)
+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"
@@ -126,14 +192,74 @@ 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',)
+NOTICE:  ('TD[name] => show_trigger_data_trig_after',)
 CONTEXT:  PL/Python function "trigger_data"
 NOTICE:  ('TD[new] => None',)
 CONTEXT:  PL/Python function "trigger_data"
@@ -145,9 +271,154 @@ 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 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)
+
index f8f3144e8e9583eca894b25b0b05cf3cf1f57a8c..07da41b01c41668a474d7dc021d592a7410c359d 100644 (file)
@@ -538,7 +538,7 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
                        platt = PyList_GetItem(plkeys, i);
                        if (!PyString_Check(platt))
                                ereport(ERROR,
-                                               (errmsg("name of TD[\"new\"] attribute at ordinal position %d is not a string", i)));
+                                               (errmsg("TD[\"new\"] dictionary key at ordinal position %d is not a string", i)));
                        attn = SPI_fnumber(tupdesc, PyString_AsString(platt));
                        if (attn == SPI_ERROR_NOATTRIBUTE)
                                ereport(ERROR,
index f8b610b5056cbed13eafa342437892ffc2b97c35..d97e0b85490a5983a163ffc26b87e1cc3162142f 100644 (file)
@@ -31,4 +31,5 @@ $$ LANGUAGE plpythonu;
 SELECT test_param_names0(2,7);
 SELECT test_param_names1(1,'text');
 SELECT test_param_names2(users) from users;
+SELECT test_param_names2(NULL);
 SELECT test_param_names3(1);
index 5bd810210750c6c58c03ca2751bfc5788b157d49..5a41565a9de2f51d83aaf2f2d23d1d63a6298fc7 100644 (file)
@@ -2,6 +2,17 @@
 -- Test returning tuples
 --
 
+CREATE TABLE table_record (
+       first text,
+       second int4
+       ) ;
+
+CREATE TYPE type_record AS (
+       first text,
+       second int4
+       ) ;
+
+
 CREATE FUNCTION test_table_record_as(typ text, first text, second integer, retnull boolean) RETURNS table_record AS $$
 if retnull:
        return None
@@ -102,3 +113,28 @@ SELECT * FROM test_in_out_params('test_in');
 -- this doesn't work yet :-(
 SELECT * FROM test_in_out_params_multi('test_in');
 SELECT * FROM test_inout_params('test_in');
+
+
+-- errors cases
+
+CREATE FUNCTION test_type_record_error1() RETURNS type_record AS $$
+    return { 'first': 'first' }
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_record_error1();
+
+
+CREATE FUNCTION test_type_record_error2() RETURNS type_record AS $$
+    return [ 'first' ]
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_record_error2();
+
+
+CREATE FUNCTION test_type_record_error3() RETURNS type_record AS $$
+    class type_record: pass
+    type_record.first = 'first'
+    return type_record
+$$ LANGUAGE plpythonu;
+
+SELECT * FROM test_type_record_error3();
index 92681c16629f1af81343002b76c100089668e997..1cb84d620e621d94b30e1485a203b5e264566302 100644 (file)
@@ -38,13 +38,3 @@ CREATE TABLE xsequences (
        sequence text not null
        ) ;
 CREATE INDEX xsequences_pid_idx ON xsequences(pid) ;
-
-CREATE TABLE table_record (
-       first text,
-       second int4
-       ) ;
-
-CREATE TYPE type_record AS (
-       first text,
-       second int4
-       ) ;
index 881d90222fef1ca9f92527953fa31a509d4e25fc..e036d569f2050d8b04eb425bb2082d8d2627cb9e 100644 (file)
@@ -2,6 +2,13 @@
 -- Test returning SETOF
 --
 
+CREATE FUNCTION test_setof_error() RETURNS SETOF text AS $$
+return 37
+$$ LANGUAGE plpythonu;
+
+SELECT test_setof_error();
+
+
 CREATE FUNCTION test_setof_as_list(count integer, content text) RETURNS SETOF text AS $$
 return [ content ]*count
 $$ LANGUAGE plpythonu;
index 866a0d5febd017995310c7b4d15fadc7503ec593..c663298525747fc7a08830427aa0633271232c09 100644 (file)
@@ -1,5 +1,4 @@
 
-
 -- nested calls
 --
 
index 38e236f146367d8654311ed69ab293a79c28c8df..d45299420f7047c94b91cb61b4746759a278220e 100644 (file)
@@ -19,3 +19,18 @@ return words'
        LANGUAGE plpythonu;
 
 select argument_test_one(users, fname, lname) from users where lname = 'doe' order by 1;
+
+
+CREATE FUNCTION elog_test() RETURNS void
+AS $$
+plpy.debug('debug')
+plpy.log('log')
+plpy.info('info')
+plpy.info(37)
+plpy.info('info', 37, [1, 2, 3])
+plpy.notice('notice')
+plpy.warning('warning')
+plpy.error('error')
+$$ LANGUAGE plpythonu;
+
+SELECT elog_test();
index b042ae926e77ae35c2488f74a7d243d120b8c5d1..ce1a737a844f6b635616cd76963b0ecb5726d167 100644 (file)
@@ -82,14 +82,174 @@ 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');
 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;