Hi lists,

It seems that PL/Python exception functions (plpy.error, plpy.fatal)
and exception types (plpy.Error, plpy.Fatal) have never worked as
documented, and the behavior is quite surprising.

This is an attempt at properly documenting the current semantics. I
tested these behaviors on PostgreSQL 8.1.22 as well as 9.1alpha2.

Patch 1 updates the documentation in doc/src/sgml/plpython.sgml
Patch 2 adds tests to src/pl/plpython/sql/plpython_error.sql

Should I submit separate documentation patches for older maintenance
releases? Versions 8.1 - 8.4 contain the same incorrect text, but in
the "Database Access" section.

Regards,
Marti
From a9a83614624760d5adc4d0d31f9daa245b88cd72 Mon Sep 17 00:00:00 2001
From: Marti Raudsepp <ma...@juffo.org>
Date: Tue, 9 Nov 2010 18:19:50 +0200
Subject: [PATCH 1/2] Document current PL/Python exception raising semantics

---
 doc/src/sgml/plpython.sgml |   30 ++++++++++++++++++++----------
 1 files changed, 20 insertions(+), 10 deletions(-)

diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index 9fc8808..320faab 100644
--- a/doc/src/sgml/plpython.sgml
+++ b/doc/src/sgml/plpython.sgml
@@ -950,16 +950,7 @@ $$ LANGUAGE plpythonu;
    <literal>plpy.warning(<replaceable>msg</>)</literal>,
    <literal>plpy.error(<replaceable>msg</>)</literal>, and
    <literal>plpy.fatal(<replaceable>msg</>)</literal>.<indexterm><primary>elog</><secondary>in PL/Python</></indexterm>
-   <function>plpy.error</function> and
-   <function>plpy.fatal</function> actually raise a Python exception
-   which, if uncaught, propagates out to the calling query, causing
-   the current transaction or subtransaction to be aborted.
-   <literal>raise plpy.Error(<replaceable>msg</>)</literal> and
-   <literal>raise plpy.Fatal(<replaceable>msg</>)</literal> are
-   equivalent to calling
-   <function>plpy.error</function> and
-   <function>plpy.fatal</function>, respectively.
-   The other functions only generate messages of different
+   These functions generate messages of different
    priority levels.
    Whether messages of a particular priority are reported to the client,
    written to the server log, or both is controlled by the
@@ -967,6 +958,25 @@ $$ LANGUAGE plpythonu;
    <xref linkend="guc-client-min-messages"> configuration
    variables. See <xref linkend="runtime-config"> for more information.
   </para>
+
+  <para>
+   The <function>plpy.error</function> function actually raises a Python
+   exception which propagates out to the calling query, causing the current
+   transaction or subtransaction to be aborted.  If multiple calls are made,
+   only the last message is reported.  The exception has caveats: these errors
+   cannot be silenced by an except block, and Python exceptions raised in
+   except blocks will not be reported.  The <function>plpy.fatal</function>
+   function immediately aborts execution and terminates the client connection.
+  </para>
+
+  <para>
+   To raise catchable errors, use
+   <literal>raise plpy.Error(<replaceable>msg</>)</literal>,
+   <literal>raise plpy.Fatal(<replaceable>msg</>)</literal> or native Python
+   exceptions.  These have Python exception semantics.  However, an unhandled
+   <literal>plpy.Fatal</literal> will still cause a PostgreSQL ERROR message to
+   be emitted.
+  </para>
  </sect1>
 
  <sect1 id="plpython-envar">
-- 
1.7.3.2

From 7ef8ec6f13906d6305db5142ba2415911e81852f Mon Sep 17 00:00:00 2001
From: Marti Raudsepp <ma...@juffo.org>
Date: Tue, 9 Nov 2010 18:32:22 +0200
Subject: [PATCH 2/2] Add tests for current PL/Python exception behavior

---
 src/pl/plpython/expected/plpython_error.out |  100 +++++++++++++++++++++++++++
 src/pl/plpython/sql/plpython_error.sql      |   89 ++++++++++++++++++++++++
 2 files changed, 189 insertions(+), 0 deletions(-)

diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
index 1f24c13..18b7b99 100644
--- a/src/pl/plpython/expected/plpython_error.out
+++ b/src/pl/plpython/expected/plpython_error.out
@@ -121,3 +121,103 @@ SELECT valid_type('rick');
  
 (1 row)
 
