<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
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 <module>
+ 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>,
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')
plpy.warning('warning')
plpy.error('error')
$$ LANGUAGE plpythonu;
-SELECT elog_test();
+SELECT elog_test_basic();
INFO: info
INFO: 37
INFO: ()
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
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
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;
}
(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();
{
* 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;
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;
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
{
* 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();
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
*/
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;
+}
+
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 */
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);
/*
* 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
* 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 *
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)
{
}
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
*/
/* 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);
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;
select module_contents();
-
-CREATE FUNCTION elog_test() RETURNS void
+CREATE FUNCTION elog_test_basic() RETURNS void
AS $$
plpy.debug('debug')
plpy.log('log')
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;