Skip to content

Commit 316186f

Browse files
committed
Handle SPIErrors raised directly in PL/Python code.
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.
1 parent 96bb29d commit 316186f

File tree

4 files changed

+119
-5
lines changed

4 files changed

+119
-5
lines changed

src/pl/plpython/expected/plpython_error.out

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,3 +400,29 @@ CONTEXT: Traceback (most recent call last):
400400
PL/Python function "manual_subxact_prepared", line 4, in <module>
401401
plpy.execute(save)
402402
PL/Python function "manual_subxact_prepared"
403+
/* raising plpy.spiexception.* from python code should preserve sqlstate
404+
*/
405+
CREATE FUNCTION plpy_raise_spiexception() RETURNS void AS $$
406+
raise plpy.spiexceptions.DivisionByZero()
407+
$$ LANGUAGE plpythonu;
408+
DO $$
409+
BEGIN
410+
SELECT plpy_raise_spiexception();
411+
EXCEPTION WHEN division_by_zero THEN
412+
-- NOOP
413+
END
414+
$$ LANGUAGE plpgsql;
415+
/* setting a custom sqlstate should be handled
416+
*/
417+
CREATE FUNCTION plpy_raise_spiexception_override() RETURNS void AS $$
418+
exc = plpy.spiexceptions.DivisionByZero()
419+
exc.sqlstate = 'SILLY'
420+
raise exc
421+
$$ LANGUAGE plpythonu;
422+
DO $$
423+
BEGIN
424+
SELECT plpy_raise_spiexception_override();
425+
EXCEPTION WHEN SQLSTATE 'SILLY' THEN
426+
-- NOOP
427+
END
428+
$$ LANGUAGE plpgsql;

src/pl/plpython/expected/plpython_error_0.out

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,3 +400,29 @@ CONTEXT: Traceback (most recent call last):
400400
PL/Python function "manual_subxact_prepared", line 4, in <module>
401401
plpy.execute(save)
402402
PL/Python function "manual_subxact_prepared"
403+
/* raising plpy.spiexception.* from python code should preserve sqlstate
404+
*/
405+
CREATE FUNCTION plpy_raise_spiexception() RETURNS void AS $$
406+
raise plpy.spiexceptions.DivisionByZero()
407+
$$ LANGUAGE plpythonu;
408+
DO $$
409+
BEGIN
410+
SELECT plpy_raise_spiexception();
411+
EXCEPTION WHEN division_by_zero THEN
412+
-- NOOP
413+
END
414+
$$ LANGUAGE plpgsql;
415+
/* setting a custom sqlstate should be handled
416+
*/
417+
CREATE FUNCTION plpy_raise_spiexception_override() RETURNS void AS $$
418+
exc = plpy.spiexceptions.DivisionByZero()
419+
exc.sqlstate = 'SILLY'
420+
raise exc
421+
$$ LANGUAGE plpythonu;
422+
DO $$
423+
BEGIN
424+
SELECT plpy_raise_spiexception_override();
425+
EXCEPTION WHEN SQLSTATE 'SILLY' THEN
426+
-- NOOP
427+
END
428+
$$ LANGUAGE plpgsql;

src/pl/plpython/plpy_elog.c

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,31 @@ PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
336336
Py_DECREF(e);
337337
}
338338

339+
/*
340+
* Extract error code from SPIError's sqlstate attribute.
341+
*/
342+
static void
343+
PLy_get_spi_sqlerrcode(PyObject *exc, int *sqlerrcode)
344+
{
345+
PyObject *sqlstate;
346+
char *buffer;
347+
348+
sqlstate = PyObject_GetAttrString(exc, "sqlstate");
349+
if (sqlstate == NULL)
350+
return;
351+
352+
buffer = PyString_AsString(sqlstate);
353+
if (strlen(buffer) == 5 &&
354+
strspn(buffer, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 5)
355+
{
356+
*sqlerrcode = MAKE_SQLSTATE(buffer[0], buffer[1], buffer[2],
357+
buffer[3], buffer[4]);
358+
}
359+
360+
Py_DECREF(sqlstate);
361+
}
362+
363+
339364
/*
340365
* Extract the error data from a SPIError
341366
*/
@@ -345,13 +370,20 @@ PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hin
345370
PyObject *spidata = NULL;
346371

347372
spidata = PyObject_GetAttrString(exc, "spidata");
348-
if (!spidata)
349-
goto cleanup;
350373

351-
if (!PyArg_ParseTuple(spidata, "izzzi", sqlerrcode, detail, hint, query, position))
352-
goto cleanup;
374+
if (spidata != NULL)
375+
{
376+
PyArg_ParseTuple(spidata, "izzzi", sqlerrcode, detail, hint, query, position);
377+
}
378+
else
379+
{
380+
/*
381+
* If there's no spidata, at least set the sqlerrcode. This can happen
382+
* if someone explicitly raises a SPI exception from Python code.
383+
*/
384+
PLy_get_spi_sqlerrcode(exc, sqlerrcode);
385+
}
353386

354-
cleanup:
355387
PyErr_Clear();
356388
/* no elog here, we simply won't report the errhint, errposition etc */
357389
Py_XDECREF(spidata);

src/pl/plpython/sql/plpython_error.sql

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,3 +298,33 @@ plpy.execute(rollback)
298298
$$ LANGUAGE plpythonu;
299299

300300
SELECT manual_subxact_prepared();
301+
302+
/* raising plpy.spiexception.* from python code should preserve sqlstate
303+
*/
304+
CREATE FUNCTION plpy_raise_spiexception() RETURNS void AS $$
305+
raise plpy.spiexceptions.DivisionByZero()
306+
$$ LANGUAGE plpythonu;
307+
308+
DO $$
309+
BEGIN
310+
SELECT plpy_raise_spiexception();
311+
EXCEPTION WHEN division_by_zero THEN
312+
-- NOOP
313+
END
314+
$$ LANGUAGE plpgsql;
315+
316+
/* setting a custom sqlstate should be handled
317+
*/
318+
CREATE FUNCTION plpy_raise_spiexception_override() RETURNS void AS $$
319+
exc = plpy.spiexceptions.DivisionByZero()
320+
exc.sqlstate = 'SILLY'
321+
raise exc
322+
$$ LANGUAGE plpythonu;
323+
324+
DO $$
325+
BEGIN
326+
SELECT plpy_raise_spiexception_override();
327+
EXCEPTION WHEN SQLSTATE 'SILLY' THEN
328+
-- NOOP
329+
END
330+
$$ LANGUAGE plpgsql;

0 commit comments

Comments
 (0)