Skip to content

Commit f21fc7f

Browse files
committed
Preserve SQLSTATE when an SPI error is propagated through PL/python
exception handler. This was a regression in 9.1, when the capability to catch specific SPI errors was added, so backpatch to 9.1. Mika Eloranta, with some editing by Jan Urbański.
1 parent 5df1403 commit f21fc7f

File tree

4 files changed

+72
-6
lines changed

4 files changed

+72
-6
lines changed

src/pl/plpython/expected/plpython_error.out

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,28 @@ CONTEXT: PL/Python function "specific_exception"
351351

352352
(1 row)
353353

354+
/* SPI errors in PL/Python functions should preserve the SQLSTATE value
355+
*/
356+
CREATE FUNCTION python_unique_violation() RETURNS void AS $$
357+
plpy.execute("insert into specific values (1)")
358+
plpy.execute("insert into specific values (1)")
359+
$$ LANGUAGE plpythonu;
360+
CREATE FUNCTION catch_python_unique_violation() RETURNS text AS $$
361+
begin
362+
begin
363+
perform python_unique_violation();
364+
exception when unique_violation then
365+
return 'ok';
366+
end;
367+
return 'not reached';
368+
end;
369+
$$ language plpgsql;
370+
SELECT catch_python_unique_violation();
371+
catch_python_unique_violation
372+
-------------------------------
373+
ok
374+
(1 row)
375+
354376
/* manually starting subtransactions - a bad idea
355377
*/
356378
CREATE FUNCTION manual_subxact() RETURNS void AS $$

src/pl/plpython/expected/plpython_error_0.out

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,28 @@ CONTEXT: PL/Python function "specific_exception"
351351

352352
(1 row)
353353

354+
/* SPI errors in PL/Python functions should preserve the SQLSTATE value
355+
*/
356+
CREATE FUNCTION python_unique_violation() RETURNS void AS $$
357+
plpy.execute("insert into specific values (1)")
358+
plpy.execute("insert into specific values (1)")
359+
$$ LANGUAGE plpythonu;
360+
CREATE FUNCTION catch_python_unique_violation() RETURNS text AS $$
361+
begin
362+
begin
363+
perform python_unique_violation();
364+
exception when unique_violation then
365+
return 'ok';
366+
end;
367+
return 'not reached';
368+
end;
369+
$$ language plpgsql;
370+
SELECT catch_python_unique_violation();
371+
catch_python_unique_violation
372+
-------------------------------
373+
ok
374+
(1 row)
375+
354376
/* manually starting subtransactions - a bad idea
355377
*/
356378
CREATE FUNCTION manual_subxact() RETURNS void AS $$

src/pl/plpython/plpython.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ static char *PLy_procedure_name(PLyProcedure *);
383383
static void
384384
PLy_elog(int, const char *,...)
385385
__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
386-
static void PLy_get_spi_error_data(PyObject *exc, char **detail, char **hint, char **query, int *position);
386+
static void PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint, char **query, int *position);
387387
static void PLy_traceback(char **, char **, int *);
388388

389389
static void *PLy_malloc(size_t);
@@ -4441,7 +4441,7 @@ PLy_spi_exception_set(PyObject *excclass, ErrorData *edata)
44414441
if (!spierror)
44424442
goto failure;
44434443

4444-
spidata = Py_BuildValue("(zzzi)", edata->detail, edata->hint,
4444+
spidata = Py_BuildValue("(izzzi)", edata->sqlerrcode, edata->detail, edata->hint,
44454445
edata->internalquery, edata->internalpos);
44464446
if (!spidata)
44474447
goto failure;
@@ -4481,6 +4481,7 @@ PLy_elog(int elevel, const char *fmt,...)
44814481
*val,
44824482
*tb;
44834483
const char *primary = NULL;
4484+
int sqlerrcode = 0;
44844485
char *detail = NULL;
44854486
char *hint = NULL;
44864487
char *query = NULL;
@@ -4490,7 +4491,7 @@ PLy_elog(int elevel, const char *fmt,...)
44904491
if (exc != NULL)
44914492
{
44924493
if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error))
4493-
PLy_get_spi_error_data(val, &detail, &hint, &query, &position);
4494+
PLy_get_spi_error_data(val, &sqlerrcode, &detail, &hint, &query, &position);
44944495
else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal))
44954496
elevel = FATAL;
44964497
}
@@ -4531,7 +4532,8 @@ PLy_elog(int elevel, const char *fmt,...)
45314532
PG_TRY();
45324533
{
45334534
ereport(elevel,
4534-
(errmsg_internal("%s", primary ? primary : "no exception data"),
4535+
(errcode(sqlerrcode ? sqlerrcode : ERRCODE_INTERNAL_ERROR),
4536+
errmsg_internal("%s", primary ? primary : "no exception data"),
45354537
(detail) ? errdetail_internal("%s", detail) : 0,
45364538
(tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0,
45374539
(hint) ? errhint("%s", hint) : 0,
@@ -4562,15 +4564,15 @@ PLy_elog(int elevel, const char *fmt,...)
45624564
* Extract the error data from a SPIError
45634565
*/
45644566
static void
4565-
PLy_get_spi_error_data(PyObject *exc, char **detail, char **hint, char **query, int *position)
4567+
PLy_get_spi_error_data(PyObject *exc, int* sqlerrcode, char **detail, char **hint, char **query, int *position)
45664568
{
45674569
PyObject *spidata = NULL;
45684570

45694571
spidata = PyObject_GetAttrString(exc, "spidata");
45704572
if (!spidata)
45714573
goto cleanup;
45724574

4573-
if (!PyArg_ParseTuple(spidata, "zzzi", detail, hint, query, position))
4575+
if (!PyArg_ParseTuple(spidata, "izzzi", sqlerrcode, detail, hint, query, position))
45744576
goto cleanup;
45754577

45764578
cleanup:

src/pl/plpython/sql/plpython_error.sql

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,26 @@ SELECT specific_exception(2);
257257
SELECT specific_exception(NULL);
258258
SELECT specific_exception(2);
259259

260+
/* SPI errors in PL/Python functions should preserve the SQLSTATE value
261+
*/
262+
CREATE FUNCTION python_unique_violation() RETURNS void AS $$
263+
plpy.execute("insert into specific values (1)")
264+
plpy.execute("insert into specific values (1)")
265+
$$ LANGUAGE plpythonu;
266+
267+
CREATE FUNCTION catch_python_unique_violation() RETURNS text AS $$
268+
begin
269+
begin
270+
perform python_unique_violation();
271+
exception when unique_violation then
272+
return 'ok';
273+
end;
274+
return 'not reached';
275+
end;
276+
$$ language plpgsql;
277+
278+
SELECT catch_python_unique_violation();
279+
260280
/* manually starting subtransactions - a bad idea
261281
*/
262282
CREATE FUNCTION manual_subxact() RETURNS void AS $$

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy