From 8e76ccde28901f96678f21a2487af3b6a433ee35 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Wed, 3 Jun 2015 19:52:08 -0400 Subject: [PATCH] PL/Python: Make tests pass with Python 3.5 The error message wording for AttributeError has changed in Python 3.5. For the plpython_error test, add a new expected file. In the plpython_subtransaction test, we didn't really care what the exception is, only that it is something coming from Python. So use a generic exception instead, which has a message that doesn't vary across versions. --- src/pl/plpython/expected/README | 1 + src/pl/plpython/expected/plpython_error_5.out | 428 +++++++++++++++++++++ .../plpython/expected/plpython_subtransaction.out | 12 +- .../expected/plpython_subtransaction_0.out | 8 +- .../expected/plpython_subtransaction_5.out | 8 +- src/pl/plpython/sql/plpython_subtransaction.sql | 4 +- 6 files changed, 445 insertions(+), 16 deletions(-) create mode 100644 src/pl/plpython/expected/plpython_error_5.out diff --git a/src/pl/plpython/expected/README b/src/pl/plpython/expected/README index 5bf9366..b890563 100644 --- a/src/pl/plpython/expected/README +++ b/src/pl/plpython/expected/README @@ -1,6 +1,7 @@ Guide to alternative expected files: plpython_error_0.out Python 2.4 and older +plpython_error_5.out Python 3.5 and newer plpython_unicode.out server encoding != SQL_ASCII plpython_unicode_3.out server encoding == SQL_ASCII diff --git a/src/pl/plpython/expected/plpython_error_5.out b/src/pl/plpython/expected/plpython_error_5.out new file mode 100644 index 0000000..fcd944c --- /dev/null +++ b/src/pl/plpython/expected/plpython_error_5.out @@ -0,0 +1,428 @@ +-- 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 plpython3u; +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 plpython3u; +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 plpython3u; +SELECT sql_syntax_error(); +ERROR: spiexceptions.SyntaxError: syntax error at or near "syntax" +LINE 1: syntax error + ^ +QUERY: syntax error +CONTEXT: Traceback (most recent call last): + PL/Python function "sql_syntax_error", line 1, in + plpy.execute("syntax error") +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 plpython3u; +SELECT exception_index_invalid('test'); +ERROR: IndexError: list index out of range +CONTEXT: Traceback (most recent call last): + PL/Python function "exception_index_invalid", line 1, in + return args[1] +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 plpython3u; +SELECT exception_index_invalid_nested(); +ERROR: spiexceptions.UndefinedFunction: 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: Traceback (most recent call last): + PL/Python function "exception_index_invalid_nested", line 1, in + rv = plpy.execute("SELECT test5('foo')") +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 plpython3u; +SELECT invalid_type_uncaught('rick'); +ERROR: spiexceptions.UndefinedObject: type "test" does not exist +CONTEXT: Traceback (most recent call last): + PL/Python function "invalid_type_uncaught", line 3, in + SD["plan"] = plpy.prepare(q, [ "test" ]) +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 as ex: + plpy.notice(str(ex)) + return None +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; +SELECT invalid_type_caught('rick'); +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 as ex: + plpy.error(str(ex)) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; +SELECT invalid_type_reraised('rick'); +ERROR: plpy.Error: type "test" does not exist +CONTEXT: Traceback (most recent call last): + PL/Python function "invalid_type_reraised", line 6, in + plpy.error(str(ex)) +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 plpython3u; +SELECT valid_type('rick'); + valid_type +------------ + +(1 row) + +/* error in nested functions to get a traceback +*/ +CREATE FUNCTION nested_error() RETURNS text + AS +'def fun1(): + plpy.error("boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "not reached" +' + LANGUAGE plpython3u; +SELECT nested_error(); +ERROR: plpy.Error: boom +CONTEXT: Traceback (most recent call last): + PL/Python function "nested_error", line 10, in + fun3() + PL/Python function "nested_error", line 8, in fun3 + fun2() + PL/Python function "nested_error", line 5, in fun2 + fun1() + PL/Python function "nested_error", line 2, in fun1 + plpy.error("boom") +PL/Python function "nested_error" +/* raising plpy.Error is just like calling plpy.error +*/ +CREATE FUNCTION nested_error_raise() RETURNS text + AS +'def fun1(): + raise plpy.Error("boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "not reached" +' + LANGUAGE plpython3u; +SELECT nested_error_raise(); +ERROR: plpy.Error: boom +CONTEXT: Traceback (most recent call last): + PL/Python function "nested_error_raise", line 10, in + fun3() + PL/Python function "nested_error_raise", line 8, in fun3 + fun2() + PL/Python function "nested_error_raise", line 5, in fun2 + fun1() + PL/Python function "nested_error_raise", line 2, in fun1 + raise plpy.Error("boom") +PL/Python function "nested_error_raise" +/* using plpy.warning should not produce a traceback +*/ +CREATE FUNCTION nested_warning() RETURNS text + AS +'def fun1(): + plpy.warning("boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "you''ve been warned" +' + LANGUAGE plpython3u; +SELECT nested_warning(); +WARNING: boom +CONTEXT: PL/Python function "nested_warning" + nested_warning +-------------------- + you've been warned +(1 row) + +/* AttributeError at toplevel used to give segfaults with the traceback +*/ +CREATE FUNCTION toplevel_attribute_error() RETURNS void AS +$$ +plpy.nonexistent +$$ LANGUAGE plpython3u; +SELECT toplevel_attribute_error(); +ERROR: AttributeError: module 'plpy' has no attribute 'nonexistent' +CONTEXT: Traceback (most recent call last): + PL/Python function "toplevel_attribute_error", line 2, in + plpy.nonexistent +PL/Python function "toplevel_attribute_error" +/* Calling PL/Python functions from SQL and vice versa should not lose context. + */ +CREATE OR REPLACE FUNCTION python_traceback() RETURNS void AS $$ +def first(): + second() + +def second(): + third() + +def third(): + plpy.execute("select sql_error()") + +first() +$$ LANGUAGE plpython3u; +CREATE OR REPLACE FUNCTION sql_error() RETURNS void AS $$ +begin + select 1/0; +end +$$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION python_from_sql_error() RETURNS void AS $$ +begin + select python_traceback(); +end +$$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION sql_from_python_error() RETURNS void AS $$ +plpy.execute("select sql_error()") +$$ LANGUAGE plpython3u; +SELECT python_traceback(); +ERROR: spiexceptions.DivisionByZero: division by zero +CONTEXT: Traceback (most recent call last): + PL/Python function "python_traceback", line 11, in + first() + PL/Python function "python_traceback", line 3, in first + second() + PL/Python function "python_traceback", line 6, in second + third() + PL/Python function "python_traceback", line 9, in third + plpy.execute("select sql_error()") +PL/Python function "python_traceback" +SELECT sql_error(); +ERROR: division by zero +CONTEXT: SQL statement "select 1/0" +PL/pgSQL function sql_error() line 3 at SQL statement +SELECT python_from_sql_error(); +ERROR: spiexceptions.DivisionByZero: division by zero +CONTEXT: Traceback (most recent call last): + PL/Python function "python_traceback", line 11, in + first() + PL/Python function "python_traceback", line 3, in first + second() + PL/Python function "python_traceback", line 6, in second + third() + PL/Python function "python_traceback", line 9, in third + plpy.execute("select sql_error()") +PL/Python function "python_traceback" +SQL statement "select python_traceback()" +PL/pgSQL function python_from_sql_error() line 3 at SQL statement +SELECT sql_from_python_error(); +ERROR: spiexceptions.DivisionByZero: division by zero +CONTEXT: Traceback (most recent call last): + PL/Python function "sql_from_python_error", line 2, in + plpy.execute("select sql_error()") +PL/Python function "sql_from_python_error" +/* check catching specific types of exceptions + */ +CREATE TABLE specific ( + i integer PRIMARY KEY +); +CREATE FUNCTION specific_exception(i integer) RETURNS void AS +$$ +from plpy import spiexceptions +try: + plpy.execute("insert into specific values (%s)" % (i or "NULL")); +except spiexceptions.NotNullViolation as e: + plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate) +except spiexceptions.UniqueViolation as e: + plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate) +$$ LANGUAGE plpython3u; +SELECT specific_exception(2); + specific_exception +-------------------- + +(1 row) + +SELECT specific_exception(NULL); +NOTICE: Violated the NOT NULL constraint, sqlstate 23502 +CONTEXT: PL/Python function "specific_exception" + specific_exception +-------------------- + +(1 row) + +SELECT specific_exception(2); +NOTICE: Violated the UNIQUE constraint, sqlstate 23505 +CONTEXT: PL/Python function "specific_exception" + specific_exception +-------------------- + +(1 row) + +/* SPI errors in PL/Python functions should preserve the SQLSTATE value + */ +CREATE FUNCTION python_unique_violation() RETURNS void AS $$ +plpy.execute("insert into specific values (1)") +plpy.execute("insert into specific values (1)") +$$ LANGUAGE plpython3u; +CREATE FUNCTION catch_python_unique_violation() RETURNS text AS $$ +begin + begin + perform python_unique_violation(); + exception when unique_violation then + return 'ok'; + end; + return 'not reached'; +end; +$$ language plpgsql; +SELECT catch_python_unique_violation(); + catch_python_unique_violation +------------------------------- + ok +(1 row) + +/* manually starting subtransactions - a bad idea + */ +CREATE FUNCTION manual_subxact() RETURNS void AS $$ +plpy.execute("savepoint save") +plpy.execute("create table foo(x integer)") +plpy.execute("rollback to save") +$$ LANGUAGE plpython3u; +SELECT manual_subxact(); +ERROR: plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION +CONTEXT: Traceback (most recent call last): + PL/Python function "manual_subxact", line 2, in + plpy.execute("savepoint save") +PL/Python function "manual_subxact" +/* same for prepared plans + */ +CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$ +save = plpy.prepare("savepoint save") +rollback = plpy.prepare("rollback to save") +plpy.execute(save) +plpy.execute("create table foo(x integer)") +plpy.execute(rollback) +$$ LANGUAGE plpython3u; +SELECT manual_subxact_prepared(); +ERROR: plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION +CONTEXT: Traceback (most recent call last): + PL/Python function "manual_subxact_prepared", line 4, in + plpy.execute(save) +PL/Python function "manual_subxact_prepared" +/* raising plpy.spiexception.* from python code should preserve sqlstate + */ +CREATE FUNCTION plpy_raise_spiexception() RETURNS void AS $$ +raise plpy.spiexceptions.DivisionByZero() +$$ LANGUAGE plpython3u; +DO $$ +BEGIN + SELECT plpy_raise_spiexception(); +EXCEPTION WHEN division_by_zero THEN + -- NOOP +END +$$ LANGUAGE plpgsql; +/* setting a custom sqlstate should be handled + */ +CREATE FUNCTION plpy_raise_spiexception_override() RETURNS void AS $$ +exc = plpy.spiexceptions.DivisionByZero() +exc.sqlstate = 'SILLY' +raise exc +$$ LANGUAGE plpython3u; +DO $$ +BEGIN + SELECT plpy_raise_spiexception_override(); +EXCEPTION WHEN SQLSTATE 'SILLY' THEN + -- NOOP +END +$$ LANGUAGE plpgsql; diff --git a/src/pl/plpython/expected/plpython_subtransaction.out b/src/pl/plpython/expected/plpython_subtransaction.out index ced4682..c7bf6cc 100644 --- a/src/pl/plpython/expected/plpython_subtransaction.out +++ b/src/pl/plpython/expected/plpython_subtransaction.out @@ -19,7 +19,7 @@ try: if what_error == "SPI": plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')") elif what_error == "Python": - plpy.attribute_error + raise Exception("Python exception") except: exc = False subxact.__exit__(*sys.exc_info()) @@ -58,10 +58,10 @@ SELECT * FROM subtransaction_tbl; TRUNCATE subtransaction_tbl; SELECT subtransaction_test('Python'); -ERROR: AttributeError: 'module' object has no attribute 'attribute_error' +ERROR: Exception: Python exception CONTEXT: Traceback (most recent call last): PL/Python function "subtransaction_test", line 13, in - plpy.attribute_error + raise Exception("Python exception") PL/Python function "subtransaction_test" SELECT * FROM subtransaction_tbl; i @@ -78,7 +78,7 @@ with plpy.subtransaction(): if what_error == "SPI": plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')") elif what_error == "Python": - plpy.attribute_error + raise Exception("Python exception") $$ LANGUAGE plpythonu; SELECT subtransaction_ctx_test(); subtransaction_ctx_test @@ -110,10 +110,10 @@ SELECT * FROM subtransaction_tbl; TRUNCATE subtransaction_tbl; SELECT subtransaction_ctx_test('Python'); -ERROR: AttributeError: 'module' object has no attribute 'attribute_error' +ERROR: Exception: Python exception CONTEXT: Traceback (most recent call last): PL/Python function "subtransaction_ctx_test", line 8, in - plpy.attribute_error + raise Exception("Python exception") PL/Python function "subtransaction_ctx_test" SELECT * FROM subtransaction_tbl; i diff --git a/src/pl/plpython/expected/plpython_subtransaction_0.out b/src/pl/plpython/expected/plpython_subtransaction_0.out index 6f4be55..73bd724 100644 --- a/src/pl/plpython/expected/plpython_subtransaction_0.out +++ b/src/pl/plpython/expected/plpython_subtransaction_0.out @@ -19,7 +19,7 @@ try: if what_error == "SPI": plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')") elif what_error == "Python": - plpy.attribute_error + raise Exception("Python exception") except: exc = False subxact.__exit__(*sys.exc_info()) @@ -58,10 +58,10 @@ SELECT * FROM subtransaction_tbl; TRUNCATE subtransaction_tbl; SELECT subtransaction_test('Python'); -ERROR: AttributeError: 'module' object has no attribute 'attribute_error' +ERROR: Exception: Python exception CONTEXT: Traceback (most recent call last): PL/Python function "subtransaction_test", line 13, in - plpy.attribute_error + raise Exception("Python exception") PL/Python function "subtransaction_test" SELECT * FROM subtransaction_tbl; i @@ -78,7 +78,7 @@ with plpy.subtransaction(): if what_error == "SPI": plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')") elif what_error == "Python": - plpy.attribute_error + raise Exception("Python exception") $$ LANGUAGE plpythonu; ERROR: could not compile PL/Python function "subtransaction_ctx_test" DETAIL: SyntaxError: invalid syntax (line 3) diff --git a/src/pl/plpython/expected/plpython_subtransaction_5.out b/src/pl/plpython/expected/plpython_subtransaction_5.out index 4ca1835..d177620 100644 --- a/src/pl/plpython/expected/plpython_subtransaction_5.out +++ b/src/pl/plpython/expected/plpython_subtransaction_5.out @@ -19,7 +19,7 @@ try: if what_error == "SPI": plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')") elif what_error == "Python": - plpy.attribute_error + raise Exception("Python exception") except: exc = False subxact.__exit__(*sys.exc_info()) @@ -58,10 +58,10 @@ SELECT * FROM subtransaction_tbl; TRUNCATE subtransaction_tbl; SELECT subtransaction_test('Python'); -ERROR: AttributeError: 'module' object has no attribute 'attribute_error' +ERROR: Exception: Python exception CONTEXT: Traceback (most recent call last): PL/Python function "subtransaction_test", line 13, in - plpy.attribute_error + raise Exception("Python exception") PL/Python function "subtransaction_test" SELECT * FROM subtransaction_tbl; i @@ -78,7 +78,7 @@ with plpy.subtransaction(): if what_error == "SPI": plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')") elif what_error == "Python": - plpy.attribute_error + raise Exception("Python exception") $$ LANGUAGE plpythonu; ERROR: could not compile PL/Python function "subtransaction_ctx_test" DETAIL: SyntaxError: invalid syntax (, line 3) diff --git a/src/pl/plpython/sql/plpython_subtransaction.sql b/src/pl/plpython/sql/plpython_subtransaction.sql index 9ad6377..3c188e3 100644 --- a/src/pl/plpython/sql/plpython_subtransaction.sql +++ b/src/pl/plpython/sql/plpython_subtransaction.sql @@ -23,7 +23,7 @@ try: if what_error == "SPI": plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')") elif what_error == "Python": - plpy.attribute_error + raise Exception("Python exception") except: exc = False subxact.__exit__(*sys.exc_info()) @@ -53,7 +53,7 @@ with plpy.subtransaction(): if what_error == "SPI": plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')") elif what_error == "Python": - plpy.attribute_error + raise Exception("Python exception") $$ LANGUAGE plpythonu; SELECT subtransaction_ctx_test(); -- 2.5.0