diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
new file mode 100644
index 015bbad..089b143
*** a/doc/src/sgml/plpython.sgml
--- b/doc/src/sgml/plpython.sgml
*************** $$ LANGUAGE plpythonu;
*** 1205,1210 ****
--- 1205,1228 ----
approximately the same functionality
+
+
+ Raising Errors
+
+
+ A plpy.Error can be raised from PL/Python, the constructor accepts
+ keyword parameters:
+ plpy.Error([ message [, detail [, hint [, sqlstate [, schema [, table [, column [, datatype [, constraint ]]]]]]]]]).
+
+
+ An example of raising custom exception could be written as:
+
+ DO $$
+ raise plpy.Error('custom message', hint = 'It is test only');
+ $$ LANGUAGE plpythonu;
+
+
+
*************** $$ LANGUAGE plpythonu;
*** 1367,1372 ****
--- 1385,1421 ----
+ The plpy module also provides the functions
+ plpy.raise_debug(args>),
+ plpy.raise_log(args>),
+ plpy.raise_info(args>),
+ plpy.raise_notice(args>),
+ plpy.raise_warning(args>),
+ plpy.raise_error(args>), and
+ plpy.raise_fatal(args>).elog>in PL/Python>
+ plpy.raise_error and
+ plpy.raise_fatal actually raise a Python exception
+ which, if uncaught, propagates out to the calling query, causing
+ the current transaction or subtransaction to be aborted.
+ raise plpy.Error(msg>) and
+ raise plpy.Fatal(msg>) are
+ equivalent to calling
+ plpy.raise_error and
+ plpy.raise_fatal, respectively.
+ 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
+ and
+ configuration
+ variables. See for more information.
+ These functions allows to using keyword parameters:
+ [ message [, detail [, hint [, sqlstate [, schema [, table [, column [, datatype [, constraint ]]]]]]]]].
+
+
+
+
+
Another set of utility functions are
plpy.quote_literal(string>),
plpy.quote_nullable(string>), and
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
new file mode 100644
index 1f52af7..cb792eb
*** a/src/pl/plpython/expected/plpython_error.out
--- b/src/pl/plpython/expected/plpython_error.out
*************** EXCEPTION WHEN SQLSTATE 'SILLY' THEN
*** 422,424 ****
--- 422,486 ----
-- NOOP
END
$$ LANGUAGE plpgsql;
+ /* the possibility to set fields of custom exception
+ */
+ DO $$
+ raise plpy.Error('This is message text.',
+ detail = 'This is detail text',
+ hint = 'This is hint text.')
+ $$ LANGUAGE plpythonu;
+ ERROR: plpy.Error: This is message text.
+ DETAIL: This is detail text
+ HINT: This is hint text.
+ CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 4, in
+ hint = 'This is hint text.')
+ PL/Python anonymous code block
+ \set VERBOSITY verbose
+ DO $$
+ raise plpy.Error('This is message text.',
+ detail = 'This is detail text',
+ hint = 'This is hint text.',
+ sqlstate = 'SILLY',
+ 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')
+ $$ LANGUAGE plpythonu;
+ ERROR: SILLY: plpy.Error: This is message text.
+ DETAIL: This is detail text
+ HINT: This is hint text.
+ CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 10, in
+ constraint = 'any info about constraint')
+ PL/Python anonymous code block
+ SCHEMA NAME: any info about schema
+ TABLE NAME: any info about table
+ COLUMN NAME: any info about column
+ DATATYPE NAME: any info about datatype
+ CONSTRAINT NAME: any info about constraint
+ LOCATION: PLy_elog, plpy_elog.c:122
+ \set VERBOSITY default
+ DO $$
+ raise plpy.Error(detail = 'This is detail text')
+ $$ LANGUAGE plpythonu;
+ ERROR: plpy.Error:
+ DETAIL: This is detail text
+ CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 2, in
+ raise plpy.Error(detail = 'This is detail text')
+ PL/Python anonymous code block
+ DO $$
+ raise plpy.Error();
+ $$ LANGUAGE plpythonu;
+ ERROR: plpy.Error:
+ CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 2, in
+ raise plpy.Error();
+ PL/Python anonymous code block
+ DO $$
+ raise plpy.Error(sqlstate = 'wrong sql state');
+ $$ LANGUAGE plpythonu;
+ ERROR: could not create Error object (invalid SQLSTATE code)
+ CONTEXT: PL/Python anonymous code block
diff --git a/src/pl/plpython/expected/plpython_error_0.out b/src/pl/plpython/expected/plpython_error_0.out
new file mode 100644
index 5323906..3d83a53
*** a/src/pl/plpython/expected/plpython_error_0.out
--- b/src/pl/plpython/expected/plpython_error_0.out
*************** EXCEPTION WHEN SQLSTATE 'SILLY' THEN
*** 422,424 ****
--- 422,486 ----
-- NOOP
END
$$ LANGUAGE plpgsql;
+ /* the possibility to set fields of custom exception
+ */
+ DO $$
+ raise plpy.Error('This is message text.',
+ detail = 'This is detail text',
+ hint = 'This is hint text.')
+ $$ LANGUAGE plpythonu;
+ ERROR: plpy.Error: This is message text.
+ DETAIL: This is detail text
+ HINT: This is hint text.
+ CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 4, in
+ hint = 'This is hint text.')
+ PL/Python anonymous code block
+ \set VERBOSITY verbose
+ DO $$
+ raise plpy.Error('This is message text.',
+ detail = 'This is detail text',
+ hint = 'This is hint text.',
+ sqlstate = 'SILLY',
+ 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')
+ $$ LANGUAGE plpythonu;
+ ERROR: SILLY: plpy.Error: This is message text.
+ DETAIL: This is detail text
+ HINT: This is hint text.
+ CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 10, in
+ constraint = 'any info about constraint')
+ PL/Python anonymous code block
+ SCHEMA NAME: any info about schema
+ TABLE NAME: any info about table
+ COLUMN NAME: any info about column
+ DATATYPE NAME: any info about datatype
+ CONSTRAINT NAME: any info about constraint
+ LOCATION: PLy_elog, plpy_elog.c:122
+ \set VERBOSITY default
+ DO $$
+ raise plpy.Error(detail = 'This is detail text')
+ $$ LANGUAGE plpythonu;
+ ERROR: plpy.Error: unknown
+ DETAIL: This is detail text
+ CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 2, in
+ raise plpy.Error(detail = 'This is detail text')
+ PL/Python anonymous code block
+ DO $$
+ raise plpy.Error();
+ $$ LANGUAGE plpythonu;
+ ERROR: plpy.Error: unknown
+ CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 2, in
+ raise plpy.Error();
+ PL/Python anonymous code block
+ DO $$
+ raise plpy.Error(sqlstate = 'wrong sql state');
+ $$ LANGUAGE plpythonu;
+ ERROR: could not create Error object (invalid SQLSTATE code)
+ CONTEXT: PL/Python anonymous code block
diff --git a/src/pl/plpython/expected/plpython_error_5.out b/src/pl/plpython/expected/plpython_error_5.out
new file mode 100644
index 5ff46ca..098e506
*** a/src/pl/plpython/expected/plpython_error_5.out
--- b/src/pl/plpython/expected/plpython_error_5.out
*************** EXCEPTION WHEN SQLSTATE 'SILLY' THEN
*** 422,424 ****
--- 422,486 ----
-- NOOP
END
$$ LANGUAGE plpgsql;
+ /* the possibility to set fields of custom exception
+ */
+ DO $$
+ raise plpy.Error('This is message text.',
+ detail = 'This is detail text',
+ hint = 'This is hint text.')
+ $$ LANGUAGE plpython3u;
+ ERROR: plpy.Error: This is message text.
+ DETAIL: This is detail text
+ HINT: This is hint text.
+ CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 4, in
+ hint = 'This is hint text.')
+ PL/Python anonymous code block
+ \set VERBOSITY verbose
+ DO $$
+ raise plpy.Error('This is message text.',
+ detail = 'This is detail text',
+ hint = 'This is hint text.',
+ sqlstate = 'SILLY',
+ 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')
+ $$ LANGUAGE plpython3u;
+ ERROR: SILLY: plpy.Error: This is message text.
+ DETAIL: This is detail text
+ HINT: This is hint text.
+ CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 10, in
+ constraint = 'any info about constraint')
+ PL/Python anonymous code block
+ SCHEMA NAME: any info about schema
+ TABLE NAME: any info about table
+ COLUMN NAME: any info about column
+ DATATYPE NAME: any info about datatype
+ CONSTRAINT NAME: any info about constraint
+ LOCATION: PLy_elog, plpy_elog.c:122
+ \set VERBOSITY default
+ DO $$
+ raise plpy.Error(detail = 'This is detail text')
+ $$ LANGUAGE plpython3u;
+ ERROR: plpy.Error:
+ DETAIL: This is detail text
+ CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 2, in
+ raise plpy.Error(detail = 'This is detail text')
+ PL/Python anonymous code block
+ DO $$
+ raise plpy.Error();
+ $$ LANGUAGE plpython3u;
+ ERROR: plpy.Error:
+ CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 2, in
+ raise plpy.Error();
+ PL/Python anonymous code block
+ DO $$
+ raise plpy.Error(sqlstate = 'wrong sql state');
+ $$ LANGUAGE plpython3u;
+ ERROR: could not create Error object (invalid SQLSTATE code)
+ CONTEXT: PL/Python anonymous code block
diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out
new file mode 100644
index 7b76faf..a63f699
*** a/src/pl/plpython/expected/plpython_test.out
--- b/src/pl/plpython/expected/plpython_test.out
*************** contents.sort()
*** 43,51 ****
return ", ".join(contents)
$$ LANGUAGE plpythonu;
select module_contents();
! 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
--- 43,51 ----
return ", ".join(contents)
$$ LANGUAGE plpythonu;
select module_contents();
! module_contents
! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
! BaseError, Error, Fatal, SPIError, cursor, debug, error, execute, fatal, info, log, notice, prepare, quote_ident, quote_literal, quote_nullable, raise_debug, raise_error, raise_fatal, raise_info, raise_log, raise_notice, raise_warning, spiexceptions, subtransaction, warning
(1 row)
CREATE FUNCTION elog_test() RETURNS void
*************** CONTEXT: Traceback (most recent call la
*** 72,74 ****
--- 72,111 ----
PL/Python function "elog_test", line 10, in
plpy.error('error')
PL/Python function "elog_test"
+ CREATE FUNCTION elog_test2() RETURNS void
+ AS $$
+ plpy.raise_debug('debug','some detail')
+ plpy.raise_log('log','some detail')
+ plpy.raise_info('info','some detail')
+ plpy.raise_info()
+ plpy.raise_info('This is message text.',
+ detail = 'This is detail text',
+ hint = 'This is hint text.',
+ sqlstate = 'SILLY',
+ 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.raise_notice('notice','some detail')
+ plpy.raise_warning('warning','some detail')
+ plpy.raise_error('stop on error', 'some detail','some hint')
+ $$ LANGUAGE plpythonu;
+ SELECT elog_test2();
+ INFO: info
+ DETAIL: some detail
+ INFO: missing error text
+ 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_test2", line 17, in
+ plpy.raise_error('stop on error', 'some detail','some hint')
+ PL/Python function "elog_test2"
diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c
new file mode 100644
index 15406d6..54594d2
*** a/src/pl/plpython/plpy_elog.c
--- b/src/pl/plpython/plpy_elog.c
***************
*** 15,29 ****
#include "plpy_main.h"
#include "plpy_procedure.h"
!
PyObject *PLy_exc_error = NULL;
PyObject *PLy_exc_fatal = NULL;
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);
static char *get_source_line(const char *src, int lineno);
--- 15,32 ----
#include "plpy_main.h"
#include "plpy_procedure.h"
! PyObject *PLy_exc_base = NULL;
PyObject *PLy_exc_error = NULL;
PyObject *PLy_exc_fatal = NULL;
PyObject *PLy_exc_spi_error = NULL;
static void PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth);
! static void PLy_get_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);
!
static char *get_source_line(const char *src, int lineno);
*************** PLy_elog(int elevel, const char *fmt,...
*** 51,63 ****
char *hint = NULL;
char *query = NULL;
int position = 0;
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);
! else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal))
elevel = FATAL;
}
PyErr_Restore(exc, val, tb);
--- 54,74 ----
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_base))
! PLy_get_error_data(val, &sqlerrcode, &detail, &hint, &query, &position,
! &schema_name, &table_name, &column_name,
! &datatype_name, &constraint_name);
!
! if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal))
elevel = FATAL;
}
PyErr_Restore(exc, val, tb);
*************** PLy_elog(int elevel, const char *fmt,...
*** 103,109 ****
(tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0,
(hint) ? errhint("%s", hint) : 0,
(query) ? internalerrquery(query) : 0,
! (position) ? internalerrposition(position) : 0));
}
PG_CATCH();
{
--- 114,126 ----
(tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0,
(hint) ? errhint("%s", hint) : 0,
(query) ? internalerrquery(query) : 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();
{
*************** PLy_get_spi_sqlerrcode(PyObject *exc, in
*** 365,371 ****
* 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)
{
PyObject *spidata = NULL;
--- 382,390 ----
* Extract the error data from a SPIError
*/
static void
! PLy_get_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;
*************** PLy_get_spi_error_data(PyObject *exc, in
*** 373,379 ****
if (spidata != NULL)
{
! PyArg_ParseTuple(spidata, "izzzi", sqlerrcode, detail, hint, query, position);
}
else
{
--- 392,400 ----
if (spidata != NULL)
{
! PyArg_ParseTuple(spidata, "izzzizzzzz", sqlerrcode, detail, hint, query, position,
! schema_name, table_name, column_name,
! datatype_name, constraint_name);
}
else
{
*************** PLy_exception_set_plural(PyObject *exc,
*** 464,466 ****
--- 485,532 ----
PyErr_SetString(exc, buf);
}
+
+ /*
+ * Raise a BaseError, passing in it more error details, like the
+ * internal query and error position.
+ */
+ void
+ PLy_base_exception_set(PyObject *excclass, ErrorData *edata)
+ {
+ PyObject *args = NULL;
+ PyObject *excpt = NULL;
+ PyObject *excpt_data = NULL;
+
+ args = Py_BuildValue("(s)", edata->message);
+ if (!args)
+ goto failure;
+
+ /* create a new SPI exception with the error message as the parameter */
+ excpt = PyObject_CallObject(excclass, args);
+ if (!excpt)
+ goto failure;
+
+ excpt_data = 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 (!excpt_data)
+ goto failure;
+
+ if (PyObject_SetAttrString(excpt, "spidata", excpt_data) == -1)
+ goto failure;
+
+ PyErr_SetObject(excclass, excpt);
+
+ Py_DECREF(args);
+ Py_DECREF(excpt);
+ Py_DECREF(excpt_data);
+ return;
+
+ failure:
+ Py_XDECREF(args);
+ Py_XDECREF(excpt);
+ Py_XDECREF(excpt_data);
+ elog(ERROR, "could not convert PostgreSQL error to Python exception");
+ }
diff --git a/src/pl/plpython/plpy_elog.h b/src/pl/plpython/plpy_elog.h
new file mode 100644
index 94725c2..eccdd44
*** a/src/pl/plpython/plpy_elog.h
--- b/src/pl/plpython/plpy_elog.h
***************
*** 6,11 ****
--- 6,12 ----
#define PLPY_ELOG_H
/* global exception classes */
+ extern PyObject *PLy_exc_base;
extern PyObject *PLy_exc_error;
extern PyObject *PLy_exc_fatal;
extern PyObject *PLy_exc_spi_error;
*************** extern void PLy_exception_set(PyObject *
*** 17,20 ****
--- 18,23 ----
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_base_exception_set(PyObject *excclass, ErrorData *edata);
+
#endif /* PLPY_ELOG_H */
diff --git a/src/pl/plpython/plpy_plpymodule.c b/src/pl/plpython/plpy_plpymodule.c
new file mode 100644
index a44b7fb..d0bc519
*** a/src/pl/plpython/plpy_plpymodule.c
--- b/src/pl/plpython/plpy_plpymodule.c
*************** HTAB *PLy_spi_exceptions = NULL;
*** 26,31 ****
--- 26,32 ----
static void PLy_add_exceptions(PyObject *plpy);
static void PLy_generate_spi_exceptions(PyObject *mod, PyObject *base);
+ static void PLy_add_methods_to_dictionary(PyObject *dict, PyMethodDef *methods);
/* module functions */
static PyObject *PLy_debug(PyObject *self, PyObject *args);
*************** static PyObject *PLy_quote_literal(PyObj
*** 39,44 ****
--- 40,57 ----
static PyObject *PLy_quote_nullable(PyObject *self, PyObject *args);
static PyObject *PLy_quote_ident(PyObject *self, PyObject *args);
+ /* module functions with keyword argument support */
+ static PyObject *PLy_raise_debug(PyObject *self, PyObject *args, PyObject *kw);
+ static PyObject *PLy_raise_log(PyObject *self, PyObject *args, PyObject *kw);
+ static PyObject *PLy_raise_info(PyObject *self, PyObject *args, PyObject *kw);
+ static PyObject *PLy_raise_notice(PyObject *self, PyObject *args, PyObject *kw);
+ static PyObject *PLy_raise_warning(PyObject *self, PyObject *args, PyObject *kw);
+ static PyObject *PLy_raise_error(PyObject *self, PyObject *args, PyObject *kw);
+ static PyObject *PLy_raise_fatal(PyObject *self, PyObject *args, PyObject *kw);
+
+ /* methods */
+ static PyObject *PLy_error__init__(PyObject *self, PyObject *args, PyObject *kw);
+
/* A list of all known exceptions, generated from backend/utils/errcodes.txt */
typedef struct ExceptionMap
*************** static PyMethodDef PLy_methods[] = {
*** 64,69 ****
--- 77,89 ----
{"warning", PLy_warning, METH_VARARGS, NULL},
{"error", PLy_error, METH_VARARGS, NULL},
{"fatal", PLy_fatal, METH_VARARGS, NULL},
+ {"raise_debug", (PyCFunction) PLy_raise_debug, METH_VARARGS | METH_KEYWORDS, NULL},
+ {"raise_log", (PyCFunction) PLy_raise_log, METH_VARARGS | METH_KEYWORDS, NULL},
+ {"raise_info", (PyCFunction) PLy_raise_info, METH_VARARGS | METH_KEYWORDS, NULL},
+ {"raise_notice", (PyCFunction) PLy_raise_notice, METH_VARARGS | METH_KEYWORDS, NULL},
+ {"raise_warning", (PyCFunction) PLy_raise_warning, METH_VARARGS | METH_KEYWORDS, NULL},
+ {"raise_error", (PyCFunction) PLy_raise_error, METH_VARARGS | METH_KEYWORDS, NULL},
+ {"raise_fatal", (PyCFunction) PLy_raise_fatal, METH_VARARGS | METH_KEYWORDS, NULL},
/*
* create a stored plan
*************** static PyMethodDef PLy_methods[] = {
*** 86,92 ****
* create the subtransaction context manager
*/
{"subtransaction", PLy_subtransaction_new, METH_NOARGS, NULL},
-
/*
* create a cursor
*/
--- 106,111 ----
*************** static PyMethodDef PLy_exc_methods[] = {
*** 99,104 ****
--- 118,128 ----
{NULL, NULL, 0, NULL}
};
+ static PyMethodDef PLy_error_methods[] = {
+ {"__init__", (PyCFunction) PLy_error__init__, METH_VARARGS | METH_KEYWORDS, NULL},
+ {NULL, NULL, 0, NULL}
+ };
+
#if PY_MAJOR_VERSION >= 3
static PyModuleDef PLy_module = {
PyModuleDef_HEAD_INIT, /* m_base */
*************** static void
*** 185,190 ****
--- 209,215 ----
PLy_add_exceptions(PyObject *plpy)
{
PyObject *excmod;
+ PyObject *error_dict;
HASHCTL hash_ctl;
#if PY_MAJOR_VERSION < 3
*************** PLy_add_exceptions(PyObject *plpy)
*** 207,220 ****
*/
Py_INCREF(excmod);
! PLy_exc_error = PyErr_NewException("plpy.Error", NULL, NULL);
! PLy_exc_fatal = PyErr_NewException("plpy.Fatal", NULL, NULL);
! PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", NULL, NULL);
! if (PLy_exc_error == NULL ||
PLy_exc_fatal == NULL ||
PLy_exc_spi_error == NULL)
! PLy_elog(ERROR, "could not create the base SPI exceptions");
Py_INCREF(PLy_exc_error);
PyModule_AddObject(plpy, "Error", PLy_exc_error);
--- 232,260 ----
*/
Py_INCREF(excmod);
! /* prepare dictionary with __init__ method for SPIError class */
! error_dict = PyDict_New();
! if (error_dict == NULL)
! PLy_elog(ERROR, "could not create dictionary for BaseError class");
! PLy_add_methods_to_dictionary(error_dict, PLy_error_methods);
! /* create common ancestor for exception classes */
! PLy_exc_base = PyErr_NewException("plpy.BaseError", NULL, error_dict);
!
! /* create all other builtin exception classes */
! PLy_exc_error = PyErr_NewException("plpy.Error", PLy_exc_base, NULL);
! PLy_exc_fatal = PyErr_NewException("plpy.Fatal", PLy_exc_base, NULL);
! PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", PLy_exc_base, NULL);
! Py_DECREF(error_dict);
!
! if (PLy_exc_base == NULL ||
! PLy_exc_error == NULL ||
PLy_exc_fatal == NULL ||
PLy_exc_spi_error == NULL)
! PLy_elog(ERROR, "could not create the plpy base exceptions");
!
! Py_INCREF(PLy_exc_base);
! PyModule_AddObject(plpy, "BaseError", PLy_exc_base);
Py_INCREF(PLy_exc_error);
PyModule_AddObject(plpy, "Error", PLy_exc_error);
*************** PLy_generate_spi_exceptions(PyObject *mo
*** 266,277 ****
--- 306,467 ----
}
}
+ /*
+ * Returns dictionary with specified set of methods. It is used for
+ * definition __init__ method of SPIError class. Our __init__ method
+ * supports keyword parameters and allows to set all available PostgreSQL
+ * Error fields.
+ */
+ static void
+ PLy_add_methods_to_dictionary(PyObject *dict, PyMethodDef *methods)
+ {
+ PyMethodDef *method;
+
+ for (method = methods; method->ml_name != NULL; method++)
+ {
+ PyObject *func;
+ PyObject *meth;
+
+ func = PyCFunction_New(method, NULL);
+ if (func == NULL)
+ PLy_elog(ERROR, "could not create function wrapper for \"%s\"", method->ml_name);
+
+ #if PY_MAJOR_VERSION < 3
+ meth = PyMethod_New(func, NULL, NULL);
+ #else
+ meth = PyInstanceMethod_New(func);
+ #endif
+ if (meth == NULL)
+ {
+ Py_DECREF(func);
+ PLy_elog(ERROR, "could not create method wrapper for \"%s\"", method->ml_name);
+ }
+
+ if (PyDict_SetItemString(dict, method->ml_name, meth))
+ {
+ Py_DECREF(func);
+ Py_DECREF(meth);
+ PLy_elog(ERROR, "could not add method \"%s\" to class dictionary", method->ml_name);
+ }
+
+ Py_DECREF(func);
+ Py_DECREF(meth);
+ }
+ }
+
+ /*
+ * Init method for SPIError class.
+ *
+ * This constructor allows to enter all user fields in PostgreSQL exception.
+ * Keywords parameters are supported.
+ */
+ static PyObject *
+ PLy_error__init__(PyObject *self, PyObject *args, PyObject *kw)
+ {
+ int sqlstate = 0;
+ bool sqlstate_is_invalid = false;
+ const char *sqlstatestr = NULL;
+ const char *message = NULL;
+ const char *detail = NULL;
+ const char *hint = NULL;
+ const char *column = NULL;
+ const char *constraint = NULL;
+ const char *datatype = NULL;
+ const char *table = NULL;
+ const char *schema = NULL;
+
+ PyObject *exc_args = NULL;
+ PyObject *spidata = NULL;
+
+ static char *kwlist[] = { "self", "message", "detail", "hint",
+ "sqlstate",
+ "schema","table", "column",
+ "datatype", "constraint",
+ NULL };
+
+ /*
+ * don't try to overwrite default sqlstate field, when constructor
+ * is called without any parameter. Important for predefined
+ * spiexception.* exceptions.
+ */
+ if (PyTuple_Size(args) > 1 || (kw != NULL && PyDict_Size(kw) >= 1))
+ {
+ if (!PyArg_ParseTupleAndKeywords(args, kw, "O|sssssssss",
+ kwlist, &self,
+ &message, &detail, &hint,
+ &sqlstatestr,
+ &schema, &table, &column,
+ &datatype, &constraint))
+ return NULL;
+
+ if (message != NULL)
+ {
+ exc_args = Py_BuildValue("(s)", message);
+ if (!exc_args)
+ goto failure;
+
+ if (PyObject_SetAttrString(self, "args", exc_args) == -1)
+ goto failure;
+ }
+
+ if (sqlstatestr != NULL)
+ {
+ if (strlen(sqlstatestr) != 5)
+ {
+ sqlstate_is_invalid = true;
+ goto failure;
+ }
+
+ if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5)
+ {
+ sqlstate_is_invalid = true;
+ goto failure;
+ }
+
+ sqlstate = MAKE_SQLSTATE(sqlstatestr[0],
+ sqlstatestr[1],
+ sqlstatestr[2],
+ sqlstatestr[3],
+ sqlstatestr[4]);
+ }
+
+ spidata = Py_BuildValue("(izzzizzzzz)",
+ sqlstate, detail, hint,
+ NULL, -1,
+ schema, table, column,
+ datatype, constraint);
+ if (!spidata)
+ goto failure;
+
+ if (PyObject_SetAttrString(self, "spidata", spidata) == -1)
+ goto failure;
+
+ Py_XDECREF(exc_args);
+ Py_DECREF(spidata);
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+
+ failure:
+ Py_XDECREF(exc_args);
+ Py_XDECREF(spidata);
+
+ if (sqlstate_is_invalid)
+ PLy_elog(ERROR, "could not create Error object (invalid SQLSTATE code)");
+ else
+ PLy_elog(ERROR, "could not create Error object");
+
+ return NULL;
+ }
+
/*
* 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_kw(volatile int, PyObject *, PyObject *, PyObject *);
static PyObject *
PLy_debug(PyObject *self, PyObject *args)
*************** PLy_fatal(PyObject *self, PyObject *args
*** 316,321 ****
--- 506,553 ----
}
static PyObject *
+ PLy_raise_debug(PyObject *self, PyObject *args, PyObject *kw)
+ {
+ return PLy_output_kw(DEBUG2, self, args, kw);
+ }
+
+ static PyObject *
+ PLy_raise_log(PyObject *self, PyObject *args, PyObject *kw)
+ {
+ return PLy_output_kw(LOG, self, args, kw);
+ }
+
+ static PyObject *
+ PLy_raise_info(PyObject *self, PyObject *args, PyObject *kw)
+ {
+ return PLy_output_kw(INFO, self, args, kw);
+ }
+
+ static PyObject *
+ PLy_raise_notice(PyObject *self, PyObject *args, PyObject *kw)
+ {
+ return PLy_output_kw(NOTICE, self, args, kw);
+ }
+
+ static PyObject *
+ PLy_raise_warning(PyObject *self, PyObject *args, PyObject *kw)
+ {
+ return PLy_output_kw(WARNING, self, args, kw);
+ }
+
+ static PyObject *
+ PLy_raise_error(PyObject *self, PyObject *args, PyObject *kw)
+ {
+ return PLy_output_kw(ERROR, self, args, kw);
+ }
+
+ static PyObject *
+ PLy_raise_fatal(PyObject *self, PyObject *args, PyObject *kw)
+ {
+ return PLy_output_kw(FATAL, self, args, kw);
+ }
+
+ static PyObject *
PLy_quote_literal(PyObject *self, PyObject *args)
{
const char *str;
*************** PLy_output(volatile int level, PyObject
*** 429,431 ****
--- 661,760 ----
Py_INCREF(Py_None);
return Py_None;
}
+
+ static PyObject *
+ PLy_output_kw(volatile int level, PyObject *self, PyObject *args, PyObject *kw)
+ {
+ int sqlstate = 0;
+ const char *sqlstatestr = NULL;
+ const char *message = NULL;
+ const char *detail = NULL;
+ const char *hint = NULL;
+ const char *column = NULL;
+ const char *constraint = NULL;
+ const char *datatype = NULL;
+ const char *table = NULL;
+ const char *schema = NULL;
+ MemoryContext oldcontext ;
+
+ static char *kwlist[] = { "message", "detail", "hint",
+ "sqlstate",
+ "schema","table", "column",
+ "datatype", "constraint",
+ NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kw, "|sssssssss", kwlist,
+ &message, &detail, &hint,
+ &sqlstatestr,
+ &schema, &table, &column,
+ &datatype, &constraint))
+ return NULL;
+
+ 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();
+ {
+ 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;
+
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ PLy_base_exception_set(PLy_exc_error, edata);
+ FreeErrorData(edata);
+ return NULL;
+ }
+ PG_END_TRY();
+
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c
new file mode 100644
index 58e78ec..8d4604b
*** a/src/pl/plpython/plpy_spi.c
--- b/src/pl/plpython/plpy_spi.c
***************
*** 30,36 ****
static PyObject *PLy_spi_execute_query(char *query, long limit);
static PyObject *PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit);
static PyObject *PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status);
- static void PLy_spi_exception_set(PyObject *excclass, ErrorData *edata);
/* prepare(query="select * from foo")
--- 30,35 ----
*************** PLy_spi_subtransaction_abort(MemoryConte
*** 540,587 ****
Assert(entry != NULL);
exc = entry ? entry->exc : PLy_exc_spi_error;
/* Make Python raise the exception */
! PLy_spi_exception_set(exc, edata);
FreeErrorData(edata);
}
-
- /*
- * Raise a SPIError, passing in it more error details, like the
- * internal query and error position.
- */
- static void
- PLy_spi_exception_set(PyObject *excclass, ErrorData *edata)
- {
- PyObject *args = NULL;
- PyObject *spierror = NULL;
- PyObject *spidata = NULL;
-
- args = Py_BuildValue("(s)", edata->message);
- if (!args)
- goto failure;
-
- /* create a new SPI exception with the error message as the parameter */
- spierror = PyObject_CallObject(excclass, args);
- if (!spierror)
- goto failure;
-
- spidata = Py_BuildValue("(izzzi)", edata->sqlerrcode, edata->detail, edata->hint,
- edata->internalquery, edata->internalpos);
- if (!spidata)
- goto failure;
-
- if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1)
- goto failure;
-
- PyErr_SetObject(excclass, spierror);
-
- Py_DECREF(args);
- Py_DECREF(spierror);
- Py_DECREF(spidata);
- return;
-
- failure:
- Py_XDECREF(args);
- Py_XDECREF(spierror);
- Py_XDECREF(spidata);
- elog(ERROR, "could not convert SPI error to Python exception");
- }
--- 539,544 ----
Assert(entry != NULL);
exc = entry ? entry->exc : PLy_exc_spi_error;
/* Make Python raise the exception */
! PLy_base_exception_set(exc, edata);
FreeErrorData(edata);
}
diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql
new file mode 100644
index d0df7e6..4657ce6
*** a/src/pl/plpython/sql/plpython_error.sql
--- b/src/pl/plpython/sql/plpython_error.sql
*************** EXCEPTION WHEN SQLSTATE 'SILLY' THEN
*** 328,330 ****
--- 328,365 ----
-- NOOP
END
$$ LANGUAGE plpgsql;
+
+ /* the possibility to set fields of custom exception
+ */
+ DO $$
+ raise plpy.Error('This is message text.',
+ detail = 'This is detail text',
+ hint = 'This is hint text.')
+ $$ LANGUAGE plpythonu;
+
+ \set VERBOSITY verbose
+ DO $$
+ raise plpy.Error('This is message text.',
+ detail = 'This is detail text',
+ hint = 'This is hint text.',
+ sqlstate = 'SILLY',
+ 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')
+ $$ LANGUAGE plpythonu;
+
+ \set VERBOSITY default
+
+ DO $$
+ raise plpy.Error(detail = 'This is detail text')
+ $$ LANGUAGE plpythonu;
+
+ DO $$
+ raise plpy.Error();
+ $$ LANGUAGE plpythonu;
+
+ DO $$
+ raise plpy.Error(sqlstate = 'wrong sql state');
+ $$ LANGUAGE plpythonu;
diff --git a/src/pl/plpython/sql/plpython_test.sql b/src/pl/plpython/sql/plpython_test.sql
new file mode 100644
index c8d5ef5..5eac2f5
*** a/src/pl/plpython/sql/plpython_test.sql
--- b/src/pl/plpython/sql/plpython_test.sql
*************** plpy.error('error')
*** 51,53 ****
--- 51,75 ----
$$ LANGUAGE plpythonu;
SELECT elog_test();
+
+ CREATE FUNCTION elog_test2() RETURNS void
+ AS $$
+ plpy.raise_debug('debug','some detail')
+ plpy.raise_log('log','some detail')
+ plpy.raise_info('info','some detail')
+ plpy.raise_info()
+ plpy.raise_info('This is message text.',
+ detail = 'This is detail text',
+ hint = 'This is hint text.',
+ sqlstate = 'SILLY',
+ 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.raise_notice('notice','some detail')
+ plpy.raise_warning('warning','some detail')
+ plpy.raise_error('stop on error', 'some detail','some hint')
+ $$ LANGUAGE plpythonu;
+
+ SELECT elog_test2();