]> granicus.if.org Git - postgresql/commitdiff
Handle SPIErrors raised directly in PL/Python code.
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Mon, 28 Jan 2013 07:40:20 +0000 (09:40 +0200)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Mon, 28 Jan 2013 07:46:23 +0000 (09:46 +0200)
If a PL/Python function raises an SPIError (or one if its subclasses)
directly with python's raise statement, treat it the same as an SPIError
generated internally. In particular, if the user sets the sqlstate
attribute, preserve that.

Oskari Saarenmaa and Jan UrbaƄski, reviewed by Karl O. Pinc.

src/pl/plpython/expected/plpython_error.out
src/pl/plpython/expected/plpython_error_0.out
src/pl/plpython/plpy_elog.c
src/pl/plpython/sql/plpython_error.sql

index e1ec9c2c1322b5861f248f8cd0dca0a73faa63cd..be2ec9708ad1fb0e067308c780e1242e21b309a6 100644 (file)
@@ -400,3 +400,29 @@ CONTEXT:  Traceback (most recent call last):
   PL/Python function "manual_subxact_prepared", line 4, in <module>
     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 plpythonu;
+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 plpythonu;
+DO $$
+BEGIN
+       SELECT plpy_raise_spiexception_override();
+EXCEPTION WHEN SQLSTATE 'SILLY' THEN
+       -- NOOP
+END
+$$ LANGUAGE plpgsql;
index 6f74a5038f08a67905f7622317691723e49d3f6f..39c63c547a41d1ade08bfd31ed6dbb39d04889dd 100644 (file)
@@ -400,3 +400,29 @@ CONTEXT:  Traceback (most recent call last):
   PL/Python function "manual_subxact_prepared", line 4, in <module>
     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 plpythonu;
+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 plpythonu;
+DO $$
+BEGIN
+       SELECT plpy_raise_spiexception_override();
+EXCEPTION WHEN SQLSTATE 'SILLY' THEN
+       -- NOOP
+END
+$$ LANGUAGE plpgsql;
index c375ac07fa8f12a2f7189a702e777fba956933e3..70450d7d9e5e5ce2b1101b97202a78858188110e 100644 (file)
@@ -336,6 +336,31 @@ PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
        Py_DECREF(e);
 }
 
+/*
+ * Extract error code from SPIError's sqlstate attribute.
+ */
+static void
+PLy_get_spi_sqlerrcode(PyObject *exc, int *sqlerrcode)
+{
+       PyObject        *sqlstate;
+       char            *buffer;
+
+       sqlstate = PyObject_GetAttrString(exc, "sqlstate");
+       if (sqlstate == NULL)
+               return;
+
+       buffer = PyString_AsString(sqlstate);
+       if (strlen(buffer) == 5 &&
+               strspn(buffer, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 5)
+       {
+               *sqlerrcode = MAKE_SQLSTATE(buffer[0], buffer[1], buffer[2],
+                                                                       buffer[3], buffer[4]);
+       }
+
+       Py_DECREF(sqlstate);
+}
+
+
 /*
  * Extract the error data from a SPIError
  */
@@ -345,13 +370,20 @@ PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hin
        PyObject   *spidata = NULL;
 
        spidata = PyObject_GetAttrString(exc, "spidata");
-       if (!spidata)
-               goto cleanup;
 
-       if (!PyArg_ParseTuple(spidata, "izzzi", sqlerrcode, detail, hint, query, position))
-               goto cleanup;
+       if (spidata != NULL)
+       {
+               PyArg_ParseTuple(spidata, "izzzi", sqlerrcode, detail, hint, query, position);
+       }
+       else
+       {
+               /*
+                * If there's no spidata, at least set the sqlerrcode. This can happen
+                * if someone explicitly raises a SPI exception from Python code.
+                */
+               PLy_get_spi_sqlerrcode(exc, sqlerrcode);
+       }
 
-cleanup:
        PyErr_Clear();
        /* no elog here, we simply won't report the errhint, errposition etc */
        Py_XDECREF(spidata);
index 502bbec38f45858b7d89d1294175a64ce37fb786..d0df7e607d3e1ae32321d731ca88b56f9c74ec81 100644 (file)
@@ -298,3 +298,33 @@ plpy.execute(rollback)
 $$ LANGUAGE plpythonu;
 
 SELECT 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 plpythonu;
+
+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 plpythonu;
+
+DO $$
+BEGIN
+       SELECT plpy_raise_spiexception_override();
+EXCEPTION WHEN SQLSTATE 'SILLY' THEN
+       -- NOOP
+END
+$$ LANGUAGE plpgsql;