Commit 5c3c3cd0 authored by Teodor Sigaev's avatar Teodor Sigaev

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
parent 5364b357
......@@ -1341,24 +1341,23 @@ $$ LANGUAGE plpythonu;
<title>Utility Functions</title>
<para>
The <literal>plpy</literal> module also provides the functions
<literal>plpy.debug(<replaceable>msg</>)</literal>,
<literal>plpy.log(<replaceable>msg</>)</literal>,
<literal>plpy.info(<replaceable>msg</>)</literal>,
<literal>plpy.notice(<replaceable>msg</>)</literal>,
<literal>plpy.warning(<replaceable>msg</>)</literal>,
<literal>plpy.error(<replaceable>msg</>)</literal>, and
<literal>plpy.fatal(<replaceable>msg</>)</literal>.<indexterm><primary>elog</><secondary>in PL/Python</></indexterm>
<function>plpy.error</function> and
<function>plpy.fatal</function> actually raise a Python exception
which, if uncaught, propagates out to the calling query, causing
the current transaction or subtransaction to be aborted.
<literal>raise plpy.Error(<replaceable>msg</>)</literal> and
<literal>plpy.debug(<replaceable>msg, **kwargs</>)</literal>,
<literal>plpy.log(<replaceable>msg, **kwargs</>)</literal>,
<literal>plpy.info(<replaceable>msg, **kwargs</>)</literal>,
<literal>plpy.notice(<replaceable>msg, **kwargs</>)</literal>,
<literal>plpy.warning(<replaceable>msg, **kwargs</>)</literal>,
<literal>plpy.error(<replaceable>msg, **kwargs</>)</literal>, and
<literal>plpy.fatal(<replaceable>msg, **kwargs</>)</literal>.
<indexterm><primary>elog</><secondary>in PL/Python</></indexterm>
<function>plpy.error</function> and <function>plpy.fatal</function>
actually raise a Python exception which, if uncaught, propagates out to
the calling query, causing the current transaction or subtransaction to
be aborted. <literal>raise plpy.Error(<replaceable>msg</>)</literal> and
<literal>raise plpy.Fatal(<replaceable>msg</>)</literal> are
equivalent to calling
<function>plpy.error</function> and
<function>plpy.fatal</function>, respectively.
The other functions only generate messages of different
priority levels.
equivalent to calling <literal>plpy.error(<replaceable>msg</>)</literal> and
<literal>plpy.fatal(<replaceable>msg</>)</literal>, respectively but
the <literal>raise</literal> form does not allow passing keyword arguments.
The other functions only generate messages of different priority levels.
Whether messages of a particular priority are reported to the client,
written to the server log, or both is controlled by the
<xref linkend="guc-log-min-messages"> and
......@@ -1366,6 +1365,39 @@ $$ LANGUAGE plpythonu;
variables. See <xref linkend="runtime-config"> for more information.
</para>
<para>
The <replaceable>msg</> argument is given as a positional argument. For
backward compatibility, more than one positional argument can be given. In
that case, the string representation of the tuple of positional arguments
becomes the message reported to the client.
The following keyword-only arguments are accepted:
<literal>
<replaceable>detail</replaceable>, <replaceable>hint</replaceable>,
<replaceable>sqlstate</replaceable>, <replaceable>schema</replaceable>,
<replaceable>table</replaceable>, <replaceable>column</replaceable>,
<replaceable>datatype</replaceable> , <replaceable>constraint</replaceable>
</literal>.
The string representation of the objects passed as keyword-only arguments
is used to enrich the messages reported to the client. For example:
<programlisting>
CREATE FUNCTION raise_custom_exception() RETURNS void AS $$
plpy.error("custom exception message", detail = "some info about exception", hint = "hint for users")
$$ LANGUAGE plpythonu;
postgres=# select raise_custom_exception();
ERROR: XX000: plpy.Error: custom exception message
DETAIL: some info about exception
HINT: hint for users
CONTEXT: Traceback (most recent call last):
PL/Python function "raise_custom_exception", line 2, in &lt;module&gt;
plpy.error("custom exception message", detail = "some info about exception", hint = "hint for users")
PL/Python function "raise_custom_exception"
LOCATION: PLy_elog, plpy_elog.c:132
</programlisting>
</para>
<para>
Another set of utility functions are
<literal>plpy.quote_literal(<replaceable>string</>)</literal>,
......
......@@ -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
......@@ -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;
}
......@@ -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 */
......@@ -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;
}
/* enforce cast of object to string */
static char *
object_to_string(PyObject *obj)
{
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)
PLy_output(volatile int level, PyObject *self, PyObject *args, PyObject *kw)
{
PyObject *volatile so;
char *volatile sv;
volatile MemoryContext oldcontext;
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
*/
......
......@@ -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;
......
......@@ -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;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment