diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 35349616bb9d99db9d7845f151a0a8caeeab823d..b630f0d6f8bc8cf054ce7709bd6753261639f6dc 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201101251 +#define CATALOG_VERSION_NO 201102011 #endif diff --git a/src/include/catalog/pg_pltemplate.h b/src/include/catalog/pg_pltemplate.h index d987ddc40ee3cde3ee0b78ca7a7a17ab291c49c4..c0578f92c098d7115b1926b4d0088a6387b85ad5 100644 --- a/src/include/catalog/pg_pltemplate.h +++ b/src/include/catalog/pg_pltemplate.h @@ -72,8 +72,8 @@ DATA(insert ( "pltcl" t t "pltcl_call_handler" _null_ _null_ "$libdir/pltcl" _n DATA(insert ( "pltclu" f f "pltclu_call_handler" _null_ _null_ "$libdir/pltcl" _null_ )); DATA(insert ( "plperl" t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" "$libdir/plperl" _null_ )); DATA(insert ( "plperlu" f f "plperl_call_handler" "plperl_inline_handler" "plperl_validator" "$libdir/plperl" _null_ )); -DATA(insert ( "plpythonu" f f "plpython_call_handler" "plpython_inline_handler" _null_ "$libdir/plpython" _null_ )); -DATA(insert ( "plpython2u" f f "plpython_call_handler" "plpython_inline_handler" _null_ "$libdir/plpython2" _null_ )); -DATA(insert ( "plpython3u" f f "plpython3_call_handler" "plpython3_inline_handler" _null_ "$libdir/plpython3" _null_ )); +DATA(insert ( "plpythonu" f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" "$libdir/plpython" _null_ )); +DATA(insert ( "plpython2u" f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" "$libdir/plpython2" _null_ )); +DATA(insert ( "plpython3u" f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" "$libdir/plpython3" _null_ )); #endif /* PG_PLTEMPLATE_H */ diff --git a/src/pl/plpython/expected/README b/src/pl/plpython/expected/README index f0115280909191d7f4ac998b140a1320fa128073..27c995db9a666894a6078abaa6a654b74eb1f154 100644 --- a/src/pl/plpython/expected/README +++ b/src/pl/plpython/expected/README @@ -1,5 +1,7 @@ Guide to alternative expected files: +plpython_error_0.out Python 2.4 and older + plpython_unicode.out any version, when server encoding != SQL_ASCII and client encoding = UTF8; else ... plpython_unicode_0.out any version, when server encoding != SQL_ASCII and client encoding != UTF8; else ... plpython_unicode_2.out Python 2.2 diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out index ea4a54c3d754a272f6ce2d1016716ba7c7d92194..2b6141c376ee9d58787f9015148195a599eadb37 100644 --- a/src/pl/plpython/expected/plpython_error.out +++ b/src/pl/plpython/expected/plpython_error.out @@ -1,6 +1,30 @@ -- test error handling, i forgot to restore Warn_restart in -- the trigger handler once. the errors and subsequent core dump were -- interesting. +/* Flat out Python syntax error + */ +CREATE FUNCTION python_syntax_error() RETURNS text + AS +'.syntaxerror' + LANGUAGE plpythonu; +ERROR: could not compile PL/Python function "python_syntax_error" +DETAIL: SyntaxError: invalid syntax (<string>, line 2) +/* With check_function_bodies = false the function should get defined + * and the error reported when called + */ +SET check_function_bodies = false; +CREATE FUNCTION python_syntax_error() RETURNS text + AS +'.syntaxerror' + LANGUAGE plpythonu; +SELECT python_syntax_error(); +ERROR: could not compile PL/Python function "python_syntax_error" +DETAIL: SyntaxError: invalid syntax (<string>, line 2) +/* Run the function twice to check if the hashtable entry gets cleaned up */ +SELECT python_syntax_error(); +ERROR: could not compile PL/Python function "python_syntax_error" +DETAIL: SyntaxError: invalid syntax (<string>, line 2) +RESET check_function_bodies; /* Flat out syntax error */ CREATE FUNCTION sql_syntax_error() RETURNS text diff --git a/src/pl/plpython/expected/plpython_error_0.out b/src/pl/plpython/expected/plpython_error_0.out new file mode 100644 index 0000000000000000000000000000000000000000..3842d8fb4a1acfeda27a24eb926425eeab03ca08 --- /dev/null +++ b/src/pl/plpython/expected/plpython_error_0.out @@ -0,0 +1,152 @@ +-- test error handling, i forgot to restore Warn_restart in +-- the trigger handler once. the errors and subsequent core dump were +-- interesting. +/* Flat out Python syntax error + */ +CREATE FUNCTION python_syntax_error() RETURNS text + AS +'.syntaxerror' + LANGUAGE plpythonu; +ERROR: could not compile PL/Python function "python_syntax_error" +DETAIL: SyntaxError: invalid syntax (line 2) +/* With check_function_bodies = false the function should get defined + * and the error reported when called + */ +SET check_function_bodies = false; +CREATE FUNCTION python_syntax_error() RETURNS text + AS +'.syntaxerror' + LANGUAGE plpythonu; +SELECT python_syntax_error(); +ERROR: could not compile PL/Python function "python_syntax_error" +DETAIL: SyntaxError: invalid syntax (line 2) +/* Run the function twice to check if the hashtable entry gets cleaned up */ +SELECT python_syntax_error(); +ERROR: could not compile PL/Python function "python_syntax_error" +DETAIL: SyntaxError: invalid syntax (line 2) +RESET check_function_bodies; +/* Flat out syntax error + */ +CREATE FUNCTION sql_syntax_error() RETURNS text + AS +'plpy.execute("syntax error")' + LANGUAGE plpythonu; +SELECT sql_syntax_error(); +WARNING: plpy.SPIError: unrecognized error in PLy_spi_execute_query +CONTEXT: PL/Python function "sql_syntax_error" +ERROR: plpy.SPIError: syntax error at or near "syntax" +LINE 1: syntax error + ^ +QUERY: syntax error +CONTEXT: PL/Python function "sql_syntax_error" +/* check the handling of uncaught python exceptions + */ +CREATE FUNCTION exception_index_invalid(text) RETURNS text + AS +'return args[1]' + LANGUAGE plpythonu; +SELECT exception_index_invalid('test'); +ERROR: IndexError: list index out of range +CONTEXT: PL/Python function "exception_index_invalid" +/* check handling of nested exceptions + */ +CREATE FUNCTION exception_index_invalid_nested() RETURNS text + AS +'rv = plpy.execute("SELECT test5(''foo'')") +return rv[0]' + LANGUAGE plpythonu; +SELECT exception_index_invalid_nested(); +WARNING: plpy.SPIError: unrecognized error in PLy_spi_execute_query +CONTEXT: PL/Python function "exception_index_invalid_nested" +ERROR: plpy.SPIError: function test5(unknown) does not exist +LINE 1: SELECT test5('foo') + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +QUERY: SELECT test5('foo') +CONTEXT: PL/Python function "exception_index_invalid_nested" +/* a typo + */ +CREATE FUNCTION invalid_type_uncaught(a text) RETURNS text + AS +'if "plan" not in SD: + q = "SELECT fname FROM users WHERE lname = $1" + SD["plan"] = plpy.prepare(q, [ "test" ]) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpythonu; +SELECT invalid_type_uncaught('rick'); +WARNING: plpy.SPIError: unrecognized error in PLy_spi_prepare +CONTEXT: PL/Python function "invalid_type_uncaught" +ERROR: plpy.SPIError: type "test" does not exist +CONTEXT: PL/Python function "invalid_type_uncaught" +/* for what it's worth catch the exception generated by + * the typo, and return None + */ +CREATE FUNCTION invalid_type_caught(a text) RETURNS text + AS +'if "plan" not in SD: + q = "SELECT fname FROM users WHERE lname = $1" + try: + SD["plan"] = plpy.prepare(q, [ "test" ]) + except plpy.SPIError, ex: + plpy.notice(str(ex)) + return None +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpythonu; +SELECT invalid_type_caught('rick'); +WARNING: plpy.SPIError: unrecognized error in PLy_spi_prepare +CONTEXT: PL/Python function "invalid_type_caught" +NOTICE: type "test" does not exist +CONTEXT: PL/Python function "invalid_type_caught" + invalid_type_caught +--------------------- + +(1 row) + +/* for what it's worth catch the exception generated by + * the typo, and reraise it as a plain error + */ +CREATE FUNCTION invalid_type_reraised(a text) RETURNS text + AS +'if "plan" not in SD: + q = "SELECT fname FROM users WHERE lname = $1" + try: + SD["plan"] = plpy.prepare(q, [ "test" ]) + except plpy.SPIError, ex: + plpy.error(str(ex)) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpythonu; +SELECT invalid_type_reraised('rick'); +WARNING: plpy.SPIError: unrecognized error in PLy_spi_prepare +CONTEXT: PL/Python function "invalid_type_reraised" +ERROR: plpy.Error: type "test" does not exist +CONTEXT: PL/Python function "invalid_type_reraised" +/* no typo no messing about + */ +CREATE FUNCTION valid_type(a text) RETURNS text + AS +'if "plan" not in SD: + SD["plan"] = plpy.prepare("SELECT fname FROM users WHERE lname = $1", [ "text" ]) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpythonu; +SELECT valid_type('rick'); + valid_type +------------ + +(1 row) + diff --git a/src/pl/plpython/expected/plpython_record.out b/src/pl/plpython/expected/plpython_record.out index c8c4f9d5a3cbb02a8d549dff1dd6f7216b8c7d6a..770f764d6f47c6ca5e2bd0a5ba86bbeced2ed073 100644 --- a/src/pl/plpython/expected/plpython_record.out +++ b/src/pl/plpython/expected/plpython_record.out @@ -47,6 +47,7 @@ CREATE FUNCTION test_in_out_params_multi(first in text, second out text, third out text) AS $$ return first + '_record_in_to_out'; $$ LANGUAGE plpythonu; +ERROR: PL/Python functions cannot return type record CREATE FUNCTION test_inout_params(first inout text) AS $$ return first + '_inout'; $$ LANGUAGE plpythonu; @@ -299,7 +300,10 @@ SELECT * FROM test_in_out_params('test_in'); -- this doesn't work yet :-( SELECT * FROM test_in_out_params_multi('test_in'); -ERROR: PL/Python functions cannot return type record +ERROR: function test_in_out_params_multi(unknown) does not exist +LINE 1: SELECT * FROM test_in_out_params_multi('test_in'); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. SELECT * FROM test_inout_params('test_in'); first --------------- diff --git a/src/pl/plpython/expected/plpython_types.out b/src/pl/plpython/expected/plpython_types.out index 982005bea162e080f23a68bb8ba23105acfdf48d..e74a4009d462adb2a38133ffc511dcf2fb3746e9 100644 --- a/src/pl/plpython/expected/plpython_types.out +++ b/src/pl/plpython/expected/plpython_types.out @@ -571,9 +571,13 @@ PL/Python function "test_type_conversion_array_mixed2" CREATE FUNCTION test_type_conversion_array_record() RETURNS type_record[] AS $$ return [None] $$ LANGUAGE plpythonu; -SELECT * FROM test_type_conversion_array_record(); ERROR: PL/Python functions cannot return type type_record[] DETAIL: PL/Python does not support conversion to arrays of row types. +SELECT * FROM test_type_conversion_array_record(); +ERROR: function test_type_conversion_array_record() does not exist +LINE 1: SELECT * FROM test_type_conversion_array_record(); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. CREATE FUNCTION test_type_conversion_array_string() RETURNS text[] AS $$ return 'abc' $$ LANGUAGE plpythonu; diff --git a/src/pl/plpython/expected/plpython_types_3.out b/src/pl/plpython/expected/plpython_types_3.out index eeb23b7b5e790944191468e3aed16734c4c707bc..577c1fff4e997f4729423ce8a9c810f642d1d6be 100644 --- a/src/pl/plpython/expected/plpython_types_3.out +++ b/src/pl/plpython/expected/plpython_types_3.out @@ -571,9 +571,13 @@ PL/Python function "test_type_conversion_array_mixed2" CREATE FUNCTION test_type_conversion_array_record() RETURNS type_record[] AS $$ return [None] $$ LANGUAGE plpython3u; -SELECT * FROM test_type_conversion_array_record(); ERROR: PL/Python functions cannot return type type_record[] DETAIL: PL/Python does not support conversion to arrays of row types. +SELECT * FROM test_type_conversion_array_record(); +ERROR: function test_type_conversion_array_record() does not exist +LINE 1: SELECT * FROM test_type_conversion_array_record(); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. CREATE FUNCTION test_type_conversion_array_string() RETURNS text[] AS $$ return 'abc' $$ LANGUAGE plpython3u; diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index aafe556c04e2677ac8efe51ed930bd067a73fa47..fbfeb2c9f16029cc682f4c557e4ba2990fbc06c3 100644 --- a/src/pl/plpython/plpython.c +++ b/src/pl/plpython/plpython.c @@ -251,15 +251,18 @@ typedef struct PLyResultObject #if PY_MAJOR_VERSION >= 3 /* Use separate names to avoid clash in pg_pltemplate */ +#define plpython_validator plpython3_validator #define plpython_call_handler plpython3_call_handler #define plpython_inline_handler plpython3_inline_handler #endif /* exported functions */ +Datum plpython_validator(PG_FUNCTION_ARGS); Datum plpython_call_handler(PG_FUNCTION_ARGS); Datum plpython_inline_handler(PG_FUNCTION_ARGS); void _PG_init(void); +PG_FUNCTION_INFO_V1(plpython_validator); PG_FUNCTION_INFO_V1(plpython_call_handler); PG_FUNCTION_INFO_V1(plpython_inline_handler); @@ -437,6 +440,42 @@ plpython_return_error_callback(void *arg) errcontext("while creating return value"); } +static bool +PLy_procedure_is_trigger(Form_pg_proc procStruct) +{ + return (procStruct->prorettype == TRIGGEROID || + (procStruct->prorettype == OPAQUEOID && + procStruct->pronargs == 0)); +} + +Datum +plpython_validator(PG_FUNCTION_ARGS) +{ + Oid funcoid = PG_GETARG_OID(0); + HeapTuple tuple; + Form_pg_proc procStruct; + bool is_trigger; + + if (!check_function_bodies) + { + PG_RETURN_VOID(); + } + + /* Get the new function's pg_proc entry */ + tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for function %u", funcoid); + procStruct = (Form_pg_proc) GETSTRUCT(tuple); + + is_trigger = PLy_procedure_is_trigger(procStruct); + + ReleaseSysCache(tuple); + + PLy_procedure_get(funcoid, is_trigger); + + PG_RETURN_VOID(); +} + Datum plpython_call_handler(PG_FUNCTION_ARGS) { diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql index 5ca68495be036f86df939d02c45115d312ce99e0..6509257b24deb355d6599f6ca651544b44694211 100644 --- a/src/pl/plpython/sql/plpython_error.sql +++ b/src/pl/plpython/sql/plpython_error.sql @@ -2,6 +2,29 @@ -- the trigger handler once. the errors and subsequent core dump were -- interesting. +/* Flat out Python syntax error + */ +CREATE FUNCTION python_syntax_error() RETURNS text + AS +'.syntaxerror' + LANGUAGE plpythonu; + +/* With check_function_bodies = false the function should get defined + * and the error reported when called + */ +SET check_function_bodies = false; + +CREATE FUNCTION python_syntax_error() RETURNS text + AS +'.syntaxerror' + LANGUAGE plpythonu; + +SELECT python_syntax_error(); +/* Run the function twice to check if the hashtable entry gets cleaned up */ +SELECT python_syntax_error(); + +RESET check_function_bodies; + /* Flat out syntax error */ CREATE FUNCTION sql_syntax_error() RETURNS text