diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml new file mode 100644 index 015bbad..705413e *** a/doc/src/sgml/plpython.sgml --- b/doc/src/sgml/plpython.sgml *************** $$ LANGUAGE plpythonu; *** 1367,1372 **** --- 1367,1401 ---- + 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).elogin 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_test.out b/src/pl/plpython/expected/plpython_test.out new file mode 100644 index 7b76faf..834b14a *** 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 ! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ! 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 = '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.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.SPIError: 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..aea79db *** a/src/pl/plpython/plpy_elog.c --- b/src/pl/plpython/plpy_elog.c *************** PyObject *PLy_exc_spi_error = NULL; *** 22,29 **** 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); --- 22,30 ---- 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 **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); --- 52,72 ---- 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, ! &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(); { --- 112,124 ---- (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; --- 380,388 ---- * 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, ! 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 { --- 390,398 ---- if (spidata != NULL) { ! PyArg_ParseTuple(spidata, "izzzizzzzz", sqlerrcode, detail, hint, query, position, ! schema_name, table_name, column_name, ! datatype_name, constraint_name); } else { diff --git a/src/pl/plpython/plpy_plpymodule.c b/src/pl/plpython/plpy_plpymodule.c new file mode 100644 index a44b7fb..a4cdada *** a/src/pl/plpython/plpy_plpymodule.c --- b/src/pl/plpython/plpy_plpymodule.c *************** static PyObject *PLy_quote_literal(PyObj *** 39,44 **** --- 39,52 ---- 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); /* A list of all known exceptions, generated from backend/utils/errcodes.txt */ typedef struct ExceptionMap *************** static PyMethodDef PLy_methods[] = { *** 66,71 **** --- 74,90 ---- {"fatal", PLy_fatal, METH_VARARGS, NULL}, /* + * rich login methods + */ + {"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 */ {"prepare", PLy_spi_prepare, METH_VARARGS, NULL}, *************** PLy_fatal(PyObject *self, PyObject *args *** 315,320 **** --- 334,386 ---- return PLy_output(FATAL, self, args); } + /* + * the Python interface for raise functions + */ + static PyObject *PLy_output_kw(volatile int, PyObject *, PyObject *, PyObject *); + + 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) { *************** PLy_output(volatile int level, PyObject *** 429,431 **** --- 495,594 ---- 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_spi_exception_set(PLy_exc_spi_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..36ea728 *** 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 *** 546,554 **** /* * 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; --- 545,553 ---- /* * Raise a SPIError, passing in it more error details, like the ! * internal query and error position, sqlcode, detail, hint, .. */ ! void PLy_spi_exception_set(PyObject *excclass, ErrorData *edata) { PyObject *args = NULL; *************** PLy_spi_exception_set(PyObject *excclass *** 564,571 **** if (!spierror) goto failure; ! spidata = Py_BuildValue("(izzzi)", edata->sqlerrcode, edata->detail, edata->hint, ! edata->internalquery, edata->internalpos); if (!spidata) goto failure; --- 563,572 ---- if (!spierror) goto failure; ! 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; diff --git a/src/pl/plpython/plpy_spi.h b/src/pl/plpython/plpy_spi.h new file mode 100644 index b042794..f9c54b8 *** a/src/pl/plpython/plpy_spi.h --- b/src/pl/plpython/plpy_spi.h *************** extern void PLy_spi_subtransaction_begin *** 22,25 **** --- 22,28 ---- extern void PLy_spi_subtransaction_commit(MemoryContext oldcontext, ResourceOwner oldowner); extern void PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner); + /* raise and fill SPIError */ + extern void PLy_spi_exception_set(PyObject *excclass, ErrorData *edata); + #endif /* PLPY_SPI_H */ diff --git a/src/pl/plpython/sql/plpython_test.sql b/src/pl/plpython/sql/plpython_test.sql new file mode 100644 index c8d5ef5..190ce23 *** 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 = '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.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();