Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 116ce2f

Browse files
committed
Get rid of the global variable holding the error state
Global error handling led to confusion and was hard to manage. With this change, errors from PostgreSQL are immediately reported to Python as exceptions. This requires setting a Python exception after reporting the caught PostgreSQL error as a warning, because PLy_elog destroys the Python exception state. Ideally, all places where PostgreSQL errors need to be reported back to Python should be wrapped in subtransactions, to make going back to Python from a longjmp safe. This will be handled in a separate patch. Jan Urbański
1 parent 37eb2cd commit 116ce2f

File tree

3 files changed

+114
-74
lines changed

3 files changed

+114
-74
lines changed

src/pl/plpython/expected/plpython_error.out

+10-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ CREATE FUNCTION sql_syntax_error() RETURNS text
1010
SELECT sql_syntax_error();
1111
WARNING: PL/Python: plpy.SPIError: unrecognized error in PLy_spi_execute_query
1212
CONTEXT: PL/Python function "sql_syntax_error"
13-
ERROR: syntax error at or near "syntax"
13+
ERROR: PL/Python: plpy.SPIError: syntax error at or near "syntax"
1414
LINE 1: syntax error
1515
^
1616
QUERY: syntax error
@@ -34,7 +34,7 @@ return rv[0]'
3434
SELECT exception_index_invalid_nested();
3535
WARNING: PL/Python: plpy.SPIError: unrecognized error in PLy_spi_execute_query
3636
CONTEXT: PL/Python function "exception_index_invalid_nested"
37-
ERROR: function test5(unknown) does not exist
37+
ERROR: PL/Python: plpy.SPIError: function test5(unknown) does not exist
3838
LINE 1: SELECT test5('foo')
3939
^
4040
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
@@ -56,7 +56,7 @@ return None
5656
SELECT invalid_type_uncaught('rick');
5757
WARNING: PL/Python: plpy.SPIError: unrecognized error in PLy_spi_prepare
5858
CONTEXT: PL/Python function "invalid_type_uncaught"
59-
ERROR: type "test" does not exist
59+
ERROR: PL/Python: plpy.SPIError: type "test" does not exist
6060
CONTEXT: PL/Python function "invalid_type_uncaught"
6161
/* for what it's worth catch the exception generated by
6262
* the typo, and return None
@@ -79,8 +79,13 @@ return None
7979
SELECT invalid_type_caught('rick');
8080
WARNING: PL/Python: plpy.SPIError: unrecognized error in PLy_spi_prepare
8181
CONTEXT: PL/Python function "invalid_type_caught"
82-
ERROR: type "test" does not exist
82+
NOTICE: type "test" does not exist
8383
CONTEXT: PL/Python function "invalid_type_caught"
84+
invalid_type_caught
85+
---------------------
86+
87+
(1 row)
88+
8489
/* for what it's worth catch the exception generated by
8590
* the typo, and reraise it as a plain error
8691
*/
@@ -101,7 +106,7 @@ return None
101106
SELECT invalid_type_reraised('rick');
102107
WARNING: PL/Python: plpy.SPIError: unrecognized error in PLy_spi_prepare
103108
CONTEXT: PL/Python function "invalid_type_reraised"
104-
ERROR: type "test" does not exist
109+
ERROR: PL/Python: plpy.Error: type "test" does not exist
105110
CONTEXT: PL/Python function "invalid_type_reraised"
106111
/* no typo no messing about
107112
*/

src/pl/plpython/expected/plpython_test.out

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,5 @@ NOTICE: notice
7373
CONTEXT: PL/Python function "elog_test"
7474
WARNING: warning
7575
CONTEXT: PL/Python function "elog_test"
76-
ERROR: error
76+
ERROR: PL/Python: plpy.Error: error
7777
CONTEXT: PL/Python function "elog_test"

src/pl/plpython/plpython.c

