1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-31 22:04:40 +03:00

Enhanced custom error in PLPythonu

Patch adds a new, more rich,  way to emit error message or exception from
PL/Pythonu code.

Author: Pavel Stehule
Reviewers: Catalin Iacob, Peter Eisentraut, Jim Nasby
This commit is contained in:
Teodor Sigaev
2016-04-08 18:30:25 +03:00
parent 5364b357fb
commit 5c3c3cd0a3
7 changed files with 702 additions and 82 deletions

View File

@ -48,7 +48,7 @@ select module_contents();
Error, Fatal, SPIError, cursor, debug, error, execute, fatal, info, log, notice, prepare, quote_ident, quote_literal, quote_nullable, spiexceptions, subtransaction, warning
(1 row)
CREATE FUNCTION elog_test() RETURNS void
CREATE FUNCTION elog_test_basic() RETURNS void
AS $$
plpy.debug('debug')
plpy.log('log')
@ -60,7 +60,7 @@ plpy.notice('notice')
plpy.warning('warning')
plpy.error('error')
$$ LANGUAGE plpythonu;
SELECT elog_test();
SELECT elog_test_basic();
INFO: info
INFO: 37
INFO: ()
@ -69,6 +69,193 @@ NOTICE: notice
WARNING: warning
ERROR: plpy.Error: error
CONTEXT: Traceback (most recent call last):
PL/Python function "elog_test", line 10, in <module>
PL/Python function "elog_test_basic", line 10, in <module>
plpy.error('error')
PL/Python function "elog_test_basic"
CREATE FUNCTION elog_test() RETURNS void
AS $$
plpy.debug('debug', detail = 'some detail')
plpy.log('log', detail = 'some detail')
plpy.info('info', detail = 'some detail')
plpy.info()
plpy.info('the question', detail = 42);
plpy.info('This is message text.',
detail = 'This is detail text',
hint = 'This is hint text.',
sqlstate = 'XX000',
schema = 'any info about schema',
table = 'any info about table',
column = 'any info about column',
datatype = 'any info about datatype',
constraint = 'any info about constraint')
plpy.notice('notice', detail = 'some detail')
plpy.warning('warning', detail = 'some detail')
plpy.error('stop on error', detail = 'some detail', hint = 'some hint')
$$ LANGUAGE plpythonu;
SELECT elog_test();
INFO: info
DETAIL: some detail
INFO: ()
INFO: the question
DETAIL: 42
INFO: This is message text.
DETAIL: This is detail text
HINT: This is hint text.
NOTICE: notice
DETAIL: some detail
WARNING: warning
DETAIL: some detail
ERROR: plpy.Error: stop on error
DETAIL: some detail
HINT: some hint
CONTEXT: Traceback (most recent call last):
PL/Python function "elog_test", line 18, in <module>
plpy.error('stop on error', detail = 'some detail', hint = 'some hint')
PL/Python function "elog_test"
do $$ plpy.info('other types', detail = (10,20)) $$ LANGUAGE plpythonu;
INFO: other types
DETAIL: (10, 20)
do $$
import time;
from datetime import date
plpy.info('other types', detail = date(2016,2,26))
$$ LANGUAGE plpythonu;
INFO: other types
DETAIL: 2016-02-26
do $$
basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
plpy.info('other types', detail = basket)
$$ LANGUAGE plpythonu;
INFO: other types
DETAIL: ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
-- should fail
do $$ plpy.info('wrong sqlstate', sqlstate='54444A') $$ LANGUAGE plpythonu;
ERROR: invalid SQLSTATE code
CONTEXT: PL/Python anonymous code block
do $$ plpy.info('unsupported argument', blabla='fooboo') $$ LANGUAGE plpythonu;
ERROR: 'blabla' is an invalid keyword argument for this function
CONTEXT: PL/Python anonymous code block
do $$ plpy.info('first message', message='second message') $$ LANGUAGE plpythonu;
ERROR: the message is already specified
CONTEXT: PL/Python anonymous code block
do $$ plpy.info('first message', 'second message', message='third message') $$ LANGUAGE plpythonu;
ERROR: the message is already specified
CONTEXT: PL/Python anonymous code block
-- raise exception in python, handle exception in plgsql
CREATE OR REPLACE FUNCTION raise_exception(_message text, _detail text DEFAULT NULL, _hint text DEFAULT NULL,
_sqlstate text DEFAULT NULL,
_schema text DEFAULT NULL, _table text DEFAULT NULL, _column text DEFAULT NULL,
_datatype text DEFAULT NULL, _constraint text DEFAULT NULL)
RETURNS void AS $$
kwargs = { "message":_message, "detail":_detail, "hint":_hint,
"sqlstate":_sqlstate, "schema":_schema, "table":_table,
"column":_column, "datatype":_datatype, "constraint":_constraint }
# ignore None values
plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
$$ LANGUAGE plpythonu;
SELECT raise_exception('hello', 'world');
ERROR: plpy.Error: hello
DETAIL: world
CONTEXT: Traceback (most recent call last):
PL/Python function "raise_exception", line 6, in <module>
plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
PL/Python function "raise_exception"
SELECT raise_exception('message text', 'detail text', _sqlstate => 'YY333');
ERROR: plpy.Error: message text
DETAIL: detail text
CONTEXT: Traceback (most recent call last):
PL/Python function "raise_exception", line 6, in <module>
plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
PL/Python function "raise_exception"
SELECT raise_exception(_message => 'message text',
_detail => 'detail text',
_hint => 'hint text',
_sqlstate => 'XX555',
_schema => 'schema text',
_table => 'table text',
_column => 'column text',
_datatype => 'datatype text',
_constraint => 'constraint text');
ERROR: plpy.Error: message text
DETAIL: detail text
HINT: hint text
CONTEXT: Traceback (most recent call last):
PL/Python function "raise_exception", line 6, in <module>
plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
PL/Python function "raise_exception"
SELECT raise_exception(_message => 'message text',
_hint => 'hint text',
_schema => 'schema text',
_column => 'column text',
_constraint => 'constraint text');
ERROR: plpy.Error: message text
HINT: hint text
CONTEXT: Traceback (most recent call last):
PL/Python function "raise_exception", line 6, in <module>
plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
PL/Python function "raise_exception"
DO $$
DECLARE
__message text;
__detail text;
__hint text;
__sqlstate text;
__schema_name text;
__table_name text;
__column_name text;
__datatype text;
__constraint text;
BEGIN
BEGIN
PERFORM raise_exception(_message => 'message text',
_detail => 'detail text',
_hint => 'hint text',
_sqlstate => 'XX555',
_schema => 'schema text',
_table => 'table text',
_column => 'column text',
_datatype => 'datatype text',
_constraint => 'constraint text');
EXCEPTION WHEN SQLSTATE 'XX555' THEN
GET STACKED DIAGNOSTICS __message = MESSAGE_TEXT,
__detail = PG_EXCEPTION_DETAIL,
__hint = PG_EXCEPTION_HINT,
__sqlstate = RETURNED_SQLSTATE,
__schema_name = SCHEMA_NAME,
__table_name = TABLE_NAME,
__column_name = COLUMN_NAME,
__datatype = PG_DATATYPE_NAME,
__constraint = CONSTRAINT_NAME;
RAISE NOTICE 'handled exception'
USING DETAIL = format('message:(%s), detail:(%s), hint: (%s), sqlstate: (%s), '
'schema:(%s), table:(%s), column:(%s), datatype:(%s), constraint:(%s)',
__message, __detail, __hint, __sqlstate, __schema_name,
__table_name, __column_name, __datatype, __constraint);
END;
END;
$$;
NOTICE: handled exception
DETAIL: message:(plpy.Error: message text), detail:(detail text), hint: (hint text), sqlstate: (XX555), schema:(schema text), table:(table text), column:(column text), datatype:(datatype text), constraint:(constraint text)
-- the displayed context is different between Python2 and Python3,
-- but that's not important for this test
\set SHOW_CONTEXT never
do $$
try:
plpy.execute("select raise_exception(_message => 'my message', _sqlstate => 'XX987', _hint => 'some hint', _table=> 'users_tab', _datatype => 'user_type')")
except Exception, e:
plpy.info(e.spidata)
raise e
$$ LANGUAGE plpythonu;
INFO: (119577128, None, 'some hint', None, 0, None, 'users_tab', None, 'user_type', None)
ERROR: plpy.SPIError: plpy.Error: my message
HINT: some hint
do $$
try:
plpy.error(message = 'my message', sqlstate = 'XX987', hint = 'some hint', table = 'users_tab', datatype = 'user_type')
except Exception, e:
plpy.info('sqlstate: %s, hint: %s, tablename: %s, datatype: %s' % (e.sqlstate, e.hint, e.table_name, e.datatype_name))
raise e
$$ LANGUAGE plpythonu;
INFO: sqlstate: XX987, hint: some hint, tablename: users_tab, datatype: user_type
ERROR: plpy.Error: my message
HINT: some hint

View File

@ -23,9 +23,16 @@ PyObject *PLy_exc_spi_error = NULL;
static void PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth);
static void PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail,
char **hint, char **query, int *position);
char **hint, char **query, int *position,
char **schema_name, char **table_name, char **column_name,
char **datatype_name, char **constraint_name);
static void PLy_get_error_data(PyObject *exc, int *sqlerrcode, char **detail,
char **hint, char **schema_name, char **table_name, char **column_name,
char **datatype_name, char **constraint_name);
static char *get_source_line(const char *src, int lineno);
static void get_string_attr(PyObject *obj, char *attrname, char **str);
static bool set_string_attr(PyObject *obj, char *attrname, char *str);
/*
* Emit a PG error or notice, together with any available info about
@ -51,12 +58,23 @@ PLy_elog(int elevel, const char *fmt,...)
char *hint = NULL;
char *query = NULL;
int position = 0;
char *schema_name = NULL;
char *table_name = NULL;
char *column_name = NULL;
char *datatype_name = NULL;
char *constraint_name = NULL;
PyErr_Fetch(&exc, &val, &tb);
if (exc != NULL)
{
if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error))
PLy_get_spi_error_data(val, &sqlerrcode, &detail, &hint, &query, &position);
PLy_get_spi_error_data(val, &sqlerrcode, &detail, &hint, &query, &position,
&schema_name, &table_name, &column_name,
&datatype_name, &constraint_name);
else if (PyErr_GivenExceptionMatches(val, PLy_exc_error))
PLy_get_error_data(val, &sqlerrcode, &detail, &hint,
&schema_name, &table_name, &column_name,
&datatype_name, &constraint_name);
else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal))
elevel = FATAL;
}
@ -103,7 +121,12 @@ PLy_elog(int elevel, const char *fmt,...)
(tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0,
(hint) ? errhint("%s", hint) : 0,
(query) ? internalerrquery(query) : 0,
(position) ? internalerrposition(position) : 0));
(position) ? internalerrposition(position) : 0,
(schema_name) ? err_generic_string(PG_DIAG_SCHEMA_NAME, schema_name) : 0,
(table_name) ? err_generic_string(PG_DIAG_TABLE_NAME, table_name) : 0,
(column_name) ? err_generic_string(PG_DIAG_COLUMN_NAME, column_name) : 0,
(datatype_name) ? err_generic_string(PG_DIAG_DATATYPE_NAME, datatype_name) : 0,
(constraint_name) ? err_generic_string(PG_DIAG_CONSTRAINT_NAME, constraint_name) : 0));
}
PG_CATCH();
{
@ -340,7 +363,7 @@ PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
* Extract error code from SPIError's sqlstate attribute.
*/
static void
PLy_get_spi_sqlerrcode(PyObject *exc, int *sqlerrcode)
PLy_get_sqlerrcode(PyObject *exc, int *sqlerrcode)
{
PyObject *sqlstate;
char *buffer;
@ -360,12 +383,14 @@ PLy_get_spi_sqlerrcode(PyObject *exc, int *sqlerrcode)
Py_DECREF(sqlstate);
}
/*
* Extract the error data from a SPIError
*/
static void
PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint, char **query, int *position)
PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail,
char **hint, char **query, int *position,
char **schema_name, char **table_name, char **column_name,
char **datatype_name, char **constraint_name)
{
PyObject *spidata = NULL;
@ -373,7 +398,9 @@ PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hin
if (spidata != NULL)
{
PyArg_ParseTuple(spidata, "izzzi", sqlerrcode, detail, hint, query, position);
PyArg_ParseTuple(spidata, "izzzizzzzz", sqlerrcode, detail, hint, query, position,
schema_name, table_name, column_name,
datatype_name, constraint_name);
}
else
{
@ -381,7 +408,7 @@ PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hin
* 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);
PLy_get_sqlerrcode(exc, sqlerrcode);
}
PyErr_Clear();
@ -389,6 +416,30 @@ PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hin
Py_XDECREF(spidata);
}
/*
* Extract the error data from an Error.
* Note: position and query attributes are never set for Error so, unlike
* PLy_get_spi_error_data, this function doesn't return them.
*/
static void
PLy_get_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint,
char **schema_name, char **table_name, char **column_name,
char **datatype_name, char **constraint_name)
{
PLy_get_sqlerrcode(exc, sqlerrcode);
get_string_attr(exc, "detail", detail);
get_string_attr(exc, "hint", hint);
get_string_attr(exc, "schema_name", schema_name);
get_string_attr(exc, "table_name", table_name);
get_string_attr(exc, "column_name", column_name);
get_string_attr(exc, "datatype_name", datatype_name);
get_string_attr(exc, "constraint_name", constraint_name);
PyErr_Clear();
/* no elog here, we simply won't report the errhint, errposition etc */
}
/*
* Get the given source line as a palloc'd string
*/
@ -464,3 +515,103 @@ PLy_exception_set_plural(PyObject *exc,
PyErr_SetString(exc, buf);
}
/* set attributes of the given exception to details from ErrorData */
void
PLy_exception_set_with_details(PyObject *excclass, ErrorData *edata)
{
PyObject *args = NULL;
PyObject *error = NULL;
args = Py_BuildValue("(s)", edata->message);
if (!args)
goto failure;
/* create a new exception with the error message as the parameter */
error = PyObject_CallObject(excclass, args);
if (!error)
goto failure;
if (!set_string_attr(error, "sqlstate",
unpack_sql_state(edata->sqlerrcode)))
goto failure;
if (!set_string_attr(error, "detail", edata->detail))
goto failure;
if (!set_string_attr(error, "hint", edata->hint))
goto failure;
if (!set_string_attr(error, "query", edata->internalquery))
goto failure;
if (!set_string_attr(error, "schema_name", edata->schema_name))
goto failure;
if (!set_string_attr(error, "table_name", edata->table_name))
goto failure;
if (!set_string_attr(error, "column_name", edata->column_name))
goto failure;
if (!set_string_attr(error, "datatype_name", edata->datatype_name))
goto failure;
if (!set_string_attr(error, "constraint_name", edata->constraint_name))
goto failure;
PyErr_SetObject(excclass, error);
Py_DECREF(args);
Py_DECREF(error);
return;
failure:
Py_XDECREF(args);
Py_XDECREF(error);
elog(ERROR, "could not convert error to Python exception");
}
/* get string value of an object attribute */
static void
get_string_attr(PyObject *obj, char *attrname, char **str)
{
PyObject *val;
val = PyObject_GetAttrString(obj, attrname);
if (val != NULL && val != Py_None)
{
*str = pstrdup(PyString_AsString(val));
}
Py_XDECREF(val);
}
/* set an object attribute to a string value, returns true when the set was
* successful
*/
static bool
set_string_attr(PyObject *obj, char *attrname, char *str)
{
int result;
PyObject *val;
if (str != NULL)
{
val = PyString_FromString(str);
if (!val)
return false;
}
else
{
val = Py_None;
Py_INCREF(Py_None);
}
result = PyObject_SetAttrString(obj, attrname, val);
Py_DECREF(val);
return result != -1;
}

View File

@ -17,4 +17,6 @@ extern void PLy_exception_set(PyObject *exc, const char *fmt,...) pg_attribute_p
extern void PLy_exception_set_plural(PyObject *exc, const char *fmt_singular, const char *fmt_plural,
unsigned long n,...) pg_attribute_printf(2, 5) pg_attribute_printf(3, 5);
extern void PLy_exception_set_with_details(PyObject *excclass, ErrorData *edata);
#endif /* PLPY_ELOG_H */

View File

@ -28,13 +28,13 @@ static void PLy_add_exceptions(PyObject *plpy);
static void PLy_generate_spi_exceptions(PyObject *mod, PyObject *base);
/* module functions */
static PyObject *PLy_debug(PyObject *self, PyObject *args);
static PyObject *PLy_log(PyObject *self, PyObject *args);
static PyObject *PLy_info(PyObject *self, PyObject *args);
static PyObject *PLy_notice(PyObject *self, PyObject *args);
static PyObject *PLy_warning(PyObject *self, PyObject *args);
static PyObject *PLy_error(PyObject *self, PyObject *args);
static PyObject *PLy_fatal(PyObject *self, PyObject *args);
static PyObject *PLy_debug(PyObject *self, PyObject *args, PyObject *kw);
static PyObject *PLy_log(PyObject *self, PyObject *args, PyObject *kw);
static PyObject *PLy_info(PyObject *self, PyObject *args, PyObject *kw);
static PyObject *PLy_notice(PyObject *self, PyObject *args, PyObject *kw);
static PyObject *PLy_warning(PyObject *self, PyObject *args, PyObject *kw);
static PyObject *PLy_error(PyObject *self, PyObject *args, PyObject *kw);
static PyObject *PLy_fatal(PyObject *self, PyObject *args, PyObject *kw);
static PyObject *PLy_quote_literal(PyObject *self, PyObject *args);
static PyObject *PLy_quote_nullable(PyObject *self, PyObject *args);
static PyObject *PLy_quote_ident(PyObject *self, PyObject *args);
@ -57,13 +57,13 @@ static PyMethodDef PLy_methods[] = {
/*
* logging methods
*/
{"debug", PLy_debug, METH_VARARGS, NULL},
{"log", PLy_log, METH_VARARGS, NULL},
{"info", PLy_info, METH_VARARGS, NULL},
{"notice", PLy_notice, METH_VARARGS, NULL},
{"warning", PLy_warning, METH_VARARGS, NULL},
{"error", PLy_error, METH_VARARGS, NULL},
{"fatal", PLy_fatal, METH_VARARGS, NULL},
{"debug", (PyCFunction) PLy_debug, METH_VARARGS|METH_KEYWORDS, NULL},
{"log", (PyCFunction) PLy_log, METH_VARARGS|METH_KEYWORDS, NULL},
{"info", (PyCFunction) PLy_info, METH_VARARGS|METH_KEYWORDS, NULL},
{"notice", (PyCFunction) PLy_notice, METH_VARARGS|METH_KEYWORDS, NULL},
{"warning", (PyCFunction) PLy_warning, METH_VARARGS|METH_KEYWORDS, NULL},
{"error", (PyCFunction) PLy_error, METH_VARARGS|METH_KEYWORDS, NULL},
{"fatal", (PyCFunction) PLy_fatal, METH_VARARGS|METH_KEYWORDS, NULL},
/*
* create a stored plan
@ -271,48 +271,49 @@ PLy_generate_spi_exceptions(PyObject *mod, PyObject *base)
* the python interface to the elog function
* don't confuse these with PLy_elog
*/
static PyObject *PLy_output(volatile int, PyObject *, PyObject *);
static PyObject *PLy_output(volatile int level, PyObject *self,
PyObject *args, PyObject *kw);
static PyObject *
PLy_debug(PyObject *self, PyObject *args)
PLy_debug(PyObject *self, PyObject *args, PyObject *kw)
{
return PLy_output(DEBUG2, self, args);
return PLy_output(DEBUG2, self, args, kw);
}
static PyObject *
PLy_log(PyObject *self, PyObject *args)
PLy_log(PyObject *self, PyObject *args, PyObject *kw)
{
return PLy_output(LOG, self, args);
return PLy_output(LOG, self, args, kw);
}
static PyObject *
PLy_info(PyObject *self, PyObject *args)
PLy_info(PyObject *self, PyObject *args, PyObject *kw)
{
return PLy_output(INFO, self, args);
return PLy_output(INFO, self, args, kw);
}
static PyObject *
PLy_notice(PyObject *self, PyObject *args)
PLy_notice(PyObject *self, PyObject *args, PyObject *kw)
{
return PLy_output(NOTICE, self, args);
return PLy_output(NOTICE, self, args, kw);
}
static PyObject *
PLy_warning(PyObject *self, PyObject *args)
PLy_warning(PyObject *self, PyObject *args, PyObject *kw)
{
return PLy_output(WARNING, self, args);
return PLy_output(WARNING, self, args, kw);
}
static PyObject *
PLy_error(PyObject *self, PyObject *args)
PLy_error(PyObject *self, PyObject *args, PyObject *kw)
{
return PLy_output(ERROR, self, args);
return PLy_output(ERROR, self, args, kw);
}
static PyObject *
PLy_fatal(PyObject *self, PyObject *args)
PLy_fatal(PyObject *self, PyObject *args, PyObject *kw)
{
return PLy_output(FATAL, self, args);
return PLy_output(FATAL, self, args, kw);
}
static PyObject *
@ -368,12 +369,45 @@ PLy_quote_ident(PyObject *self, PyObject *args)
return ret;
}
static PyObject *
PLy_output(volatile int level, PyObject *self, PyObject *args)
/* enforce cast of object to string */
static char *
object_to_string(PyObject *obj)
{
PyObject *volatile so;
char *volatile sv;
volatile MemoryContext oldcontext;
if (obj)
{
PyObject *so = PyObject_Str(obj);
if (so != NULL)
{
char *str;
str = pstrdup(PyString_AsString(so));
Py_DECREF(so);
return str;
}
}
return NULL;
}
static PyObject *
PLy_output(volatile int level, PyObject *self, PyObject *args, PyObject *kw)
{
int sqlstate = 0;
char *volatile sqlstatestr = NULL;
char *volatile message = NULL;
char *volatile detail = NULL;
char *volatile hint = NULL;
char *volatile column = NULL;
char *volatile constraint = NULL;
char *volatile datatype = NULL;
char *volatile table = NULL;
char *volatile schema = NULL;
MemoryContext oldcontext ;
PyObject *key, *value;
PyObject *volatile so;
Py_ssize_t pos = 0;
if (PyTuple_Size(args) == 1)
{
@ -389,40 +423,118 @@ PLy_output(volatile int level, PyObject *self, PyObject *args)
}
else
so = PyObject_Str(args);
if (so == NULL || ((sv = PyString_AsString(so)) == NULL))
if (so == NULL || ((message = pstrdup(PyString_AsString(so))) == NULL))
{
level = ERROR;
sv = dgettext(TEXTDOMAIN, "could not parse error message in plpy.elog");
message = dgettext(TEXTDOMAIN, "could not parse error message in plpy.elog");
}
Py_XDECREF(so);
if (kw != NULL)
{
while (PyDict_Next(kw, &pos, &key, &value))
{
char *keyword = PyString_AsString(key);
if (strcmp(keyword, "message") == 0)
{
/* the message should not be overwriten */
if (PyTuple_Size(args) != 0)
PLy_elog(ERROR, "the message is already specified");
pfree(message);
message = object_to_string(value);
}
else if (strcmp(keyword, "detail") == 0)
detail = object_to_string(value);
else if (strcmp(keyword, "hint") == 0)
hint = object_to_string(value);
else if (strcmp(keyword, "sqlstate") == 0)
sqlstatestr = object_to_string(value);
else if (strcmp(keyword, "schema") == 0)
schema = object_to_string(value);
else if (strcmp(keyword, "table") == 0)
table = object_to_string(value);
else if (strcmp(keyword, "column") == 0)
column = object_to_string(value);
else if (strcmp(keyword, "datatype") == 0)
datatype = object_to_string(value);
else if (strcmp(keyword, "constraint") == 0)
constraint = object_to_string(value);
else
PLy_elog(ERROR, "'%s' is an invalid keyword argument for this function",
keyword);
}
}
if (sqlstatestr != NULL)
{
if (strlen(sqlstatestr) != 5)
PLy_elog(ERROR, "invalid SQLSTATE code");
if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5)
PLy_elog(ERROR, "invalid SQLSTATE code");
sqlstate = MAKE_SQLSTATE(sqlstatestr[0],
sqlstatestr[1],
sqlstatestr[2],
sqlstatestr[3],
sqlstatestr[4]);
}
oldcontext = CurrentMemoryContext;
PG_TRY();
{
pg_verifymbstr(sv, strlen(sv), false);
elog(level, "%s", sv);
if (message != NULL)
pg_verifymbstr(message, strlen(message), false);
if (detail != NULL)
pg_verifymbstr(detail, strlen(detail), false);
if (hint != NULL)
pg_verifymbstr(hint, strlen(hint), false);
if (schema != NULL)
pg_verifymbstr(schema, strlen(schema), false);
if (table != NULL)
pg_verifymbstr(table, strlen(table), false);
if (column != NULL)
pg_verifymbstr(column, strlen(column), false);
if (datatype != NULL)
pg_verifymbstr(datatype, strlen(datatype), false);
if (constraint != NULL)
pg_verifymbstr(constraint, strlen(constraint), false);
ereport(level,
((sqlstate != 0) ? errcode(sqlstate) : 0,
(message != NULL) ? errmsg_internal("%s", message) : 0,
(detail != NULL) ? errdetail_internal("%s", detail) : 0,
(hint != NULL) ? errhint("%s", hint) : 0,
(column != NULL) ?
err_generic_string(PG_DIAG_COLUMN_NAME, column) : 0,
(constraint != NULL) ?
err_generic_string(PG_DIAG_CONSTRAINT_NAME, constraint) : 0,
(datatype != NULL) ?
err_generic_string(PG_DIAG_DATATYPE_NAME, datatype) : 0,
(table != NULL) ?
err_generic_string(PG_DIAG_TABLE_NAME, table) : 0,
(schema != NULL) ?
err_generic_string(PG_DIAG_SCHEMA_NAME, schema) : 0));
}
PG_CATCH();
{
ErrorData *edata;
ErrorData *edata;
MemoryContextSwitchTo(oldcontext);
edata = CopyErrorData();
FlushErrorState();
/*
* Note: If sv came from PyString_AsString(), it points into storage
* owned by so. So free so after using sv.
*/
Py_XDECREF(so);
PLy_exception_set_with_details(PLy_exc_error, edata);
FreeErrorData(edata);
/* Make Python raise the exception */
PLy_exception_set(PLy_exc_error, "%s", edata->message);
return NULL;
}
PG_END_TRY();
Py_XDECREF(so);
/*
* return a legal object so the interpreter will continue on its merry way
*/

View File

@ -554,8 +554,9 @@ PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner)
/* Look up the correct exception */
entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
HASH_FIND, NULL);
/* We really should find it, but just in case have a fallback */
Assert(entry != NULL);
/* This could be a custom error code, if that's the case fallback to
* SPIError
*/
exc = entry ? entry->exc : PLy_exc_spi_error;
/* Make Python raise the exception */
PLy_spi_exception_set(exc, edata);
@ -582,8 +583,10 @@ PLy_spi_exception_set(PyObject *excclass, ErrorData *edata)
if (!spierror)
goto failure;
spidata = Py_BuildValue("(izzzi)", edata->sqlerrcode, edata->detail, edata->hint,
edata->internalquery, edata->internalpos);
spidata= Py_BuildValue("(izzzizzzzz)", edata->sqlerrcode, edata->detail, edata->hint,
edata->internalquery, edata->internalpos,
edata->schema_name, edata->table_name, edata->column_name,
edata->datatype_name, edata->constraint_name);
if (!spidata)
goto failure;

View File

@ -36,8 +36,7 @@ $$ LANGUAGE plpythonu;
select module_contents();
CREATE FUNCTION elog_test() RETURNS void
CREATE FUNCTION elog_test_basic() RETURNS void
AS $$
plpy.debug('debug')
plpy.log('log')
@ -50,4 +49,138 @@ plpy.warning('warning')
plpy.error('error')
$$ LANGUAGE plpythonu;
SELECT elog_test_basic();
CREATE FUNCTION elog_test() RETURNS void
AS $$
plpy.debug('debug', detail = 'some detail')
plpy.log('log', detail = 'some detail')
plpy.info('info', detail = 'some detail')
plpy.info()
plpy.info('the question', detail = 42);
plpy.info('This is message text.',
detail = 'This is detail text',
hint = 'This is hint text.',
sqlstate = 'XX000',
schema = 'any info about schema',
table = 'any info about table',
column = 'any info about column',
datatype = 'any info about datatype',
constraint = 'any info about constraint')
plpy.notice('notice', detail = 'some detail')
plpy.warning('warning', detail = 'some detail')
plpy.error('stop on error', detail = 'some detail', hint = 'some hint')
$$ LANGUAGE plpythonu;
SELECT elog_test();
do $$ plpy.info('other types', detail = (10,20)) $$ LANGUAGE plpythonu;
do $$
import time;
from datetime import date
plpy.info('other types', detail = date(2016,2,26))
$$ LANGUAGE plpythonu;
do $$
basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
plpy.info('other types', detail = basket)
$$ LANGUAGE plpythonu;
-- should fail
do $$ plpy.info('wrong sqlstate', sqlstate='54444A') $$ LANGUAGE plpythonu;
do $$ plpy.info('unsupported argument', blabla='fooboo') $$ LANGUAGE plpythonu;
do $$ plpy.info('first message', message='second message') $$ LANGUAGE plpythonu;
do $$ plpy.info('first message', 'second message', message='third message') $$ LANGUAGE plpythonu;
-- raise exception in python, handle exception in plgsql
CREATE OR REPLACE FUNCTION raise_exception(_message text, _detail text DEFAULT NULL, _hint text DEFAULT NULL,
_sqlstate text DEFAULT NULL,
_schema text DEFAULT NULL, _table text DEFAULT NULL, _column text DEFAULT NULL,
_datatype text DEFAULT NULL, _constraint text DEFAULT NULL)
RETURNS void AS $$
kwargs = { "message":_message, "detail":_detail, "hint":_hint,
"sqlstate":_sqlstate, "schema":_schema, "table":_table,
"column":_column, "datatype":_datatype, "constraint":_constraint }
# ignore None values
plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
$$ LANGUAGE plpythonu;
SELECT raise_exception('hello', 'world');
SELECT raise_exception('message text', 'detail text', _sqlstate => 'YY333');
SELECT raise_exception(_message => 'message text',
_detail => 'detail text',
_hint => 'hint text',
_sqlstate => 'XX555',
_schema => 'schema text',
_table => 'table text',
_column => 'column text',
_datatype => 'datatype text',
_constraint => 'constraint text');
SELECT raise_exception(_message => 'message text',
_hint => 'hint text',
_schema => 'schema text',
_column => 'column text',
_constraint => 'constraint text');
DO $$
DECLARE
__message text;
__detail text;
__hint text;
__sqlstate text;
__schema_name text;
__table_name text;
__column_name text;
__datatype text;
__constraint text;
BEGIN
BEGIN
PERFORM raise_exception(_message => 'message text',
_detail => 'detail text',
_hint => 'hint text',
_sqlstate => 'XX555',
_schema => 'schema text',
_table => 'table text',
_column => 'column text',
_datatype => 'datatype text',
_constraint => 'constraint text');
EXCEPTION WHEN SQLSTATE 'XX555' THEN
GET STACKED DIAGNOSTICS __message = MESSAGE_TEXT,
__detail = PG_EXCEPTION_DETAIL,
__hint = PG_EXCEPTION_HINT,
__sqlstate = RETURNED_SQLSTATE,
__schema_name = SCHEMA_NAME,
__table_name = TABLE_NAME,
__column_name = COLUMN_NAME,
__datatype = PG_DATATYPE_NAME,
__constraint = CONSTRAINT_NAME;
RAISE NOTICE 'handled exception'
USING DETAIL = format('message:(%s), detail:(%s), hint: (%s), sqlstate: (%s), '
'schema:(%s), table:(%s), column:(%s), datatype:(%s), constraint:(%s)',
__message, __detail, __hint, __sqlstate, __schema_name,
__table_name, __column_name, __datatype, __constraint);
END;
END;
$$;
-- the displayed context is different between Python2 and Python3,
-- but that's not important for this test
\set SHOW_CONTEXT never
do $$
try:
plpy.execute("select raise_exception(_message => 'my message', _sqlstate => 'XX987', _hint => 'some hint', _table=> 'users_tab', _datatype => 'user_type')")
except Exception, e:
plpy.info(e.spidata)
raise e
$$ LANGUAGE plpythonu;
do $$
try:
plpy.error(message = 'my message', sqlstate = 'XX987', hint = 'some hint', table = 'users_tab', datatype = 'user_type')
except Exception, e:
plpy.info('sqlstate: %s, hint: %s, tablename: %s, datatype: %s' % (e.sqlstate, e.hint, e.table_name, e.datatype_name))
raise e
$$ LANGUAGE plpythonu;