+/* raise plpy.Error */
+CREATE FUNCTION raise_error() RETURNS void
+	AS
+'raise plpy.Error("Raising error")'
+	LANGUAGE plpythonu;
+SELECT raise_error();
+ERROR:  PL/Python: plpy.Error: Raising error
+CONTEXT:  PL/Python function "raise_error"
+/* Broken behavior: raise plpy.Fatal. This is still mapped to PostgreSQL ERROR
+ * log level.
+ */
+CREATE FUNCTION broken_raise_fatal() RETURNS void
+	AS
+'raise plpy.Fatal("Raising fatal")'
+	LANGUAGE plpythonu;
+SELECT broken_raise_fatal();
+ERROR:  PL/Python: plpy.Fatal: Raising fatal
+CONTEXT:  PL/Python function "broken_raise_fatal"
+/* raising plpy.Error can be caught and ignored
+ */
+CREATE FUNCTION catch_error() RETURNS text
+	AS
+'try:
+	raise plpy.Error("catch me if you can!")
+except:
+	plpy.notice("gotcha")
+	return "retval"
+'
+	LANGUAGE plpythonu;
+SELECT catch_error();
+NOTICE:  gotcha
+CONTEXT:  PL/Python function "catch_error"
+ catch_error 
+-------------
+ retval
+(1 row)
+
+/* catch plpy.Fatal by name
+ */
+CREATE FUNCTION catch_fatal() RETURNS text
+	AS
+'try:
+	raise plpy.Fatal("catch me if you can!")
+except plpy.Fatal:
+	return "retval"
+'
+	LANGUAGE plpythonu;
+SELECT catch_fatal();
+ catch_fatal 
+-------------
+ retval
+(1 row)
+
+/* Broken behavior: plpy.error can be caught and the exception handler is
+ * executed, but cannot be silenced
+ */
+CREATE FUNCTION broken_error_catch() RETURNS text
+	AS
+'try:
+	plpy.error("catch me if you can!")
+except:
+	plpy.notice("caught but cannot ignore")
+	return "retval"
+'
+	LANGUAGE plpythonu;
+SELECT broken_error_catch();
+NOTICE:  caught but cannot ignore
+CONTEXT:  PL/Python function "broken_error_catch"
+ERROR:  catch me if you can!
+CONTEXT:  PL/Python function "broken_error_catch"
+/* Broken behavior: plpy.error can be caught, but errors in the exception
+ * handler are ignored
+ */
+CREATE FUNCTION broken_catch_exceptions() RETURNS void
+	AS
+'try:
+	plpy.error("catch me if you can!")
+except:
+	plpy.notice("caught but cannot ignore")
+	this is totally, broken-code
+'
+	LANGUAGE plpythonu;
+SELECT broken_catch_exceptions();
+NOTICE:  caught but cannot ignore
+CONTEXT:  PL/Python function "broken_catch_exceptions"
+ERROR:  catch me if you can!
+CONTEXT:  PL/Python function "broken_catch_exceptions"
+/* plpy.error can be overridden with another message
+ */
+CREATE FUNCTION catch_error_override() RETURNS void
+	AS
+'try:
+	plpy.error("catch me if you can!")
+except:
+	plpy.error("replaced with a new error")
+'
+	LANGUAGE plpythonu;
+SELECT catch_error_override();
+ERROR:  replaced with a new error
+CONTEXT:  PL/Python function "catch_error_override"
diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql
index 5ca6849..99891e3 100644
--- a/src/pl/plpython/sql/plpython_error.sql
+++ b/src/pl/plpython/sql/plpython_error.sql
@@ -107,3 +107,92 @@ return None
 	LANGUAGE plpythonu;
 
 SELECT valid_type('rick');
+
+/* raise plpy.Error */
+CREATE FUNCTION raise_error() RETURNS void
+	AS
+'raise plpy.Error("Raising error")'
+	LANGUAGE plpythonu;
+
+SELECT raise_error();
+
+/* Broken behavior: raise plpy.Fatal. This is still mapped to PostgreSQL ERROR
+ * log level.
+ */
+CREATE FUNCTION broken_raise_fatal() RETURNS void
+	AS
+'raise plpy.Fatal("Raising fatal")'
+	LANGUAGE plpythonu;
+
+SELECT broken_raise_fatal();
+
+/* raising plpy.Error can be caught and ignored
+ */
+CREATE FUNCTION catch_error() RETURNS text
+	AS
+'try:
+	raise plpy.Error("catch me if you can!")
+except:
+	plpy.notice("gotcha")
+	return "retval"
+'
+	LANGUAGE plpythonu;
+
+SELECT catch_error();
+
+/* catch plpy.Fatal by name
+ */
+CREATE FUNCTION catch_fatal() RETURNS text
+	AS
+'try:
+	raise plpy.Fatal("catch me if you can!")
+except plpy.Fatal:
+	return "retval"
+'
+	LANGUAGE plpythonu;
+
+SELECT catch_fatal();
+
+/* Broken behavior: plpy.error can be caught and the exception handler is
+ * executed, but cannot be silenced
+ */
+CREATE FUNCTION broken_error_catch() RETURNS text
+	AS
+'try:
+	plpy.error("catch me if you can!")
+except:
+	plpy.notice("caught but cannot ignore")
+	return "retval"
+'
+	LANGUAGE plpythonu;
+
+SELECT broken_error_catch();
+
+/* Broken behavior: plpy.error can be caught, but errors in the exception
+ * handler are ignored
+ */
+CREATE FUNCTION broken_catch_exceptions() RETURNS void
+	AS
+'try:
+	plpy.error("catch me if you can!")
+except:
+	plpy.notice("caught but cannot ignore")
+	this is totally, broken-code
+'
+	LANGUAGE plpythonu;
+
+SELECT broken_catch_exceptions();
+
+/* plpy.error can be overridden with another message
+ */
+CREATE FUNCTION catch_error_override() RETURNS void
+	AS
+'try:
+	plpy.error("catch me if you can!")
+except:
+	plpy.error("replaced with a new error")
+'
+	LANGUAGE plpythonu;
+
+SELECT catch_error_override();
+
-- 
1.7.3.2

-- 
Sent via pgsql-docs mailing list (pgsql-docs@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-docs

Reply via email to