+103-68
Original file line numberDiff line numberDiff line change
@@ -284,13 +284,17 @@ PLy_exception_set_plural(PyObject *, const char *, const char *,
284284
__attribute__((format(printf, 2, 5)))
285285
__attribute__((format(printf, 3, 5)));
286286

287+
/* like PLy_exception_set, but conserve more fields from ErrorData */
288+
static void PLy_spi_exception_set(ErrorData *edata);
289+
287290
/* Get the innermost python procedure called from the backend */
288291
static char *PLy_procedure_name(PLyProcedure *);
289292

290293
/* some utility functions */
291294
static void
292295
PLy_elog(int, const char *,...)
293296
__attribute__((format(printf, 2, 3)));
297+
static void PLy_get_spi_error_data(PyObject *exc, char **hint, char **query, int *position);
294298
static char *PLy_traceback(int *);
295299

296300
static void *PLy_malloc(size_t);
@@ -364,19 +368,6 @@ static HeapTuple PLyObject_ToTuple(PLyTypeInfo *, PyObject *);
364368
*/
365369
static PLyProcedure *PLy_curr_procedure = NULL;
366370

367-
/*
368-
* When a callback from Python into PG incurs an error, we temporarily store
369-
* the error information here, and return NULL to the Python interpreter.
370-
* Any further callback attempts immediately fail, and when the Python
371-
* interpreter returns to the calling function, we re-throw the error (even if
372-
* Python thinks it trapped the error and doesn't return NULL). Eventually
373-
* this ought to be improved to let Python code really truly trap the error,
374-
* but that's more of a change from the pre-8.0 semantics than I have time for
375-
* now --- it will only be possible if the callback query is executed inside a
376-
* subtransaction.
377-
*/
378-
static ErrorData *PLy_error_in_progress = NULL;
379-
380371
static PyObject *PLy_interp_globals = NULL;
381372
static PyObject *PLy_interp_safe_globals = NULL;
382373
static HTAB *PLy_procedure_cache = NULL;
@@ -597,7 +588,6 @@ PLy_trigger_handler(FunctionCallInfo fcinfo, PLyProcedure *proc)
597588
plrv = PLy_procedure_call(proc, "TD", plargs);
598589

599590
Assert(plrv != NULL);
600-
Assert(!PLy_error_in_progress);
601591

602592
/*
603593
* Disconnect from SPI manager
@@ -1015,7 +1005,6 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *proc)
10151005
PLy_function_delete_args(proc);
10161006
}
10171007
Assert(plrv != NULL);
1018-
Assert(!PLy_error_in_progress);
10191008
}
10201009

10211010
/*
@@ -1194,23 +1183,9 @@ PLy_procedure_call(PLyProcedure *proc, char *kargs, PyObject *vargs)
11941183
rv = PyEval_EvalCode((PyCodeObject *) proc->code,
11951184
proc->globals, proc->globals);
11961185

1197-
/*
1198-
* If there was an error in a PG callback, propagate that no matter what
1199-
* Python claims about its success.
1200-
*/
1201-
if (PLy_error_in_progress)
1202-
{
1203-
ErrorData *edata = PLy_error_in_progress;
1204-
1205-
PLy_error_in_progress = NULL;
1206-
ReThrowError(edata);
1207-
}
1208-
1209-
if (rv == NULL || PyErr_Occurred())
1210-
{
1211-
Py_XDECREF(rv);
1186+
/* If the Python code returned an error, propagate it */
1187+
if (rv == NULL)
12121188
PLy_elog(ERROR, NULL);
1213-
}
12141189

12151190
return rv;
12161191
}
@@ -2862,13 +2837,6 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
28622837
void *tmpplan;
28632838
volatile MemoryContext oldcontext;
28642839

