diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile index 82909e40f615..ed337528e69d 100644 --- a/src/pl/plpython/Makefile +++ b/src/pl/plpython/Makefile @@ -94,30 +94,29 @@ else REGRESS_OPTS += --load-extension=plpython3u endif -REGRESS = plpython_record plpython_returns -# REGRESS = \ -# plpython_schema \ -# plpython_populate \ -# plpython_test \ -# plpython_do \ -# plpython_global \ -# plpython_import \ -# plpython_spi \ -# plpython_newline \ -# plpython_void \ -# plpython_params \ -# plpython_setof \ -# plpython_record \ -# plpython_trigger \ -# plpython_types \ -# plpython_error \ -# plpython_unicode \ -# plpython_quote \ -# plpython_composite \ -# plpython_subtransaction \ -# plpython_returns \ -# plpython_gpdb \ -# plpython_drop +REGRESS = \ + plpython_schema \ + plpython_populate \ + plpython_test \ + plpython_do \ + plpython_global \ + plpython_import \ + plpython_spi \ + plpython_newline \ + plpython_void \ + plpython_params \ + plpython_setof \ + plpython_record \ + plpython_trigger \ + plpython_types \ + plpython_error \ + plpython_unicode \ + plpython_quote \ + plpython_composite \ + plpython_subtransaction \ + plpython_returns \ + plpython_gpdb \ + plpython_drop REGRESS_PLPYTHON3_MANGLE := $(REGRESS) @@ -160,6 +159,9 @@ REGRESS := $(foreach test,$(REGRESS),$(if $(filter $(test),$(REGRESS_PLPYTHON3_M .PHONY: pgregress-python3-mangle pgregress-python3-mangle: $(MKDIR_P) sql/python3 expected/python3 results/python3 + # for python and python3 types and returns test are not the same. + cp expected/plpython_types_3.out expected/python3/plpython_types.out + cp expected/plpython_returns_3.out expected/python3/plpython_returns.out for file in $(patsubst %,$(srcdir)/sql/%.sql,$(REGRESS_PLPYTHON3_MANGLE)) $(patsubst %,$(srcdir)/expected/%*.out,$(REGRESS_PLPYTHON3_MANGLE)); do \ sed -e 's/except \([[:alpha:]][[:alpha:].]*\), *\([[:alpha:]][[:alpha:]]*\):/except \1 as \2:/g' \ -e "s///g" \ @@ -176,7 +178,6 @@ pgregress-python3-mangle: -e "s/EXTENSION plpython2u/EXTENSION plpython3u/Ig" \ $$file >`echo $$file | sed 's,^.*/\([^/][^/]*/\)\([^/][^/]*\)$$,\1python3/\2,'` || exit; \ done - cp expected/plpython_types_3.out expected/python3/plpython_types.out check installcheck: pgregress-python3-mangle diff --git a/src/pl/plpython/expected/plpython_returns.out b/src/pl/plpython/expected/plpython_returns.out index 07a5391c26e4..aaa13e51c615 100644 --- a/src/pl/plpython/expected/plpython_returns.out +++ b/src/pl/plpython/expected/plpython_returns.out @@ -359,7 +359,7 @@ SELECT test_return_bool('{None: None}') FROM gp_single_row; -- From Python Object with bad str() function SELECT test_return_bool( - E'1; exec("class Foo:\\n def __str__(self): return None"); y = Foo()' + E'1; exec("class Foo:\\n def __str__(self): return None", globals()); y = Foo()' ); test_return_bool ------------------ @@ -367,7 +367,7 @@ SELECT test_return_bool( (1 row) SELECT * FROM test_return_bool( - E'1; exec("class Foo:\\n def __str__(self): return None"); y = Foo()' + E'1; exec("class Foo:\\n def __str__(self): return None", globals()); y = Foo()' ); test_return_bool ------------------ @@ -541,7 +541,7 @@ FROM gp_single_row; -- From Python Object with str() function SELECT test_return_text( - E'1; exec("class Foo:\\n def __str__(self): return \\"test\\""); y = Foo()' + E'1; exec("class Foo:\\n def __str__(self): return \\"test\\"", globals()); y = Foo()' ); test_return_text ------------------ @@ -549,7 +549,7 @@ SELECT test_return_text( (1 row) SELECT * FROM test_return_text( - E'1; exec("class Foo:\\n def __str__(self): return \\"test\\""); y = Foo()' + E'1; exec("class Foo:\\n def __str__(self): return \\"test\\"", globals()); y = Foo()' ); test_return_text ------------------ @@ -557,7 +557,7 @@ SELECT * FROM test_return_text( (1 row) SELECT test_return_text( - E'1; exec("class Foo:\\n def __str__(self): return \\"test\\""); y = Foo()' + E'1; exec("class Foo:\\n def __str__(self): return \\"test\\"", globals()); y = Foo()' ) FROM gp_single_row; test_return_text ------------------ @@ -575,11 +575,11 @@ SELECT * FROM test_return_text(E'"test\\0"'); ERROR: could not convert Python object into cstring: Python string representation appears to contain null bytes -- [ERROR] From Python Object with bad str() function SELECT test_return_text( - E'1; exec("class Foo:\\n def __str__(self): return None"); y = Foo()' + E'1; exec("class Foo:\\n def __str__(self): return None", globals()); y = Foo()' ); ERROR: could not create string representation of Python object (plpy_elog.c:106) SELECT * FROM test_return_text( - E'1; exec("class Foo:\\n def __str__(self): return None"); y = Foo()' + E'1; exec("class Foo:\\n def __str__(self): return None", globals()); y = Foo()' ); ERROR: could not create string representation of Python object (plpy_elog.c:106) -- @@ -750,7 +750,7 @@ FROM gp_single_row; -- From Python Object with str() function SELECT test_return_bytea( - E'1; exec("class Foo:\\n def __str__(self): return \\"test\\""); y = Foo()' + E'1; exec("class Foo:\\n def __str__(self): return \\"test\\"", globals()); y = Foo()' ); test_return_bytea ------------------- @@ -758,7 +758,7 @@ SELECT test_return_bytea( (1 row) SELECT * FROM test_return_bytea( - E'1; exec("class Foo:\\n def __str__(self): return \\"test\\""); y = Foo()' + E'1; exec("class Foo:\\n def __str__(self): return \\"test\\"", globals()); y = Foo()' ); test_return_bytea ------------------- @@ -766,7 +766,7 @@ SELECT * FROM test_return_bytea( (1 row) SELECT test_return_bytea( - E'1; exec("class Foo:\\n def __str__(self): return \\"test\\""); y = Foo()' + E'1; exec("class Foo:\\n def __str__(self): return \\"test\\"", globals()); y = Foo()' ) FROM gp_single_row; test_return_bytea ------------------- @@ -796,15 +796,15 @@ FROM gp_single_row; -- Error conditions -- [ERROR] From Python Object with bad str() function SELECT test_return_bytea( - E'1; exec("class Foo:\\n def __str__(self): return None"); y = Foo()' + E'1; exec("class Foo:\\n def __str__(self): return None", globals()); y = Foo()' ); ERROR: could not create bytes representation of Python object (plpy_elog.c:106) SELECT * FROM test_return_bytea( - E'1; exec("class Foo:\\n def __str__(self): return None"); y = Foo()' + E'1; exec("class Foo:\\n def __str__(self): return None", globals()); y = Foo()' ); ERROR: could not create bytes representation of Python object (plpy_elog.c:106) SELECT test_return_bytea( - E'1; exec("class Foo:\\n def __str__(self): return None"); y = Foo()' + E'1; exec("class Foo:\\n def __str__(self): return None", globals()); y = Foo()' ) FROM gp_single_row; ERROR: could not create bytes representation of Python object (plpy_elog.c:106) (seg0 slice1 127.0.0.1:25432 pid=24014) (plpy_elog.c:106) -- @@ -2482,7 +2482,7 @@ FROM gp_single_row; -- From Python Object with str() function SELECT test_return_out_text( - E'1; exec("class Foo:\\n def __str__(self): return \\"test\\""); y = Foo()' + E'1; exec("class Foo:\\n def __str__(self): return \\"test\\"", globals()); y = Foo()' ); test_return_out_text ---------------------- @@ -2490,7 +2490,7 @@ SELECT test_return_out_text( (1 row) SELECT * FROM test_return_out_text( - E'1; exec("class Foo:\\n def __str__(self): return \\"test\\""); y = Foo()' + E'1; exec("class Foo:\\n def __str__(self): return \\"test\\"", globals()); y = Foo()' ); test_return_out_text ---------------------- @@ -2498,7 +2498,7 @@ SELECT * FROM test_return_out_text( (1 row) SELECT test_return_out_text( - E'1; exec("class Foo:\\n def __str__(self): return \\"test\\""); y = Foo()' + E'1; exec("class Foo:\\n def __str__(self): return \\"test\\"", globals()); y = Foo()' ) FROM gp_single_row; test_return_out_text ---------------------- @@ -2516,11 +2516,11 @@ SELECT * FROM test_return_out_text(E'"test\\0"'); ERROR: could not convert Python object into cstring: Python string representation appears to contain null bytes -- [ERROR] From Python Object with bad str() function SELECT test_return_out_text( - E'1; exec("class Foo:\\n def __str__(self): return None"); y = Foo()' + E'1; exec("class Foo:\\n def __str__(self): return None", globals()); y = Foo()' ); ERROR: could not create string representation of Python object (plpy_elog.c:106) SELECT * FROM test_return_out_text( - E'1; exec("class Foo:\\n def __str__(self): return None"); y = Foo()' + E'1; exec("class Foo:\\n def __str__(self): return None", globals()); y = Foo()' ); ERROR: could not create string representation of Python object (plpy_elog.c:106) -- diff --git a/src/pl/plpython/expected/plpython_returns_3.out b/src/pl/plpython/expected/plpython_returns_3.out new file mode 100644 index 000000000000..e77015900a49 --- /dev/null +++ b/src/pl/plpython/expected/plpython_returns_3.out @@ -0,0 +1,3064 @@ +-- Tests Datatypes for Input Parameters and return values +-- +-- PL/Python is written such that certain Postgres Types map to +-- certain Python Types, and some return types likewise have +-- special cast functions. +-- +-- The purpose of this test is to verify that all of the datatype +-- mapping code is working correctly, as well as the code that +-- manages OUT parameters, RETURNS TABLE, and other variants +-- on how parameters are returned from PL/Python functions. +-- +-- Every function (both single value and setof) is run both from +-- the SELECT clause and the FROM clause since these are different +-- execution paths. +-- +-- Greenplum has another distinction regarding whether the function +-- is run on the master or the segment. So we additionally run +-- every function as a SELECT clause function over a table with +-- a single row. +-- +-- Because Greenplum is slow at reporting errors from segments +-- we only execute against gp_single_row for the statements that +-- should succeed, or where we expect different results (executing SQL) +\set VERBOSITY terse +CREATE TABLE gp_single_row(a int) distributed by (a); +insert into gp_single_row values(1); +-- Set up test functions first. +CREATE FUNCTION test_return_void(s text) +RETURNS void AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +CREATE FUNCTION test_return_bool(s text) +RETURNS bool AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +CREATE FUNCTION test_return_text(s text) +RETURNS text AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +CREATE FUNCTION test_return_bytea(s text) +RETURNS bytea AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +CREATE FUNCTION test_return_circle(s text) +RETURNS circle AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +CREATE FUNCTION test_return_array_int(s text) +RETURNS int[] AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +CREATE FUNCTION test_return_array_text(s text) +RETURNS text[] AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +CREATE FUNCTION test_return_setof_void(s text) +RETURNS setof void AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +CREATE FUNCTION test_return_setof_bool(s text) +RETURNS setof bool AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +CREATE FUNCTION test_return_setof_text(s text) +RETURNS setof text AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +CREATE FUNCTION test_return_setof_bytea(s text) +RETURNS setof bytea AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +CREATE FUNCTION test_return_setof_circle(s text) +RETURNS setof circle AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +-- RETURNS record types +CREATE FUNCTION test_return_table_record(s text) +RETURNS table_record AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +CREATE FUNCTION test_return_type_record(s text) +RETURNS type_record AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +CREATE FUNCTION test_return_setof_table_record(s text) +RETURNS table_record AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +CREATE FUNCTION test_return_setof_type_record(s text) +RETURNS type_record AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +-- RETURNS with OUT Parameters +CREATE FUNCTION test_return_out_text(s text, OUT text) +AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +CREATE FUNCTION test_return_out_setof_text(s text, OUT text) +RETURNS setof text AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +CREATE FUNCTION test_return_out_record(s text, OUT first text, OUT second int4) +AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +CREATE FUNCTION test_return_out_setof_record(s text, OUT first text, OUT second int4) +RETURNS setof record AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +-- RETURNS TABLE +CREATE FUNCTION test_return_table(s text) RETURNS TABLE(first text, second int4) +AS $$ + exec('y = ' + s, globals()) + return y +$$ language plpythonu; +-- ============================================= +-- TEST 1: RETURN VALUE TESTING - Scalar values +-- ============================================= +-- +-- There are 4 "special" return datatypes: Void, Bool, Text, Bytea +-- all other datatypes are handled by converting the python object to +-- a cstring and then calling the typinput function for the type. +-- +-- Case 1: Return void +-- +-- The only return that is valid for a void returns is Py_None +-- From Python None +SELECT test_return_void('None'); + test_return_void +------------------ + +(1 row) + +SELECT * FROM test_return_void('None'); + test_return_void +------------------ + +(1 row) + +SELECT test_return_void('None') +FROM gp_single_row; + test_return_void +------------------ + +(1 row) + +-- Error conditions +-- [ERROR] From Python empty string +SELECT test_return_void('""'); +ERROR: PL/Python function with return type "void" did not return None +SELECT * FROM test_return_void('""'); +ERROR: PL/Python function with return type "void" did not return None +-- [ERROR] From Python empty list +SELECT test_return_void('[]'); +ERROR: PL/Python function with return type "void" did not return None +SELECT * FROM test_return_void('[]'); +ERROR: PL/Python function with return type "void" did not return None +-- [ERROR] From Python empty dict +SELECT test_return_void('{}'); +ERROR: PL/Python function with return type "void" did not return None +SELECT * FROM test_return_void('{}'); +ERROR: PL/Python function with return type "void" did not return None +-- +-- Case 2: Return Bool +-- +-- Any Object in Python can be resolved as a boolean value. +-- The conversion is based on the Python notion of True and False. +-- So the string 'f' is True, empty strings and containers are False +-- +-- From Python None +SELECT test_return_bool('None'); + test_return_bool +------------------ + +(1 row) + +SELECT * FROM test_return_bool('None'); + test_return_bool +------------------ + +(1 row) + +SELECT test_return_bool('None') FROM gp_single_row; + test_return_bool +------------------ + +(1 row) + +-- From Python bool +SELECT test_return_bool('True'); + test_return_bool +------------------ + t +(1 row) + +SELECT * FROM test_return_bool('True'); + test_return_bool +------------------ + t +(1 row) + +SELECT test_return_bool('True') FROM gp_single_row; + test_return_bool +------------------ + t +(1 row) + +SELECT test_return_bool('False'); + test_return_bool +------------------ + f +(1 row) + +SELECT * FROM test_return_bool('False'); + test_return_bool +------------------ + f +(1 row) + +SELECT test_return_bool('False') FROM gp_single_row; + test_return_bool +------------------ + f +(1 row) + +-- From Python empty string +SELECT test_return_bool('""'); + test_return_bool +------------------ + f +(1 row) + +SELECT * FROM test_return_bool('""'); + test_return_bool +------------------ + f +(1 row) + +SELECT test_return_bool('""') FROM gp_single_row; + test_return_bool +------------------ + f +(1 row) + +-- From Python string +SELECT test_return_bool('"value"'); + test_return_bool +------------------ + t +(1 row) + +SELECT * FROM test_return_bool('"value"'); + test_return_bool +------------------ + t +(1 row) + +SELECT test_return_bool('"value"') FROM gp_single_row; + test_return_bool +------------------ + t +(1 row) + +-- From Python empty list +SELECT test_return_bool('[]'); + test_return_bool +------------------ + f +(1 row) + +SELECT * FROM test_return_bool('[]'); + test_return_bool +------------------ + f +(1 row) + +SELECT test_return_bool('[]') FROM gp_single_row; + test_return_bool +------------------ + f +(1 row) + +-- From Python non-empty list +SELECT test_return_bool('[False]'); + test_return_bool +------------------ + t +(1 row) + +SELECT * FROM test_return_bool('[False]'); + test_return_bool +------------------ + t +(1 row) + +SELECT test_return_bool('[False]') FROM gp_single_row; + test_return_bool +------------------ + t +(1 row) + +-- From Python empty dict +SELECT test_return_bool('{}'); + test_return_bool +------------------ + f +(1 row) + +SELECT * FROM test_return_bool('{}'); + test_return_bool +------------------ + f +(1 row) + +SELECT test_return_bool('{}') FROM gp_single_row; + test_return_bool +------------------ + f +(1 row) + +-- From Python non-empty dict +SELECT test_return_bool('{None: None}'); + test_return_bool +------------------ + t +(1 row) + +SELECT * FROM test_return_bool('{None: None}'); + test_return_bool +------------------ + t +(1 row) + +SELECT test_return_bool('{None: None}') FROM gp_single_row; + test_return_bool +------------------ + t +(1 row) + +-- From Python Object with bad str() function +SELECT test_return_bool( + E'1; exec("class Foo:\\n def __str__(self): return None", globals()); y = Foo()' +); + test_return_bool +------------------ + t +(1 row) + +SELECT * FROM test_return_bool( + E'1; exec("class Foo:\\n def __str__(self): return None", globals()); y = Foo()' +); + test_return_bool +------------------ + t +(1 row) + +-- Error conditions +-- None +-- +-- Case 3: Return Text +-- +-- From Python None +SELECT test_return_text('None'); + test_return_text +------------------ + +(1 row) + +SELECT * FROM test_return_text('None'); + test_return_text +------------------ + +(1 row) + +SELECT test_return_text('None') +FROM gp_single_row; + test_return_text +------------------ + +(1 row) + +-- From Python bool +SELECT test_return_text('True'); + test_return_text +------------------ + True +(1 row) + +SELECT * FROM test_return_text('True'); + test_return_text +------------------ + True +(1 row) + +SELECT test_return_text('True') +FROM gp_single_row; + test_return_text +------------------ + True +(1 row) + +-- From Python empty string +SELECT test_return_text('""'); + test_return_text +------------------ + +(1 row) + +SELECT * FROM test_return_text('""'); + test_return_text +------------------ + +(1 row) + +SELECT test_return_text('""') +FROM gp_single_row; + test_return_text +------------------ + +(1 row) + +-- From Python string +SELECT test_return_text('"value"'); + test_return_text +------------------ + value +(1 row) + +SELECT * FROM test_return_text('"value"'); + test_return_text +------------------ + value +(1 row) + +SELECT test_return_text('"value"') +FROM gp_single_row; + test_return_text +------------------ + value +(1 row) + +-- From Python empty list +SELECT test_return_text('[]'); + test_return_text +------------------ + [] +(1 row) + +SELECT * FROM test_return_text('[]'); + test_return_text +------------------ + [] +(1 row) + +SELECT test_return_text('[]') +FROM gp_single_row; + test_return_text +------------------ + [] +(1 row) + +-- From Python non-empty list +SELECT test_return_text('[False]'); + test_return_text +------------------ + [False] +(1 row) + +SELECT * FROM test_return_text('[False]'); + test_return_text +------------------ + [False] +(1 row) + +SELECT test_return_text('[False]') +FROM gp_single_row; + test_return_text +------------------ + [False] +(1 row) + +-- From Python empty dict +SELECT test_return_text('{}'); + test_return_text +------------------ + {} +(1 row) + +SELECT * FROM test_return_text('{}'); + test_return_text +------------------ + {} +(1 row) + +SELECT test_return_text('{}') +FROM gp_single_row; + test_return_text +------------------ + {} +(1 row) + +-- From Python non-empty dict +SELECT test_return_text('{None: None}'); + test_return_text +------------------ + {None: None} +(1 row) + +SELECT * FROM test_return_text('{None: None}'); + test_return_text +------------------ + {None: None} +(1 row) + +SELECT test_return_text('{None: None}') +FROM gp_single_row; + test_return_text +------------------ + {None: None} +(1 row) + +-- From Python Object with str() function +SELECT test_return_text( + E'1; exec("class Foo:\\n def __str__(self): return \\"test\\"", globals()); y = Foo()' +); + test_return_text +------------------ + test +(1 row) + +SELECT * FROM test_return_text( + E'1; exec("class Foo:\\n def __str__(self): return \\"test\\"", globals()); y = Foo()' +); + test_return_text +------------------ + test +(1 row) + +SELECT test_return_text( + E'1; exec("class Foo:\\n def __str__(self): return \\"test\\"", globals()); y = Foo()' +) FROM gp_single_row; + test_return_text +------------------ + test +(1 row) + +-- Error conditions: +-- There are two ways that a "text" return can fail: +-- a) There isn't a __str__ function for the returned object +-- b) The returned string contains null characters (valid in Python) +-- [ERROR] From Python String with null characters +SELECT test_return_text(E'"test\\0"'); + test_return_text +------------------ + test +(1 row) + +SELECT * FROM test_return_text(E'"test\\0"'); + test_return_text +------------------ + test +(1 row) + +-- [ERROR] From Python Object with bad str() function +SELECT test_return_text( + E'1; exec("class Foo:\\n def __str__(self): return None", globals()); y = Foo()' +); +ERROR: could not create string representation of Python object (plpy_elog.c:106) +SELECT * FROM test_return_text( + E'1; exec("class Foo:\\n def __str__(self): return None", globals()); y = Foo()' +); +ERROR: could not create string representation of Python object (plpy_elog.c:106) +-- +-- Case 4: Return Bytea +-- +-- Bytea is like text, except that it allows return strings to contain null +-- characters +-- +-- From Python None +SELECT test_return_bytea('None'); + test_return_bytea +------------------- + +(1 row) + +SELECT * FROM test_return_bytea('None'); + test_return_bytea +------------------- + +(1 row) + +SELECT test_return_bytea('None') +FROM gp_single_row; + test_return_bytea +------------------- + +(1 row) + +-- From Python bool +SELECT test_return_bytea('True'); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +SELECT * FROM test_return_bytea('True'); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +SELECT test_return_bytea('True') +FROM gp_single_row; +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +-- From Python empty string +SELECT test_return_bytea('""'); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +SELECT * FROM test_return_bytea('""'); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +SELECT test_return_bytea('""') +FROM gp_single_row; +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +-- From Python string +SELECT test_return_bytea('"value"'); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +SELECT * FROM test_return_bytea('"value"'); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +SELECT test_return_bytea('"value"') +FROM gp_single_row; +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +-- From Python empty list +SELECT test_return_bytea('[]'); + test_return_bytea +------------------- + \x +(1 row) + +SELECT * FROM test_return_bytea('[]'); + test_return_bytea +------------------- + \x +(1 row) + +SELECT test_return_bytea('[]') +FROM gp_single_row; + test_return_bytea +------------------- + \x +(1 row) + +-- From Python non-empty list +SELECT test_return_bytea('[False]'); + test_return_bytea +------------------- + \x00 +(1 row) + +SELECT * FROM test_return_bytea('[False]'); + test_return_bytea +------------------- + \x00 +(1 row) + +SELECT test_return_bytea('[False]') +FROM gp_single_row; + test_return_bytea +------------------- + \x00 +(1 row) + +-- From Python empty dict +SELECT test_return_bytea('{}'); + test_return_bytea +------------------- + \x +(1 row) + +SELECT * FROM test_return_bytea('{}'); + test_return_bytea +------------------- + \x +(1 row) + +SELECT test_return_bytea('{}') +FROM gp_single_row; + test_return_bytea +------------------- + \x +(1 row) + +-- From Python non-empty dict +SELECT test_return_bytea('{None: None}'); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +SELECT * FROM test_return_bytea('{None: None}'); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +SELECT test_return_bytea('{None: None}') +FROM gp_single_row; +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +-- From Python Object with str() function +SELECT test_return_bytea( + E'1; exec("class Foo:\\n def __str__(self): return \\"test\\"", globals()); y = Foo()' +); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +SELECT * FROM test_return_bytea( + E'1; exec("class Foo:\\n def __str__(self): return \\"test\\"", globals()); y = Foo()' +); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +SELECT test_return_bytea( + E'1; exec("class Foo:\\n def __str__(self): return \\"test\\"", globals()); y = Foo()' +) FROM gp_single_row; +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +-- From Python String with null characters +SELECT test_return_bytea(E'"test\\0"'); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +SELECT * FROM test_return_bytea(E'"test\\0"'); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +SELECT test_return_bytea(E'"test\\0"') +FROM gp_single_row; +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +-- Error conditions +-- [ERROR] From Python Object with bad str() function +SELECT test_return_bytea( + E'1; exec("class Foo:\\n def __str__(self): return None", globals()); y = Foo()' +); +ERROR: could not create bytes representation of Python object (plpy_elog.c:106) +SELECT * FROM test_return_bytea( + E'1; exec("class Foo:\\n def __str__(self): return None", globals()); y = Foo()' +); +ERROR: could not create bytes representation of Python object (plpy_elog.c:106) +SELECT test_return_bytea( + E'1; exec("class Foo:\\n def __str__(self): return None", globals()); y = Foo()' +) FROM gp_single_row; +ERROR: could not create bytes representation of Python object (plpy_elog.c:106) (seg0 slice1 127.0.0.1:25432 pid=24014) (plpy_elog.c:106) +-- +-- Case 5: Return Circle +-- +-- We use Circle as a representative type that uses the typinput function +-- This equally applies to other types such as "int2", "int4", "money", etc. +-- +-- From Python None +SELECT test_return_circle('None'); + test_return_circle +-------------------- + +(1 row) + +SELECT * FROM test_return_circle('None'); + test_return_circle +-------------------- + +(1 row) + +SELECT test_return_circle('None') +FROM gp_single_row; + test_return_circle +-------------------- + +(1 row) + +-- From Python String +SELECT test_return_circle('"((3,1),4)"'); + test_return_circle +-------------------- + <(3,1),4> +(1 row) + +SELECT * FROM test_return_circle('"((3,1),4)"'); + test_return_circle +-------------------- + <(3,1),4> +(1 row) + +SELECT test_return_circle('"((3,1),4)"') +FROM gp_single_row; + test_return_circle +-------------------- + <(3,1),4> +(1 row) + +-- From Python List +SELECT test_return_circle('((3,1),4)'); + test_return_circle +-------------------- + <(3,1),4> +(1 row) + +SELECT * FROM test_return_circle('((3,1),4)'); + test_return_circle +-------------------- + <(3,1),4> +(1 row) + +SELECT test_return_circle('((3,1),4)') +FROM gp_single_row; + test_return_circle +-------------------- + <(3,1),4> +(1 row) + +-- Questionable error conditions +-- [ERROR] From Python Tuple +SELECT test_return_circle('[[3,1],4]'); +ERROR: invalid input syntax for type circle: "[[3, 1], 4]" +SELECT * FROM test_return_circle('[[3,1],4]'); +ERROR: invalid input syntax for type circle: "[[3, 1], 4]" +-- Error conditions +-- [ERROR] From Python bool +SELECT test_return_circle('True'); +ERROR: invalid input syntax for type circle: "True" +SELECT * FROM test_return_circle('True'); +ERROR: invalid input syntax for type circle: "True" +-- [ERROR] From Python empty string +SELECT test_return_circle('""'); +ERROR: invalid input syntax for type circle: "" +SELECT * FROM test_return_circle('""'); +ERROR: invalid input syntax for type circle: "" +-- [ERROR] From Python string +SELECT test_return_circle('"value"'); +ERROR: invalid input syntax for type circle: "value" +SELECT * FROM test_return_circle('"value"'); +ERROR: invalid input syntax for type circle: "value" +-- [ERROR] From Python empty list +SELECT test_return_circle('[]'); +ERROR: invalid input syntax for type circle: "[]" +SELECT * FROM test_return_circle('[]'); +ERROR: invalid input syntax for type circle: "[]" +-- [ERROR] From Python non-empty list +SELECT test_return_circle('[False]'); +ERROR: invalid input syntax for type circle: "[False]" +SELECT * FROM test_return_circle('[False]'); +ERROR: invalid input syntax for type circle: "[False]" +-- [ERROR] From Python empty dict +SELECT test_return_circle('{}'); +ERROR: invalid input syntax for type circle: "{}" +SELECT * FROM test_return_circle('{}'); +ERROR: invalid input syntax for type circle: "{}" +-- [ERROR] From Python non-empty dict +SELECT test_return_circle('{None: None}'); +ERROR: invalid input syntax for type circle: "{None: None}" +SELECT * FROM test_return_circle('{None: None}'); +ERROR: invalid input syntax for type circle: "{None: None}" +-- [ERROR] From Python String with null characters +SELECT test_return_circle(E'"test\\0"'); +ERROR: invalid input syntax for type circle: "test" +SELECT * FROM test_return_circle(E'"test\\0"'); +ERROR: invalid input syntax for type circle: "test" +-- +-- Case 5: Return array of integers +-- +-- Test returning arrays of fixed-width elements from PL/Python functions +-- +-- From Python None +SELECT test_return_array_int('None'); + test_return_array_int +----------------------- + +(1 row) + +SELECT * FROM test_return_array_int('None'); + test_return_array_int +----------------------- + +(1 row) + +-- From Python empty list +SELECT test_return_array_int('[]'); + test_return_array_int +----------------------- + {} +(1 row) + +SELECT * FROM test_return_array_int('[]'); + test_return_array_int +----------------------- + {} +(1 row) + +-- From Python non-empty list +SELECT test_return_array_int('[1,2]'); + test_return_array_int +----------------------- + {1,2} +(1 row) + +SELECT * FROM test_return_array_int('[1,2]'); + test_return_array_int +----------------------- + {1,2} +(1 row) + +-- From Python multiple lists +SELECT test_return_array_int('[[1,2,3],[4,5,6]]'); + test_return_array_int +----------------------- + {{1,2,3},{4,5,6}} +(1 row) + +SELECT * FROM test_return_array_int('[[1,2,3],[4,5,6]]'); + test_return_array_int +----------------------- + {{1,2,3},{4,5,6}} +(1 row) + +-- Error conditions +-- Multi-dimensional array with non-fixed dimension sizes +SELECT test_return_array_int('[[1,2,3],[1,2]]'); +ERROR: multidimensional arrays must have array expressions with matching dimensions. PL/Python function return value has sequence length 2 while expected 3 (plpy_elog.c:106) +SELECT * FROM test_return_array_int('[[1,2,3],[1,2]]'); +ERROR: multidimensional arrays must have array expressions with matching dimensions. PL/Python function return value has sequence length 2 while expected 3 (plpy_elog.c:106) +-- Multi-dimensional array with mix of arrays and atomic elements +SELECT test_return_array_int('[[1,2,3],[1,[2,3],[4,5]]]'); +ERROR: invalid input syntax for integer: "[2, 3]" +SELECT * FROM test_return_array_int('[[1,2,3],[1,[2,3],[4,5]]]'); +ERROR: invalid input syntax for integer: "[2, 3]" +-- Multi-dimensional array with missing dimensions +SELECT test_return_array_int('[[1,2,3],None,[4,5,6]]'); +ERROR: multidimensional arrays must have array expressions with matching dimensions. PL/Python function return value has sequence length -1 while expected 3 (plpy_elog.c:106) +SELECT * FROM test_return_array_int('[[1,2,3],None,[4,5,6]]'); +ERROR: multidimensional arrays must have array expressions with matching dimensions. PL/Python function return value has sequence length -1 while expected 3 (plpy_elog.c:106) +-- +-- Case 5: Return array of texts +-- +-- Test returning arrays of variable-width elements from PL/Python functions +-- +-- From Python None +SELECT test_return_array_text('None'); + test_return_array_text +------------------------ + +(1 row) + +SELECT * FROM test_return_array_text('None'); + test_return_array_text +------------------------ + +(1 row) + +-- From Python empty list +SELECT test_return_array_text('[]'); + test_return_array_text +------------------------ + {} +(1 row) + +SELECT * FROM test_return_array_text('[]'); + test_return_array_text +------------------------ + {} +(1 row) + +-- From Python non-empty list +SELECT test_return_array_text('["abc","def"]'); + test_return_array_text +------------------------ + {abc,def} +(1 row) + +SELECT * FROM test_return_array_text('["abc","def"]'); + test_return_array_text +------------------------ + {abc,def} +(1 row) + +-- From Python multiple lists +SELECT test_return_array_text('[["a","bcd","ef"],[None,"gh","ijklm"]]'); + test_return_array_text +------------------------------ + {{a,bcd,ef},{NULL,gh,ijklm}} +(1 row) + +SELECT * FROM test_return_array_text('[["a","bcd","ef"],[None,"gh","ijklm"]]'); + test_return_array_text +------------------------------ + {{a,bcd,ef},{NULL,gh,ijklm}} +(1 row) + +-- Error conditions +-- Multi-dimensional array with non-fixed dimension sizes +SELECT test_return_array_text('[["a","bcd","ef"],[None,"gh","ijklm","ERROR"]]'); +ERROR: multidimensional arrays must have array expressions with matching dimensions. PL/Python function return value has sequence length 4 while expected 3 (plpy_elog.c:106) +SELECT * FROM test_return_array_text('[["a","bcd","ef"],[None,"gh","ijklm","ERROR"]]'); +ERROR: multidimensional arrays must have array expressions with matching dimensions. PL/Python function return value has sequence length 4 while expected 3 (plpy_elog.c:106) +-- Multi-dimensional array with mix of arrays and atomic elements +SELECT test_return_array_text('[[["a"],"b"],["c",["d","e"]]]'); +ERROR: multidimensional arrays must have array expressions with matching dimensions. PL/Python function return value has sequence length 2 while expected 1 (plpy_elog.c:106) +SELECT * FROM test_return_array_text('[[["a"],"b"],["c",["d","e"]]]'); +ERROR: multidimensional arrays must have array expressions with matching dimensions. PL/Python function return value has sequence length 2 while expected 1 (plpy_elog.c:106) +-- Multi-dimensional array with missing dimensions +SELECT test_return_array_text('[["abc","def"],None,["ghij","k"]]'); +ERROR: multidimensional arrays must have array expressions with matching dimensions. PL/Python function return value has sequence length -1 while expected 2 (plpy_elog.c:106) +SELECT * FROM test_return_array_text('[["abc","def"],None,["ghij","k"]]'); +ERROR: multidimensional arrays must have array expressions with matching dimensions. PL/Python function return value has sequence length -1 while expected 2 (plpy_elog.c:106) +-- =================================================== +-- TEST 2: RETURN VALUE TESTING - SETOF scalar values +-- =================================================== +-- +-- The test for the scalar values also need to be tested with SETOF since this +-- goes through a different codepath. +-- +-- In this case the returned Python Object is converted into an iterator object +-- and each value of the resulting iterator is returned as a row matching the +-- return datatype. +-- +-- This can create some peculiar results due to how some Python objects are +-- actually converted to iterators. Most notably an iterator over a string +-- is the set of characters comprising the string, and an iterator over a dict +-- is the set of KEYS of the dictionary. +-- +-- Case 1: Return SETOF void +-- +-- Success conditions +-- From Python List of None +SELECT test_return_setof_void('[None, None]'); + test_return_setof_void +------------------------ + + +(2 rows) + +SELECT * FROM test_return_setof_void('[None, None]'); + test_return_setof_void +------------------------ + + +(2 rows) + +SELECT test_return_setof_void('[None, None]') +FROM gp_single_row; + test_return_setof_void +------------------------ + + +(2 rows) + +-- From Python Empty List +SELECT test_return_setof_void('[]'); + test_return_setof_void +------------------------ +(0 rows) + +SELECT * FROM test_return_setof_void('[]'); + test_return_setof_void +------------------------ +(0 rows) + +SELECT test_return_setof_void('[]') +FROM gp_single_row; + test_return_setof_void +------------------------ +(0 rows) + +-- Error conditions +-- [ERROR] From Python None +SELECT test_return_setof_void('None'); +ERROR: returned object cannot be iterated +SELECT * FROM test_return_setof_void('None'); +ERROR: returned object cannot be iterated +-- [ERROR] From Python string +SELECT test_return_setof_void('"value"'); +ERROR: PL/Python function with return type "void" did not return None +SELECT * FROM test_return_setof_void('"value"'); +ERROR: PL/Python function with return type "void" did not return None +-- [ERROR] From Python List of values +SELECT test_return_setof_void('["value"]'); +ERROR: PL/Python function with return type "void" did not return None +SELECT * FROM test_return_setof_void('["value"]'); +ERROR: PL/Python function with return type "void" did not return None +-- [ERROR] From Python Dict with keys other than "None" +SELECT test_return_setof_void('{"value": None}'); +ERROR: PL/Python function with return type "void" did not return None +SELECT * FROM test_return_setof_void('{"value": None}'); +ERROR: PL/Python function with return type "void" did not return None +-- Questionable Success conditions +-- From Python empty string +SELECT test_return_setof_void('""'); + test_return_setof_void +------------------------ +(0 rows) + +SELECT * FROM test_return_setof_void('""'); + test_return_setof_void +------------------------ +(0 rows) + +-- From Python empty dictionary +SELECT test_return_setof_void('{}'); + test_return_setof_void +------------------------ +(0 rows) + +SELECT * FROM test_return_setof_void('{}'); + test_return_setof_void +------------------------ +(0 rows) + +-- From Python dictionary with a "None" key +SELECT test_return_setof_void('{None: "ignored"}'); + test_return_setof_void +------------------------ + +(1 row) + +SELECT * FROM test_return_setof_void('{None: "ignored"}'); + test_return_setof_void +------------------------ + +(1 row) + +-- +-- Case 2: Return SETOF Bool +-- +-- Success conditions +-- From Python Empty List +SELECT test_return_setof_bool('[]'); + test_return_setof_bool +------------------------ +(0 rows) + +SELECT * FROM test_return_setof_bool('[]'); + test_return_setof_bool +------------------------ +(0 rows) + +SELECT test_return_setof_bool('[]') +FROM gp_single_row; + test_return_setof_bool +------------------------ +(0 rows) + +-- From Python List +SELECT test_return_setof_bool('[None, True, "value", [1], {"A": "B"}]'); + test_return_setof_bool +------------------------ + + t + t + t + t +(5 rows) + +SELECT * FROM test_return_setof_bool('[None, True, "value", [1], {"A": "B"}]'); + test_return_setof_bool +------------------------ + + t + t + t + t +(5 rows) + +SELECT test_return_setof_bool('[None, True, "value", [1], {"A": "B"}]') +FROM gp_single_row; + test_return_setof_bool +------------------------ + + t + t + t + t +(5 rows) + +-- Error conditions +-- [ERROR] From Python None +SELECT test_return_setof_bool('None'); +ERROR: returned object cannot be iterated +SELECT * FROM test_return_setof_bool('None'); +ERROR: returned object cannot be iterated +-- [ERROR] From Python Bool +SELECT test_return_setof_bool('True'); +ERROR: returned object cannot be iterated +SELECT * FROM test_return_setof_bool('True'); +ERROR: returned object cannot be iterated +-- Questionable Success conditions +-- From Python empty string +SELECT test_return_setof_bool('""'); + test_return_setof_bool +------------------------ +(0 rows) + +SELECT * FROM test_return_setof_bool('""'); + test_return_setof_bool +------------------------ +(0 rows) + +-- From Python empty dictionary +SELECT test_return_setof_bool('{}'); + test_return_setof_bool +------------------------ +(0 rows) + +SELECT * FROM test_return_setof_bool('{}'); + test_return_setof_bool +------------------------ +(0 rows) + +-- From Python string +SELECT test_return_setof_bool('"value"'); + test_return_setof_bool +------------------------ + t + t + t + t + t +(5 rows) + +SELECT * FROM test_return_setof_bool('"value"'); + test_return_setof_bool +------------------------ + t + t + t + t + t +(5 rows) + +-- From Python string with null values (which are also true) +SELECT test_return_setof_bool(E'"test\\0"'); + test_return_setof_bool +------------------------ + t + t + t + t + t +(5 rows) + +SELECT * FROM test_return_setof_bool(E'"test\\0"'); + test_return_setof_bool +------------------------ + t + t + t + t + t +(5 rows) + +-- From Python dictionary +SELECT test_return_setof_bool('{True: "ignored", False: "ignored"}'); + test_return_setof_bool +------------------------ + t + f +(2 rows) + +SELECT * FROM test_return_setof_bool('{True: "ignored", False: "ignored"}'); + test_return_setof_bool +------------------------ + t + f +(2 rows) + +-- +-- Case 3: Return SETOF Text +-- +-- From Python Empty List +SELECT test_return_setof_text('[]'); + test_return_setof_text +------------------------ +(0 rows) + +SELECT * FROM test_return_setof_text('[]'); + test_return_setof_text +------------------------ +(0 rows) + +SELECT test_return_setof_text('[]') +FROM gp_single_row; + test_return_setof_text +------------------------ +(0 rows) + +-- From Python List of values +SELECT test_return_setof_text('[None, True, "value", [1], {"A": "B"}]'); + test_return_setof_text +------------------------ + + True + value + [1] + {'A': 'B'} +(5 rows) + +SELECT * FROM test_return_setof_text('[None, True, "value", [1], {"A": "B"}]'); + test_return_setof_text +------------------------ + + True + value + [1] + {'A': 'B'} +(5 rows) + +SELECT test_return_setof_text('[None, True, "value", [1], {"A": "B"}]') +FROM gp_single_row; + test_return_setof_text +------------------------ + + True + value + [1] + {'A': 'B'} +(5 rows) + +-- Error conditions +-- [ERROR] From Python None +SELECT test_return_setof_text('None'); +ERROR: returned object cannot be iterated +SELECT * FROM test_return_setof_text('None'); +ERROR: returned object cannot be iterated +-- [ERROR] From Python Bool +SELECT test_return_setof_text('True'); +ERROR: returned object cannot be iterated +SELECT * FROM test_return_setof_text('True'); +ERROR: returned object cannot be iterated +-- [ERROR] From Python string with null values +SELECT test_return_setof_text(E'"test\\0"'); + test_return_setof_text +------------------------ + t + e + s + t + +(5 rows) + +SELECT * FROM test_return_setof_text(E'"test\\0"'); + test_return_setof_text +------------------------ + t + e + s + t + +(5 rows) + +-- Questionable Success conditions +-- From Python empty string +SELECT test_return_setof_text('""'); + test_return_setof_text +------------------------ +(0 rows) + +SELECT * FROM test_return_setof_text('""'); + test_return_setof_text +------------------------ +(0 rows) + +-- From Python empty dictionary +SELECT test_return_setof_text('{}'); + test_return_setof_text +------------------------ +(0 rows) + +SELECT * FROM test_return_setof_text('{}'); + test_return_setof_text +------------------------ +(0 rows) + +-- From Python string +SELECT test_return_setof_text('"value"'); + test_return_setof_text +------------------------ + v + a + l + u + e +(5 rows) + +SELECT * FROM test_return_setof_text('"value"'); + test_return_setof_text +------------------------ + v + a + l + u + e +(5 rows) + +-- From Python dictionary +SELECT test_return_setof_text('{True: "ignored", False: "ignored"}'); + test_return_setof_text +------------------------ + True + False +(2 rows) + +SELECT * FROM test_return_setof_text('{True: "ignored", False: "ignored"}'); + test_return_setof_text +------------------------ + True + False +(2 rows) + +-- +-- Case 4: Return SETOF Bytea +-- +-- From Python Empty List +SELECT test_return_setof_bytea('[]'); + test_return_setof_bytea +------------------------- +(0 rows) + +SELECT * FROM test_return_setof_bytea('[]'); + test_return_setof_bytea +------------------------- +(0 rows) + +SELECT test_return_setof_bytea('[]') +FROM gp_single_row; + test_return_setof_bytea +------------------------- +(0 rows) + +-- From Python List of values +SELECT test_return_setof_bytea(E'[None, True, "value", [1], {"\\0": "B"}]'); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +SELECT * FROM test_return_setof_bytea(E'[None, True, "value", [1], {"\\0": "B"}]'); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +SELECT test_return_setof_bytea(E'[None, True, "value", [1], {"\\0": "B"}]') +FROM gp_single_row; +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +-- Error conditions +-- [ERROR] From Python None +SELECT test_return_setof_bytea('None'); +ERROR: returned object cannot be iterated +SELECT * FROM test_return_setof_bytea('None'); +ERROR: returned object cannot be iterated +-- [ERROR] From Python Bool +SELECT test_return_setof_bytea('True'); +ERROR: returned object cannot be iterated +SELECT * FROM test_return_setof_bytea('True'); +ERROR: returned object cannot be iterated +-- Questionable Success conditions +-- From Python empty string +SELECT test_return_setof_bytea('""'); + test_return_setof_bytea +------------------------- +(0 rows) + +SELECT * FROM test_return_setof_bytea('""'); + test_return_setof_bytea +------------------------- +(0 rows) + +-- From Python empty dictionary +SELECT test_return_setof_bytea('{}'); + test_return_setof_bytea +------------------------- +(0 rows) + +SELECT * FROM test_return_setof_bytea('{}'); + test_return_setof_bytea +------------------------- +(0 rows) + +-- From Python string +SELECT test_return_setof_bytea('"value"'); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +SELECT * FROM test_return_setof_bytea('"value"'); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +-- From Python string with null values +SELECT test_return_setof_bytea(E'"test\\0"'); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +SELECT * FROM test_return_setof_bytea(E'"test\\0"'); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +-- From Python dictionary +SELECT test_return_setof_bytea('{True: "ignored", False: "ignored"}'); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +SELECT * FROM test_return_setof_bytea('{True: "ignored", False: "ignored"}'); +ERROR: could not create bytes representation of Python object (plpy_elog.c:121) +-- +-- Case 5: Return SETOF Circle +-- +-- From Python Empty List +SELECT test_return_setof_circle('[]'); + test_return_setof_circle +-------------------------- +(0 rows) + +SELECT * FROM test_return_setof_circle('[]'); + test_return_setof_circle +-------------------------- +(0 rows) + +SELECT test_return_setof_circle('[]') +FROM gp_single_row; + test_return_setof_circle +-------------------------- +(0 rows) + +-- From Python List of compatable values +SELECT test_return_setof_circle('[None, ((3,1),4), "((5,3),1)"]'); + test_return_setof_circle +-------------------------- + + <(3,1),4> + <(5,3),1> +(3 rows) + +SELECT * FROM test_return_setof_circle('[None, ((3,1),4), "((5,3),1)"]'); + test_return_setof_circle +-------------------------- + + <(3,1),4> + <(5,3),1> +(3 rows) + +SELECT test_return_setof_circle('[None, ((3,1),4), "((5,3),1)"]') +FROM gp_single_row; + test_return_setof_circle +-------------------------- + + <(3,1),4> + <(5,3),1> +(3 rows) + +-- Questionable Error conditions +-- [ERROR] From Python List of tuples +SELECT test_return_setof_circle('[ [[3,1],4] ]'); +ERROR: invalid input syntax for type circle: "[[3, 1], 4]" +SELECT * FROM test_return_setof_circle('[ [[3,1],4] ]'); +ERROR: invalid input syntax for type circle: "[[3, 1], 4]" +-- Error conditions +-- [ERROR] From Python None +SELECT test_return_setof_circle('None'); +ERROR: returned object cannot be iterated +SELECT * FROM test_return_setof_circle('None'); +ERROR: returned object cannot be iterated +-- [ERROR] From Python Bool +SELECT test_return_setof_circle('True'); +ERROR: returned object cannot be iterated +SELECT * FROM test_return_setof_circle('True'); +ERROR: returned object cannot be iterated +-- [ERROR] From Python string +SELECT test_return_setof_circle('"value"'); +ERROR: invalid input syntax for type circle: "v" +SELECT * FROM test_return_setof_circle('"value"'); +ERROR: invalid input syntax for type circle: "v" +-- [ERROR] From Python List of incompatable values +SELECT test_return_setof_circle('[None, "value"]'); +ERROR: invalid input syntax for type circle: "value" +SELECT * FROM test_return_setof_circle('[None, "value"]'); +ERROR: invalid input syntax for type circle: "value" +-- Questionable Success conditions +-- From Python empty string +SELECT test_return_setof_circle('""'); + test_return_setof_circle +-------------------------- +(0 rows) + +SELECT * FROM test_return_setof_circle('""'); + test_return_setof_circle +-------------------------- +(0 rows) + +-- From Python empty dictionary +SELECT test_return_setof_circle('{}'); + test_return_setof_circle +-------------------------- +(0 rows) + +SELECT * FROM test_return_setof_circle('{}'); + test_return_setof_circle +-------------------------- +(0 rows) + +-- From Python dictionary +SELECT test_return_setof_circle('{((3,1),4): "ignored"}'); + test_return_setof_circle +-------------------------- + <(3,1),4> +(1 row) + +SELECT * FROM test_return_setof_circle('{((3,1),4): "ignored"}'); + test_return_setof_circle +-------------------------- + <(3,1),4> +(1 row) + +-- =============================================== +-- TEST 3: RETURN VALUE TESTING - Compound values +-- =============================================== +-- Compound types, aka record types, are simply built up from +-- the existing primitive types. +-- +-- Within PL/Python the most common way of dealing with compound +-- types is to return them as a python dictionary with the keys +-- of the dictionary being the names of the compound elements +-- and the values being the values. +-- +-- Case 1: From a User Defined Type +-- +-- From Python None +SELECT test_return_type_record('None'); + test_return_type_record +------------------------- + +(1 row) + +SELECT * FROM test_return_type_record('None'); + first | second +-------+-------- + | +(1 row) + +SELECT test_return_type_record('None') +FROM gp_single_row; + test_return_type_record +------------------------- + +(1 row) + +SELECT (test_return_type_record('None')).*; + first | second +-------+-------- + | +(1 row) + +SELECT (test_return_type_record('None')).* +FROM gp_single_row; + first | second +-------+-------- + | +(1 row) + +-- From Python List +SELECT test_return_type_record('("value", 4)'); + test_return_type_record +------------------------- + (value,4) +(1 row) + +SELECT * FROM test_return_type_record('("value", 4)'); + first | second +-------+-------- + value | 4 +(1 row) + +SELECT test_return_type_record('("value", 4)') +FROM gp_single_row; + test_return_type_record +------------------------- + (value,4) +(1 row) + +SELECT (test_return_type_record('("value", 4)')).*; + first | second +-------+-------- + value | 4 +(1 row) + +SELECT (test_return_type_record('("value", 4)')).* +FROM gp_single_row; + first | second +-------+-------- + value | 4 +(1 row) + +-- From Python Tuple +SELECT test_return_type_record('["value", 4]'); + test_return_type_record +------------------------- + (value,4) +(1 row) + +SELECT * FROM test_return_type_record('["value", 4]'); + first | second +-------+-------- + value | 4 +(1 row) + +SELECT test_return_type_record('["value", 4]') +FROM gp_single_row; + test_return_type_record +------------------------- + (value,4) +(1 row) + +SELECT (test_return_type_record('["value", 4]')).*; + first | second +-------+-------- + value | 4 +(1 row) + +SELECT (test_return_type_record('["value", 4]')).* +FROM gp_single_row; + first | second +-------+-------- + value | 4 +(1 row) + +-- From Python Dictionary +SELECT test_return_type_record('{"first": "value", "second": 4}'); + test_return_type_record +------------------------- + (value,4) +(1 row) + +SELECT * FROM test_return_type_record('{"first": "value", "second": 4}'); + first | second +-------+-------- + value | 4 +(1 row) + +SELECT test_return_type_record('{"first": "value", "second": 4}') +FROM gp_single_row; + test_return_type_record +------------------------- + (value,4) +(1 row) + +SELECT (test_return_type_record('{"first": "value", "second": 4}')).*; + first | second +-------+-------- + value | 4 +(1 row) + +SELECT (test_return_type_record('{"first": "value", "second": 4}')).* +FROM gp_single_row; + first | second +-------+-------- + value | 4 +(1 row) + +-- Error conditions +-- [ERROR] From Python bool +SELECT test_return_type_record('True'); +ERROR: attribute "first" does not exist in Python object +SELECT * FROM test_return_type_record('True'); +ERROR: attribute "first" does not exist in Python object +-- [ERROR] From Python empty string +SELECT test_return_type_record('""'); +ERROR: malformed record literal: "" +SELECT * FROM test_return_type_record('""'); +ERROR: malformed record literal: "" +-- [ERROR] From Python string +SELECT test_return_type_record('"value"'); +ERROR: malformed record literal: "value" +SELECT * FROM test_return_type_record('"value"'); +ERROR: malformed record literal: "value" +-- [ERROR] From Python non-empty list, wrong number of columns +SELECT test_return_type_record('[False]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_type_record('[False]'); +ERROR: length of returned sequence did not match number of columns in row +-- [ERROR] From Python non-empty list, wrong datatypes +SELECT test_return_type_record('[4, "value"]'); +ERROR: invalid input syntax for integer: "value" +SELECT * FROM test_return_type_record('[4, "value"]'); +ERROR: invalid input syntax for integer: "value" +-- [ERROR] From Python empty dict +SELECT test_return_type_record('{}'); +ERROR: key "first" not found in mapping +SELECT * FROM test_return_type_record('{}'); +ERROR: key "first" not found in mapping +-- [ERROR] From Python without the needed columns +SELECT test_return_type_record('{None: None}'); +ERROR: key "first" not found in mapping +SELECT * FROM test_return_type_record('{None: None}'); +ERROR: key "first" not found in mapping +-- Questionable Success conditions +-- From Python String (one character per column) +SELECT test_return_type_record('"a4"'); +ERROR: malformed record literal: "a4" +SELECT * FROM test_return_type_record('"a4"'); +ERROR: malformed record literal: "a4" +-- From Python Dictionary, with extra fields +SELECT test_return_type_record('{"first": "value", "second": 4, "third": "ignored"}'); + test_return_type_record +------------------------- + (value,4) +(1 row) + +SELECT * FROM test_return_type_record('{"first": "value", "second": 4, "third": "ignored"}'); + first | second +-------+-------- + value | 4 +(1 row) + +-- +-- Case 2: From a Table Type +-- +-- From Python None +SELECT test_return_table_record('None'); + test_return_table_record +-------------------------- + +(1 row) + +SELECT * FROM test_return_table_record('None'); + first | second +-------+-------- + | +(1 row) + +SELECT test_return_table_record('None') +FROM gp_single_row; + test_return_table_record +-------------------------- + +(1 row) + +SELECT (test_return_table_record('None')).*; + first | second +-------+-------- + | +(1 row) + +SELECT (test_return_table_record('None')).* +FROM gp_single_row; + first | second +-------+-------- + | +(1 row) + +-- From Python List +SELECT test_return_table_record('("value", 4)'); + test_return_table_record +-------------------------- + (value,4) +(1 row) + +SELECT * FROM test_return_table_record('("value", 4)'); + first | second +-------+-------- + value | 4 +(1 row) + +SELECT test_return_table_record('("value", 4)') +FROM gp_single_row; + test_return_table_record +-------------------------- + (value,4) +(1 row) + +SELECT (test_return_table_record('("value", 4)')).*; + first | second +-------+-------- + value | 4 +(1 row) + +SELECT (test_return_table_record('("value", 4)')).* +FROM gp_single_row; + first | second +-------+-------- + value | 4 +(1 row) + +-- From Python Tuple +SELECT test_return_table_record('["value", 4]'); + test_return_table_record +-------------------------- + (value,4) +(1 row) + +SELECT * FROM test_return_table_record('["value", 4]'); + first | second +-------+-------- + value | 4 +(1 row) + +SELECT test_return_table_record('["value", 4]') +FROM gp_single_row; + test_return_table_record +-------------------------- + (value,4) +(1 row) + +SELECT (test_return_table_record('["value", 4]')).*; + first | second +-------+-------- + value | 4 +(1 row) + +SELECT (test_return_table_record('["value", 4]')).* +FROM gp_single_row; + first | second +-------+-------- + value | 4 +(1 row) + +-- From Python Dictionary +SELECT test_return_table_record('{"first": "value", "second": 4}'); + test_return_table_record +-------------------------- + (value,4) +(1 row) + +SELECT * FROM test_return_table_record('{"first": "value", "second": 4}'); + first | second +-------+-------- + value | 4 +(1 row) + +SELECT test_return_table_record('{"first": "value", "second": 4}') +FROM gp_single_row; + test_return_table_record +-------------------------- + (value,4) +(1 row) + +SELECT (test_return_table_record('{"first": "value", "second": 4}')).*; + first | second +-------+-------- + value | 4 +(1 row) + +SELECT (test_return_table_record('{"first": "value", "second": 4}')).* +FROM gp_single_row; + first | second +-------+-------- + value | 4 +(1 row) + +-- Error conditions +-- [ERROR] From Python bool +SELECT test_return_table_record('True'); +ERROR: attribute "first" does not exist in Python object +SELECT * FROM test_return_table_record('True'); +ERROR: attribute "first" does not exist in Python object +-- [ERROR] From Python empty string +SELECT test_return_table_record('""'); +ERROR: malformed record literal: "" +SELECT * FROM test_return_table_record('""'); +ERROR: malformed record literal: "" +-- [ERROR] From Python string +SELECT test_return_table_record('"value"'); +ERROR: malformed record literal: "value" +SELECT * FROM test_return_table_record('"value"'); +ERROR: malformed record literal: "value" +-- [ERROR] From Python non-empty list, wrong number of columns +SELECT test_return_table_record('[False]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_table_record('[False]'); +ERROR: length of returned sequence did not match number of columns in row +-- [ERROR] From Python non-empty list, wrong datatypes +SELECT test_return_table_record('[4, "value"]'); +ERROR: invalid input syntax for integer: "value" +SELECT * FROM test_return_table_record('[4, "value"]'); +ERROR: invalid input syntax for integer: "value" +-- [ERROR] From Python empty dict +SELECT test_return_table_record('{}'); +ERROR: key "first" not found in mapping +SELECT * FROM test_return_table_record('{}'); +ERROR: key "first" not found in mapping +-- [ERROR] From Python without the needed columns +SELECT test_return_table_record('{None: None}'); +ERROR: key "first" not found in mapping +SELECT * FROM test_return_table_record('{None: None}'); +ERROR: key "first" not found in mapping +-- Questionable Success conditions +-- From Python String (one character per column) +SELECT test_return_table_record('"a4"'); +ERROR: malformed record literal: "a4" +SELECT * FROM test_return_table_record('"a4"'); +ERROR: malformed record literal: "a4" +-- From Python Dictionary, with extra fields +SELECT test_return_table_record('{"first": "value", "second": 4, "third": "ignored"}'); + test_return_table_record +-------------------------- + (value,4) +(1 row) + +SELECT * FROM test_return_table_record('{"first": "value", "second": 4}, "third": "ignored"}'); +ERROR: SyntaxError: invalid syntax (, line 1) (plpy_elog.c:106) +-- ===================================================== +-- TEST 4: RETURN VALUE TESTING - SETOF Compound values +-- ===================================================== +-- The test for the scalar Compound values also need to be tested with SETOF +-- since this goes through a different codepath. +-- +-- In this case the returned Python Object is converted into an iterator object +-- and each value of the resulting iterator is returned as a row matching the +-- return datatype +-- +-- Case 1: From a User Defined Type +-- +-- From Python empty list +SELECT test_return_setof_type_record('[]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_setof_type_record('[]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT test_return_setof_type_record('[]') +FROM gp_single_row; +ERROR: length of returned sequence did not match number of columns in row (seg0 slice1 127.0.0.1:25432 pid=24014) +SELECT (test_return_setof_type_record('[]')).*; +ERROR: length of returned sequence did not match number of columns in row +SELECT (test_return_setof_type_record('[]')).* +FROM gp_single_row; +ERROR: length of returned sequence did not match number of columns in row (seg0 slice1 127.0.0.1:25432 pid=24014) +-- From Python List +SELECT test_return_setof_type_record('[ ("value", 4), {"first": "test", "second": 2} ]'); +ERROR: invalid input syntax for integer: "{'second': 2, 'first': 'test'}" +SELECT * FROM test_return_setof_type_record('[ None, ("value", 4), {"first": "test", "second": 2} ]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT test_return_setof_type_record('[ ("value", 4), {"first": "test", "second": 2} ]') +FROM gp_single_row; +ERROR: invalid input syntax for integer: "{'second': 2, 'first': 'test'}" (seg0 slice1 127.0.0.1:25432 pid=24014) +SELECT (test_return_setof_type_record('[ ("value", 4), {"first": "test", "second": 2} ]')).*; +ERROR: invalid input syntax for integer: "{'second': 2, 'first': 'test'}" +SELECT (test_return_setof_type_record('[ ("value", 4), {"first": "test", "second": 2} ]')).* +FROM gp_single_row; +ERROR: invalid input syntax for integer: "{'second': 2, 'first': 'test'}" (seg0 slice1 127.0.0.1:25432 pid=24014) +-- Error conditions +-- [ERROR] From Python None +SELECT test_return_setof_type_record('None'); + test_return_setof_type_record +------------------------------- + +(1 row) + +SELECT * FROM test_return_setof_type_record('None'); + first | second +-------+-------- + | +(1 row) + +-- [ERROR] From Python bool +SELECT test_return_setof_type_record('True'); +ERROR: attribute "first" does not exist in Python object +SELECT * FROM test_return_setof_type_record('True'); +ERROR: attribute "first" does not exist in Python object +-- [ERROR] From Python string +SELECT test_return_setof_type_record('"value"'); +ERROR: malformed record literal: "value" +SELECT * FROM test_return_setof_type_record('"value"'); +ERROR: malformed record literal: "value" +-- [ERROR] From Python List +SELECT test_return_setof_type_record('("value", 4)'); + test_return_setof_type_record +------------------------------- + (value,4) +(1 row) + +SELECT * FROM test_return_setof_type_record('("value", 4)'); + first | second +-------+-------- + value | 4 +(1 row) + +-- [ERROR] From Python non-empty list, wrong number of columns +SELECT test_return_setof_type_record('[ [False] ]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_setof_type_record('[ [False] ]'); +ERROR: length of returned sequence did not match number of columns in row +-- [ERROR] From Python non-empty list, wrong datatypes +SELECT test_return_setof_type_record('[ [4, "value"] ]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_setof_type_record('[ [4, "value"] ]'); +ERROR: length of returned sequence did not match number of columns in row +-- [ERROR] From Python non-empty list, cointaining None +SELECT test_return_setof_type_record('[None]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_setof_type_record('[None]'); +ERROR: length of returned sequence did not match number of columns in row +-- [ERROR] From Python dict without the needed columns +SELECT test_return_setof_type_record('[ {None: None} ]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_setof_type_record('[ {None: None} ]'); +ERROR: length of returned sequence did not match number of columns in row +-- [ERROR] From Python List of String (one character per column) +SELECT test_return_setof_type_record('["a4"]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_setof_type_record('["a4"]'); +ERROR: length of returned sequence did not match number of columns in row +-- Questionable Success conditions +-- From Python empty string +SELECT test_return_setof_type_record('""'); +ERROR: malformed record literal: "" +SELECT * FROM test_return_setof_type_record('""'); +ERROR: malformed record literal: "" +-- From Python empty dict +SELECT test_return_setof_type_record('{}'); +ERROR: key "first" not found in mapping +SELECT * FROM test_return_setof_type_record('{}'); +ERROR: key "first" not found in mapping +-- From Python Dictionary, with extra fields +SELECT test_return_setof_type_record('[{"first": "value", "second": 4, "third": "ignored"}]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_setof_type_record('[{"first": "value", "second": 4}, "third": "ignored"}]'); +ERROR: SyntaxError: invalid syntax (, line 1) (plpy_elog.c:106) +-- +-- Case 2: From a setof Table Type +-- +-- From Python empty list +SELECT test_return_setof_table_record('[]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_setof_table_record('[]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT test_return_setof_table_record('[]') +FROM gp_single_row; +ERROR: length of returned sequence did not match number of columns in row (seg0 slice1 127.0.0.1:25432 pid=24014) +SELECT (test_return_setof_table_record('[]')).*; +ERROR: length of returned sequence did not match number of columns in row +SELECT (test_return_setof_table_record('[]')).* +FROM gp_single_row; +ERROR: length of returned sequence did not match number of columns in row (seg0 slice1 127.0.0.1:25432 pid=24014) +-- From Python List +SELECT test_return_setof_table_record('[ None, ("value", 4), {"first": "test", "second": 2} ]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_setof_table_record('[ None, ("value", 4), {"first": "test", "second": 2} ]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT test_return_setof_table_record('[ None, ("value", 4), {"first": "test", "second": 2} ]') +FROM gp_single_row; +ERROR: length of returned sequence did not match number of columns in row (seg0 slice1 127.0.0.1:25432 pid=24014) +SELECT (test_return_setof_table_record('[ None, ("value", 4), {"first": "test", "second": 2} ]')).*; +ERROR: length of returned sequence did not match number of columns in row +SELECT (test_return_setof_table_record('[ None, ("value", 4), {"first": "test", "second": 2} ]')).* +FROM gp_single_row; +ERROR: length of returned sequence did not match number of columns in row (seg0 slice1 127.0.0.1:25432 pid=24014) +-- Error conditions +-- [ERROR] From Python None +SELECT test_return_setof_table_record('None'); + test_return_setof_table_record +-------------------------------- + +(1 row) + +SELECT * FROM test_return_setof_table_record('None'); + first | second +-------+-------- + | +(1 row) + +-- [ERROR] From Python bool +SELECT test_return_setof_table_record('True'); +ERROR: attribute "first" does not exist in Python object +SELECT * FROM test_return_setof_table_record('True'); +ERROR: attribute "first" does not exist in Python object +-- [ERROR] From Python string +SELECT test_return_setof_table_record('"value"'); +ERROR: malformed record literal: "value" +SELECT * FROM test_return_setof_table_record('"value"'); +ERROR: malformed record literal: "value" +-- [ERROR] From Python List +SELECT test_return_setof_table_record('("value", 4)'); + test_return_setof_table_record +-------------------------------- + (value,4) +(1 row) + +SELECT * FROM test_return_setof_table_record('("value", 4)'); + first | second +-------+-------- + value | 4 +(1 row) + +-- [ERROR] From Python non-empty list, wrong number of columns +SELECT test_return_setof_table_record('[ [False] ]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_setof_table_record('[ [False] ]'); +ERROR: length of returned sequence did not match number of columns in row +-- [ERROR] From Python non-empty list, wrong datatypes +SELECT test_return_setof_table_record('[ [4, "value"] ]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_setof_table_record('[ [4, "value"] ]'); +ERROR: length of returned sequence did not match number of columns in row +-- [ERROR] From Python non-empty list, cointaining None +SELECT test_return_setof_table_record('[None]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_setof_table_record('[None]'); +ERROR: length of returned sequence did not match number of columns in row +-- [ERROR] From Python dict without the needed columns +SELECT test_return_setof_table_record('[ {None: None} ]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_setof_table_record('[ {None: None} ]'); +ERROR: length of returned sequence did not match number of columns in row +-- [ERROR] From Python List of String (one character per column) +SELECT test_return_setof_table_record('["a4"]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_setof_table_record('["a4"]'); +ERROR: length of returned sequence did not match number of columns in row +-- Questionable Success conditions +-- From Python empty string +SELECT test_return_setof_table_record('""'); +ERROR: malformed record literal: "" +SELECT * FROM test_return_setof_table_record('""'); +ERROR: malformed record literal: "" +-- From Python empty dict +SELECT test_return_setof_table_record('{}'); +ERROR: key "first" not found in mapping +SELECT * FROM test_return_setof_table_record('{}'); +ERROR: key "first" not found in mapping +-- From Python Dictionary, with extra fields +SELECT test_return_setof_table_record('[{"first": "value", "second": 4, "third": "ignored"}]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_setof_table_record('[{"first": "value", "second": 4}, "third": "ignored"}]'); +ERROR: SyntaxError: invalid syntax (, line 1) (plpy_elog.c:106) +-- ===================================================== +-- TEST 5: RETURN VALUE TESTING - OUT Parameters +-- ===================================================== +-- +-- Case 1: RETURNS SCALAR OUT Parameters +-- +-- From Python None +SELECT test_return_out_text('None'); + test_return_out_text +---------------------- + +(1 row) + +SELECT * FROM test_return_out_text('None'); + test_return_out_text +---------------------- + +(1 row) + +SELECT test_return_out_text('None') +FROM gp_single_row; + test_return_out_text +---------------------- + +(1 row) + +-- From Python bool +SELECT test_return_out_text('True'); + test_return_out_text +---------------------- + True +(1 row) + +SELECT * FROM test_return_out_text('True'); + test_return_out_text +---------------------- + True +(1 row) + +SELECT test_return_out_text('True') +FROM gp_single_row; + test_return_out_text +---------------------- + True +(1 row) + +-- From Python empty string +SELECT test_return_out_text('""'); + test_return_out_text +---------------------- + +(1 row) + +SELECT * FROM test_return_out_text('""'); + test_return_out_text +---------------------- + +(1 row) + +SELECT test_return_out_text('""') +FROM gp_single_row; + test_return_out_text +---------------------- + +(1 row) + +-- From Python string +SELECT test_return_out_text('"value"'); + test_return_out_text +---------------------- + value +(1 row) + +SELECT * FROM test_return_out_text('"value"'); + test_return_out_text +---------------------- + value +(1 row) + +SELECT test_return_out_text('"value"') +FROM gp_single_row; + test_return_out_text +---------------------- + value +(1 row) + +-- From Python empty list +SELECT test_return_out_text('[]'); + test_return_out_text +---------------------- + [] +(1 row) + +SELECT * FROM test_return_out_text('[]'); + test_return_out_text +---------------------- + [] +(1 row) + +SELECT test_return_out_text('[]') +FROM gp_single_row; + test_return_out_text +---------------------- + [] +(1 row) + +-- From Python non-empty list +SELECT test_return_out_text('[False]'); + test_return_out_text +---------------------- + [False] +(1 row) + +SELECT * FROM test_return_out_text('[False]'); + test_return_out_text +---------------------- + [False] +(1 row) + +SELECT test_return_out_text('[False]') +FROM gp_single_row; + test_return_out_text +---------------------- + [False] +(1 row) + +-- From Python empty dict +SELECT test_return_out_text('{}'); + test_return_out_text +---------------------- + {} +(1 row) + +SELECT * FROM test_return_out_text('{}'); + test_return_out_text +---------------------- + {} +(1 row) + +SELECT test_return_out_text('{}') +FROM gp_single_row; + test_return_out_text +---------------------- + {} +(1 row) + +-- From Python non-empty dict +SELECT test_return_out_text('{None: None}'); + test_return_out_text +---------------------- + {None: None} +(1 row) + +SELECT * FROM test_return_out_text('{None: None}'); + test_return_out_text +---------------------- + {None: None} +(1 row) + +SELECT test_return_out_text('{None: None}') +FROM gp_single_row; + test_return_out_text +---------------------- + {None: None} +(1 row) + +-- From Python Object with str() function +SELECT test_return_out_text( + E'1; exec("class Foo:\\n def __str__(self): return \\"test\\"", globals()); y = Foo()' +); + test_return_out_text +---------------------- + test +(1 row) + +SELECT * FROM test_return_out_text( + E'1; exec("class Foo:\\n def __str__(self): return \\"test\\"", globals()); y = Foo()' +); + test_return_out_text +---------------------- + test +(1 row) + +SELECT test_return_out_text( + E'1; exec("class Foo:\\n def __str__(self): return \\"test\\"", globals()); y = Foo()' +) FROM gp_single_row; + test_return_out_text +---------------------- + test +(1 row) + +-- Error conditions: +-- There are two ways that a "text" return can fail: +-- a) There isn't a __str__ function for the returned object +-- b) The returned string contains null characters (valid in Python) +-- [ERROR] From Python String with null characters +SELECT test_return_out_text(E'"test\\0"'); + test_return_out_text +---------------------- + test +(1 row) + +SELECT * FROM test_return_out_text(E'"test\\0"'); + test_return_out_text +---------------------- + test +(1 row) + +-- [ERROR] From Python Object with bad str() function +SELECT test_return_out_text( + E'1; exec("class Foo:\\n def __str__(self): return None", globals()); y = Foo()' +); +ERROR: could not create string representation of Python object (plpy_elog.c:106) +SELECT * FROM test_return_out_text( + E'1; exec("class Foo:\\n def __str__(self): return None", globals()); y = Foo()' +); +ERROR: could not create string representation of Python object (plpy_elog.c:106) +-- +-- Case 2: RETURNS RECORD +-- +-- From Python None +SELECT test_return_out_record('None'); + test_return_out_record +------------------------ + +(1 row) + +SELECT * FROM test_return_out_record('None'); + first | second +-------+-------- + | +(1 row) + +SELECT test_return_out_record('None') +FROM gp_single_row; + test_return_out_record +------------------------ + +(1 row) + +SELECT (test_return_out_record('None')).*; + first | second +-------+-------- + | +(1 row) + +SELECT (test_return_out_record('None')).* +FROM gp_single_row; + first | second +-------+-------- + | +(1 row) + +-- From Python List +SELECT test_return_out_record('("value", 4)'); + test_return_out_record +------------------------ + (value,4) +(1 row) + +SELECT * FROM test_return_out_record('("value", 4)'); + first | second +-------+-------- + value | 4 +(1 row) + +SELECT test_return_out_record('("value", 4)') +FROM gp_single_row; + test_return_out_record +------------------------ + (value,4) +(1 row) + +SELECT (test_return_out_record('("value", 4)')).*; + first | second +-------+-------- + value | 4 +(1 row) + +SELECT (test_return_out_record('("value", 4)')).* +FROM gp_single_row; + first | second +-------+-------- + value | 4 +(1 row) + +-- From Python Tuple +SELECT test_return_out_record('["value", 4]'); + test_return_out_record +------------------------ + (value,4) +(1 row) + +SELECT * FROM test_return_out_record('["value", 4]'); + first | second +-------+-------- + value | 4 +(1 row) + +SELECT test_return_out_record('["value", 4]') +FROM gp_single_row; + test_return_out_record +------------------------ + (value,4) +(1 row) + +SELECT (test_return_out_record('["value", 4]')).*; + first | second +-------+-------- + value | 4 +(1 row) + +SELECT (test_return_out_record('["value", 4]')).* +FROM gp_single_row; + first | second +-------+-------- + value | 4 +(1 row) + +-- From Python Dictionary +SELECT test_return_out_record('{"first": "value", "second": 4}'); + test_return_out_record +------------------------ + (value,4) +(1 row) + +SELECT * FROM test_return_out_record('{"first": "value", "second": 4}'); + first | second +-------+-------- + value | 4 +(1 row) + +SELECT test_return_out_record('{"first": "value", "second": 4}') +FROM gp_single_row; + test_return_out_record +------------------------ + (value,4) +(1 row) + +SELECT (test_return_out_record('{"first": "value", "second": 4}')).*; + first | second +-------+-------- + value | 4 +(1 row) + +SELECT (test_return_out_record('{"first": "value", "second": 4}')).* +FROM gp_single_row; + first | second +-------+-------- + value | 4 +(1 row) + +-- Error conditions +-- [ERROR] From Python bool +SELECT test_return_out_record('True'); +ERROR: attribute "first" does not exist in Python object +SELECT * FROM test_return_out_record('True'); +ERROR: attribute "first" does not exist in Python object +-- [ERROR] From Python empty string +SELECT test_return_out_record('""'); +ERROR: malformed record literal: "" +SELECT * FROM test_return_out_record('""'); +ERROR: malformed record literal: "" +-- [ERROR] From Python string +SELECT test_return_out_record('"value"'); +ERROR: malformed record literal: "value" +SELECT * FROM test_return_out_record('"value"'); +ERROR: malformed record literal: "value" +-- [ERROR] From Python non-empty list, wrong number of columns +SELECT test_return_out_record('[False]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_out_record('[False]'); +ERROR: length of returned sequence did not match number of columns in row +-- [ERROR] From Python non-empty list, wrong datatypes +SELECT test_return_out_record('[4, "value"]'); +ERROR: invalid input syntax for integer: "value" +SELECT * FROM test_return_out_record('[4, "value"]'); +ERROR: invalid input syntax for integer: "value" +-- [ERROR] From Python empty dict +SELECT test_return_out_record('{}'); +ERROR: key "first" not found in mapping +SELECT * FROM test_return_out_record('{}'); +ERROR: key "first" not found in mapping +-- [ERROR] From Python without the needed columns +SELECT test_return_out_record('{None: None}'); +ERROR: key "first" not found in mapping +SELECT * FROM test_return_out_record('{None: None}'); +ERROR: key "first" not found in mapping +-- Questionable Success conditions +-- From Python String (one character per column) +SELECT test_return_out_record('"a4"'); +ERROR: malformed record literal: "a4" +SELECT * FROM test_return_out_record('"a4"'); +ERROR: malformed record literal: "a4" +-- From Python Dictionary, with extra fields +SELECT test_return_out_record('{"first": "value", "second": 4, "third": "ignored"}'); + test_return_out_record +------------------------ + (value,4) +(1 row) + +SELECT * FROM test_return_out_record('{"first": "value", "second": 4, "third": "ignored"}'); + first | second +-------+-------- + value | 4 +(1 row) + +-- +-- Case 3: RETURNS SETOF SCALAR +-- +-- From Python Empty List +SELECT test_return_out_setof_text('[]'); + test_return_out_setof_text +---------------------------- +(0 rows) + +SELECT * FROM test_return_out_setof_text('[]'); + test_return_out_setof_text +---------------------------- +(0 rows) + +SELECT test_return_out_setof_text('[]') +FROM gp_single_row; + test_return_out_setof_text +---------------------------- +(0 rows) + +-- From Python List of values +SELECT test_return_out_setof_text('[None, True, "value", [1], {"A": "B"}]'); + test_return_out_setof_text +---------------------------- + + True + value + [1] + {'A': 'B'} +(5 rows) + +SELECT * FROM test_return_out_setof_text('[None, True, "value", [1], {"A": "B"}]'); + test_return_out_setof_text +---------------------------- + + True + value + [1] + {'A': 'B'} +(5 rows) + +SELECT test_return_out_setof_text('[None, True, "value", [1], {"A": "B"}]') +FROM gp_single_row; + test_return_out_setof_text +---------------------------- + + True + value + [1] + {'A': 'B'} +(5 rows) + +-- Error conditions +-- [ERROR] From Python None +SELECT test_return_out_setof_text('None'); +ERROR: returned object cannot be iterated +SELECT * FROM test_return_out_setof_text('None'); +ERROR: returned object cannot be iterated +-- [ERROR] From Python Bool +SELECT test_return_out_setof_text('True'); +ERROR: returned object cannot be iterated +SELECT * FROM test_return_out_setof_text('True'); +ERROR: returned object cannot be iterated +-- [ERROR] From Python string with null values +SELECT test_return_out_setof_text(E'"test\\0"'); + test_return_out_setof_text +---------------------------- + t + e + s + t + +(5 rows) + +SELECT * FROM test_return_out_setof_text(E'"test\\0"'); + test_return_out_setof_text +---------------------------- + t + e + s + t + +(5 rows) + +-- Questionable Success conditions +-- From Python empty string +SELECT test_return_out_setof_text('""'); + test_return_out_setof_text +---------------------------- +(0 rows) + +SELECT * FROM test_return_out_setof_text('""'); + test_return_out_setof_text +---------------------------- +(0 rows) + +-- From Python empty dictionary +SELECT test_return_out_setof_text('{}'); + test_return_out_setof_text +---------------------------- +(0 rows) + +SELECT * FROM test_return_out_setof_text('{}'); + test_return_out_setof_text +---------------------------- +(0 rows) + +-- From Python string +SELECT test_return_out_setof_text('"value"'); + test_return_out_setof_text +---------------------------- + v + a + l + u + e +(5 rows) + +SELECT * FROM test_return_out_setof_text('"value"'); + test_return_out_setof_text +---------------------------- + v + a + l + u + e +(5 rows) + +-- From Python dictionary +SELECT test_return_out_setof_text('{True: "ignored", False: "ignored"}'); + test_return_out_setof_text +---------------------------- + True + False +(2 rows) + +SELECT * FROM test_return_out_setof_text('{True: "ignored", False: "ignored"}'); + test_return_out_setof_text +---------------------------- + True + False +(2 rows) + +-- +-- Case 4: RETURNS SETOF RECORD +-- +-- From Python empty list +SELECT test_return_out_setof_record('[]'); + test_return_out_setof_record +------------------------------ +(0 rows) + +SELECT * FROM test_return_out_setof_record('[]'); + first | second +-------+-------- +(0 rows) + +SELECT test_return_out_setof_record('[]') +FROM gp_single_row; + test_return_out_setof_record +------------------------------ +(0 rows) + +SELECT (test_return_out_setof_record('[]')).*; + first | second +-------+-------- +(0 rows) + +SELECT (test_return_out_setof_record('[]')).* +FROM gp_single_row; + first | second +-------+-------- +(0 rows) + +-- From Python List +SELECT test_return_out_setof_record('[ ("value", 4), {"first": "test", "second": 2} ]'); + test_return_out_setof_record +------------------------------ + (value,4) + (test,2) +(2 rows) + +SELECT * FROM test_return_out_setof_record('[ ("value", 4), {"first": "test", "second": 2} ]'); + first | second +-------+-------- + value | 4 + test | 2 +(2 rows) + +SELECT test_return_out_setof_record('[ ("value", 4), {"first": "test", "second": 2} ]') +FROM gp_single_row; + test_return_out_setof_record +------------------------------ + (value,4) + (test,2) +(2 rows) + +SELECT (test_return_out_setof_record('[ ("value", 4), {"first": "test", "second": 2} ]')).*; + first | second +-------+-------- + value | 4 + test | 2 +(2 rows) + +SELECT (test_return_out_setof_record('[ ("value", 4), {"first": "test", "second": 2} ]')).* +FROM gp_single_row; + first | second +-------+-------- + value | 4 + test | 2 +(2 rows) + +-- Error conditions +-- [ERROR] From Python None +SELECT test_return_out_setof_record('None'); +ERROR: returned object cannot be iterated +SELECT * FROM test_return_out_setof_record('None'); +ERROR: returned object cannot be iterated +-- [ERROR] From Python bool +SELECT test_return_out_setof_record('True'); +ERROR: returned object cannot be iterated +SELECT * FROM test_return_out_setof_record('True'); +ERROR: returned object cannot be iterated +-- [ERROR] From Python string +SELECT test_return_out_setof_record('"value"'); +ERROR: malformed record literal: "v" +SELECT * FROM test_return_out_setof_record('"value"'); +ERROR: malformed record literal: "v" +-- [ERROR] From Python List +SELECT test_return_out_setof_record('("value", 4)'); +ERROR: malformed record literal: "value" +SELECT * FROM test_return_out_setof_record('("value", 4)'); +ERROR: malformed record literal: "value" +-- [ERROR] From Python non-empty list, wrong number of columns +SELECT test_return_out_setof_record('[ [False] ]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_out_setof_record('[ [False] ]'); +ERROR: length of returned sequence did not match number of columns in row +-- [ERROR] From Python non-empty list, wrong datatypes +SELECT test_return_out_setof_record('[ [4, "value"] ]'); +ERROR: invalid input syntax for integer: "value" +SELECT * FROM test_return_out_setof_record('[ [4, "value"] ]'); +ERROR: invalid input syntax for integer: "value" +-- [ERROR] From Python non-empty list, containing None +SELECT test_return_out_setof_record('[ None ]'); + test_return_out_setof_record +------------------------------ + +(1 row) + +SELECT * FROM test_return_out_setof_record('[ None ]'); +ERROR: function returning set of rows cannot return null value +-- [ERROR] From Python dict without the needed columns +SELECT test_return_out_setof_record('[ {None: None} ]'); +ERROR: key "first" not found in mapping +SELECT * FROM test_return_out_setof_record('[ {None: None} ]'); +ERROR: key "first" not found in mapping +-- Questionable Success conditions +-- From Python empty string +SELECT test_return_out_setof_record('""'); + test_return_out_setof_record +------------------------------ +(0 rows) + +SELECT * FROM test_return_out_setof_record('""'); + first | second +-------+-------- +(0 rows) + +-- From Python empty dict +SELECT test_return_out_setof_record('{}'); + test_return_out_setof_record +------------------------------ +(0 rows) + +SELECT * FROM test_return_out_setof_record('{}'); + first | second +-------+-------- +(0 rows) + +-- From Python Dictionary, with extra fields +SELECT test_return_out_setof_record('[{"first": "value", "second": 4, "third": "ignored"}]'); + test_return_out_setof_record +------------------------------ + (value,4) +(1 row) + +SELECT * FROM test_return_out_setof_record('[{"first": "value", "second": 4}, "third": "ignored"}]'); +ERROR: SyntaxError: invalid syntax (, line 1) (plpy_elog.c:106) +-- From Python List of String (one character per column) +SELECT test_return_out_setof_record('["a4"]'); +ERROR: malformed record literal: "a4" +SELECT * FROM test_return_out_setof_record('["a4"]'); +ERROR: malformed record literal: "a4" +-- +-- Case 5: RETURNS TABLE +-- +-- From Python empty list +SELECT test_return_table('[]'); + test_return_table +------------------- +(0 rows) + +SELECT * FROM test_return_table('[]'); + first | second +-------+-------- +(0 rows) + +SELECT test_return_table('[]') +FROM gp_single_row; + test_return_table +------------------- +(0 rows) + +SELECT (test_return_table('[]')).*; + first | second +-------+-------- +(0 rows) + +SELECT (test_return_table('[]')).* +FROM gp_single_row; + first | second +-------+-------- +(0 rows) + +-- From Python List +SELECT test_return_table('[ ("value", 4), {"first": "test", "second": 2} ]'); + test_return_table +------------------- + (value,4) + (test,2) +(2 rows) + +SELECT * FROM test_return_table('[ ("value", 4), {"first": "test", "second": 2} ]'); + first | second +-------+-------- + value | 4 + test | 2 +(2 rows) + +SELECT test_return_table('[ None, ("value", 4), {"first": "test", "second": 2} ]') +FROM gp_single_row; + test_return_table +------------------- + + (value,4) + (test,2) +(3 rows) + +SELECT (test_return_table('[ ("value", 4), {"first": "test", "second": 2} ]')).*; + first | second +-------+-------- + value | 4 + test | 2 +(2 rows) + +SELECT (test_return_table('[ ("value", 4), {"first": "test", "second": 2} ]')).* +FROM gp_single_row; + first | second +-------+-------- + value | 4 + test | 2 +(2 rows) + +-- Error conditions +-- [ERROR] From Python None +SELECT test_return_table('None'); +ERROR: returned object cannot be iterated +SELECT * FROM test_return_table('None'); +ERROR: returned object cannot be iterated +-- [ERROR] From Python bool +SELECT test_return_table('True'); +ERROR: returned object cannot be iterated +SELECT * FROM test_return_table('True'); +ERROR: returned object cannot be iterated +-- [ERROR] From Python string +SELECT test_return_table('"value"'); +ERROR: malformed record literal: "v" +SELECT * FROM test_return_table('"value"'); +ERROR: malformed record literal: "v" +-- [ERROR] From Python List +SELECT test_return_table('("value", 4)'); +ERROR: malformed record literal: "value" +SELECT * FROM test_return_table('("value", 4)'); +ERROR: malformed record literal: "value" +-- [ERROR] From Python non-empty list, wrong number of columns +SELECT test_return_table('[ [False] ]'); +ERROR: length of returned sequence did not match number of columns in row +SELECT * FROM test_return_table('[ [False] ]'); +ERROR: length of returned sequence did not match number of columns in row +-- [ERROR] From Python non-empty list, wrong datatypes +SELECT test_return_table('[ [4, "value"] ]'); +ERROR: invalid input syntax for integer: "value" +SELECT * FROM test_return_table('[ [4, "value"] ]'); +ERROR: invalid input syntax for integer: "value" +-- [ERROR] From Python non-empty list, containing None +SELECT test_return_table('[ None, ["value", 4] ]'); + test_return_table +------------------- + + (value,4) +(2 rows) + +SELECT * FROM test_return_table('[ None, ["value", 4] ]'); +ERROR: function returning set of rows cannot return null value +-- [ERROR] From Python dict without the needed columns +SELECT test_return_table('[ {None: None} ]'); +ERROR: key "first" not found in mapping +SELECT * FROM test_return_table('[ {None: None} ]'); +ERROR: key "first" not found in mapping +-- [ERROR] From Python List of String (one character per column) +SELECT test_return_table('["a4"]'); +ERROR: malformed record literal: "a4" +SELECT * FROM test_return_table('["a4"]'); +ERROR: malformed record literal: "a4" +-- Questionable Success conditions +-- From Python empty string +SELECT test_return_table('""'); + test_return_table +------------------- +(0 rows) + +SELECT * FROM test_return_table('""'); + first | second +-------+-------- +(0 rows) + +-- From Python empty dict +SELECT test_return_table('{}'); + test_return_table +------------------- +(0 rows) + +SELECT * FROM test_return_table('{}'); + first | second +-------+-------- +(0 rows) + +-- From Python Dictionary, with extra fields +SELECT test_return_table('[{"first": "value", "second": 4, "third": "ignored"}]'); + test_return_table +------------------- + (value,4) +(1 row) + +SELECT * FROM test_return_table('[{"first": "value", "second": 4, "third": "ignored"}]'); + first | second +-------+-------- + value | 4 +(1 row) + +-- +-- Cleanup +-- +DROP TABLE gp_single_row; diff --git a/src/pl/plpython/init_file b/src/pl/plpython/init_file index d8c1869b4044..6203e13d145f 100644 --- a/src/pl/plpython/init_file +++ b/src/pl/plpython/init_file @@ -12,6 +12,17 @@ s/"subtransaction_exit_subtransaction_in_with", line \d+// m/with plpy\.subtransaction\(\) as s:/ s/with plpy\.subtransaction\(\) as s:/s.__exit__(None, None, None)/ +# new add for plpython3 +m/ERROR: invalid input syntax for integer:.*/ +s/ERROR: invalid input syntax for integer:.*// +m/ERROR: length of returned sequence did not match number of columns in row.*/ +s/ERROR: length of returned sequence did not match number of columns in row.*// +m/(seg\d+.*)/ +s/(seg\d+.*)// +# for python3 different version have different SyntaxError so we ignore it. +m/ERROR: SyntaxError:.*/ +m/ERROR: SyntaxError:.*// + -- end_matchsubs # Copied from src/test/regress/init_file: diff --git a/src/pl/plpython3/Makefile b/src/pl/plpython3/Makefile index 2b63decf03a7..3752adfa1245 100644 --- a/src/pl/plpython3/Makefile +++ b/src/pl/plpython3/Makefile @@ -153,6 +153,8 @@ REGRESS := $(foreach test,$(REGRESS),$(if $(filter $(test),$(REGRESS_PLPYTHON3_M .PHONY: pgregress-python3-mangle pgregress-python3-mangle: $(MKDIR_P) sql/python3 expected/python3 results/python3 + # for python and python3 types and returns test are not the same. + cp -f ../plpython/expected/plpython_returns_3.out ../plpython/expected/python3/plpython_returns.out for file in $(patsubst %,$(srcdir)/sql/%.sql,$(REGRESS_PLPYTHON3_MANGLE)) $(patsubst %,$(srcdir)/expected/%*.out,$(REGRESS_PLPYTHON3_MANGLE)); do \ sed -e 's/except \([[:alpha:]][[:alpha:].]*\), *\([[:alpha:]][[:alpha:]]*\):/except \1 as \2:/g' \ -e "s///g" \