2865-
/* Can't execute more if we have an unhandled error */
2866-
if (PLy_error_in_progress)
2867-
{
2868-
PLy_exception_set(PLy_exc_error, "transaction aborted");
2869-
return NULL;
2870-
}
2871-
28722840
if (!PyArg_ParseTuple(args, "s|O", &query, &list))
28732841
{
28742842
PLy_exception_set(PLy_exc_spi_error,
@@ -2978,15 +2946,18 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
29782946
}
29792947
PG_CATCH();
29802948
{
2949+
ErrorData *edata;
2950+
29812951
MemoryContextSwitchTo(oldcontext);
2982-
PLy_error_in_progress = CopyErrorData();
2952+
edata = CopyErrorData();
29832953
FlushErrorState();
29842954
Py_DECREF(plan);
29852955
Py_XDECREF(optr);
29862956
if (!PyErr_Occurred())
29872957
PLy_exception_set(PLy_exc_spi_error,
29882958
"unrecognized error in PLy_spi_prepare");
29892959
PLy_elog(WARNING, NULL);
2960+
PLy_spi_exception_set(edata);
29902961
return NULL;
29912962
}
29922963
PG_END_TRY();
@@ -3005,13 +2976,6 @@ PLy_spi_execute(PyObject *self, PyObject *args)
30052976
PyObject *list = NULL;
30062977
long limit = 0;
30072978

3008-
/* Can't execute more if we have an unhandled error */
3009-
if (PLy_error_in_progress)
3010-
{
3011-
PLy_exception_set(PLy_exc_error, "transaction aborted");
3012-
return NULL;
3013-
}
3014-
30152979
if (PyArg_ParseTuple(args, "s|l", &query, &limit))
30162980
return PLy_spi_execute_query(query, limit);
30172981

@@ -3116,9 +3080,10 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
31163080
PG_CATCH();
31173081
{
31183082
int k;
3083+
ErrorData *edata;
31193084

31203085
MemoryContextSwitchTo(oldcontext);
3121-
PLy_error_in_progress = CopyErrorData();
3086+
edata = CopyErrorData();
31223087
FlushErrorState();
31233088

31243089
/*
@@ -3138,6 +3103,7 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
31383103
PLy_exception_set(PLy_exc_error,
31393104
"unrecognized error in PLy_spi_execute_plan");
31403105
PLy_elog(WARNING, NULL);
3106+
PLy_spi_exception_set(edata);
31413107
return NULL;
31423108
}
31433109
PG_END_TRY();
@@ -3152,14 +3118,6 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
31523118
}
31533119
}
31543120

3155-
if (rv < 0)
3156-
{
3157-
PLy_exception_set(PLy_exc_spi_error,
3158-
"SPI_execute_plan failed: %s",
3159-
SPI_result_code_string(rv));
3160-
return NULL;
3161-
}
3162-
31633121
return PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv);
31643122
}
31653123

@@ -3177,13 +3135,16 @@ PLy_spi_execute_query(char *query, long limit)
31773135
}
31783136
PG_CATCH();
31793137
{
3138+
ErrorData *edata;
3139+
31803140
MemoryContextSwitchTo(oldcontext);
3181-
PLy_error_in_progress = CopyErrorData();
3141+
edata = CopyErrorData();
31823142
FlushErrorState();
31833143
if (!PyErr_Occurred())
31843144
PLy_exception_set(PLy_exc_spi_error,
31853145
"unrecognized error in PLy_spi_execute_query");
31863146
PLy_elog(WARNING, NULL);
3147+
PLy_spi_exception_set(edata);
31873148
return NULL;
31883149
}
31893150
PG_END_TRY();
@@ -3244,8 +3205,6 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status)
32443205
PG_CATCH();
32453206
{
32463207
MemoryContextSwitchTo(oldcontext);
3247-
PLy_error_in_progress = CopyErrorData();
3248-
FlushErrorState();
32493208
if (!PyErr_Occurred())
32503209
PLy_exception_set(PLy_exc_error,
32513210
"unrecognized error in PLy_spi_execute_fetch_result");
@@ -3502,23 +3461,20 @@ PLy_output(volatile int level, PyObject *self, PyObject *args)
35023461
}
35033462
PG_CATCH();
35043463
{
3464+
ErrorData *edata;
3465+
35053466
MemoryContextSwitchTo(oldcontext);
3506-
PLy_error_in_progress = CopyErrorData();
3467+
edata = CopyErrorData();
35073468
FlushErrorState();
35083469

3509-
PyErr_SetString(PLy_exc_error, sv);
3510-
35113470
/*
35123471
* Note: If sv came from PyString_AsString(), it points into storage
35133472
* owned by so. So free so after using sv.
35143473
*/
35153474
Py_XDECREF(so);
35163475

3517-
/*
3518-
* returning NULL here causes the python interpreter to bail. when
3519-
* control passes back to PLy_procedure_call, we check for PG
3520-
* exceptions and re-throw the error.
3521-
*/
3476+
/* Make Python raise the exception */
3477+
PLy_exception_set(PLy_exc_error, edata->message);
35223478
return NULL;
35233479
}
35243480
PG_END_TRY();
@@ -3584,6 +3540,48 @@ PLy_exception_set_plural(PyObject *exc,
35843540
PyErr_SetString(exc, buf);
35853541
}
35863542

3543+
/*
3544+
* Raise a SPIError, passing in it more error details, like the
3545+
* internal query and error position.
3546+
*/
3547+
static void
3548+
PLy_spi_exception_set(ErrorData *edata)
3549+
{
3550+
PyObject *args = NULL;
3551+
PyObject *spierror = NULL;
3552+
PyObject *spidata = NULL;
3553+
3554+
args = Py_BuildValue("(s)", edata->message);
3555+
if (!args)
3556+
goto failure;
3557+
3558+
/* create a new SPIError with the error message as the parameter */
3559+
spierror = PyObject_CallObject(PLy_exc_spi_error, args);
3560+
if (!spierror)
3561+
goto failure;
3562+
3563+
spidata = Py_BuildValue("(zzi)", edata->hint,
3564+
edata->internalquery, edata->internalpos);
3565+
if (!spidata)
3566+
goto failure;
3567+
3568+
if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1)
3569+
goto failure;
3570+
3571+
PyErr_SetObject(PLy_exc_spi_error, spierror);
3572+
3573+
Py_DECREF(args);
3574+
Py_DECREF(spierror);
3575+
Py_DECREF(spidata);
3576+
return;
3577+
3578+
failure:
3579+
Py_XDECREF(args);
3580+
Py_XDECREF(spierror);
3581+
Py_XDECREF(spidata);
3582+
elog(ERROR, "could not convert SPI error to Python exception");
3583+
}
3584+
35873585
/* Emit a PG error or notice, together with any available info about
35883586
* the current Python error, previously set by PLy_exception_set().
35893587
* This should be used to propagate Python errors into PG. If fmt is
@@ -3596,6 +3594,15 @@ PLy_elog(int elevel, const char *fmt,...)
35963594
char *xmsg;
35973595
int xlevel;
35983596
StringInfoData emsg;
3597+
PyObject *exc, *val, *tb;
3598+
char *hint = NULL;
3599+
char *query = NULL;
3600+
int position = 0;
3601+
3602+
PyErr_Fetch(&exc, &val, &tb);
3603+
if (exc != NULL && PyErr_GivenExceptionMatches(val, PLy_exc_spi_error))
3604+
PLy_get_spi_error_data(val, &hint, &query, &position);
3605+
PyErr_Restore(exc, val, tb);
35993606

36003607
xmsg = PLy_traceback(&xlevel);
36013608

@@ -3621,10 +3628,16 @@ PLy_elog(int elevel, const char *fmt,...)
36213628
if (fmt)
36223629
ereport(elevel,
36233630
(errmsg("PL/Python: %s", emsg.data),
3624-
(xmsg) ? errdetail("%s", xmsg) : 0));
3631+
(xmsg) ? errdetail("%s", xmsg) : 0,
3632+
(hint) ? errhint(hint) : 0,
3633+
(query) ? internalerrquery(query) : 0,
3634+
(position) ? internalerrposition(position) : 0));
36253635
else
36263636
ereport(elevel,
3627-
(errmsg("PL/Python: %s", xmsg)));
3637+
(errmsg("PL/Python: %s", xmsg),
3638+
(hint) ? errhint(hint) : 0,
3639+
(query) ? internalerrquery(query) : 0,
3640+
(position) ? internalerrposition(position) : 0));
36283641
}
36293642
PG_CATCH();
36303643
{
@@ -3642,6 +3655,28 @@ PLy_elog(int elevel, const char *fmt,...)
36423655
pfree(xmsg);
36433656
}
36443657

3658+
/*
3659+
* Extract the error data from a SPIError
3660+
*/
3661+
static void
3662+
PLy_get_spi_error_data(PyObject *exc, char **hint, char **query, int *position)
3663+
{
3664+
PyObject *spidata = NULL;
3665+
3666+
spidata = PyObject_GetAttrString(exc, "spidata");
3667+
if (!spidata)
3668+
goto cleanup;
3669+
3670+
if (!PyArg_ParseTuple(spidata, "zzi", hint, query, position))
3671+
goto cleanup;
3672+
3673+
cleanup:
3674+
PyErr_Clear();
3675+
/* no elog here, we simply won't report the errhint, errposition etc */
3676+
Py_XDECREF(spidata);
3677+
}
3678+
3679+
36453680
static char *
36463681
PLy_traceback(int *xlevel)
36473682
{

0 commit comments

Comments
 (0)