Functions accessing the database might encounter errors, which
will cause them to abort and raise an exception. Both
<function>plpy.execute</function> and
- <function>plpy.prepare</function> can raise an instance of
+ <function>plpy.prepare</function> can raise an instance of a subclass of
<literal>plpy.SPIError</literal>, which by default will terminate
the function. This error can be handled just like any other
Python exception, by using the <literal>try/except</literal>
$$ LANGUAGE plpythonu;
</programlisting>
</para>
+
+ <para>
+ The actual class of the exception being raised corresponds to the
+ specific condition that caused the error. Refer
+ to <xref linkend="errcodes-table"> for a list of possible
+ conditions. The module
+ <literal>plpy.spiexceptions</literal> defines an exception class
+ for each <productname>PostgreSQL</productname> condition, deriving
+ their names from the condition name. For
+ instance, <literal>division_by_zero</literal>
+ becomes <literal>DivisionByZero</literal>, <literal>unique_violation</literal>
+ becomes <literal>UniqueViolation</literal>, <literal>fdw_error</literal>
+ becomes <literal>FdwError</literal>, and so on. Each of these
+ exception classes inherits from <literal>SPIError</literal>. This
+ separation makes it easier to handle specific errors, for
+ instance:
+<programlisting>
+CREATE FUNCTION insert_fraction(numerator int, denominator int) RETURNS text AS $$
+from plpy import spiexceptions
+try:
+ plan = plpy.prepare("INSERT INTO fractions (frac) VALUES ($1 / $2)", ["int", "int"])
+ plpy.execute(plan, [numerator, denominator])
+except spiexceptions.DivisionByZero:
+ return "denominator cannot equal zero"
+except spiexceptions.UniqueViolation:
+ return "already have that fraction"
+except plpy.SPIError, e:
+ return "other error, SQLSTATE %s" % e.sqlstate
+else:
+ return "fraction inserted"
+$$ LANGUAGE plpythonu;
+</programlisting>
+ Note that because all exceptions from
+ the <literal>plpy.spiexceptions</literal> module inherit
+ from <literal>SPIError</literal>, an <literal>except</literal>
+ clause handling it will catch any database access error.
+ </para>
+
+ <para>
+ As an alternative way of handling different error conditions, you
+ can catch the <literal>SPIError</literal> exception and determine
+ the specific error condition inside the <literal>except</literal>
+ block by looking at the <literal>sqlstate</literal> attribute of
+ the exception object. This attribute is a string value containing
+ the <quote>SQLSTATE</quote> error code. This approach provides
+ approximately the same functionality
+ </para>
</sect2>
</sect1>
+/spiexceptions.h
# Generated subdirectories
/log/
/results/
include $(top_srcdir)/src/Makefile.shlib
+# Force this dependency to be known even without dependency info built:
+plpython.o: spiexceptions.h
+
+spiexceptions.h: $(top_srcdir)/src/backend/utils/errcodes.txt generate-spiexceptions.pl
+ $(PERL) $(srcdir)/generate-spiexceptions.pl $< > $@
all: all-lib
+distprep: spiexceptions.h
+
install: all installdirs install-lib
ifeq ($(python_majorversion),2)
cd '$(DESTDIR)$(pkglibdir)' && rm -f plpython$(DLSUFFIX) && $(LN_S) $(shlib) plpython$(DLSUFFIX)
submake:
$(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
-clean distclean maintainer-clean: clean-lib
+clean distclean: clean-lib
rm -f $(OBJS)
rm -rf $(pg_regress_clean_files)
ifeq ($(PORTNAME), win32)
rm -f python${pytverstr}.def
endif
+maintainer-clean: distclean
+ rm -f spiexceptions.h
+
else # can't build
all:
'plpy.execute("syntax error")'
LANGUAGE plpythonu;
SELECT sql_syntax_error();
-ERROR: plpy.SPIError: syntax error at or near "syntax"
+ERROR: spiexceptions.SyntaxError: syntax error at or near "syntax"
LINE 1: syntax error
^
QUERY: syntax error
return rv[0]'
LANGUAGE plpythonu;
SELECT exception_index_invalid_nested();
-ERROR: plpy.SPIError: function test5(unknown) does not exist
+ERROR: spiexceptions.UndefinedFunction: function test5(unknown) does not exist
LINE 1: SELECT test5('foo')
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
'
LANGUAGE plpythonu;
SELECT invalid_type_uncaught('rick');
-ERROR: plpy.SPIError: type "test" does not exist
+ERROR: spiexceptions.UndefinedObject: type "test" does not exist
CONTEXT: PL/Python function "invalid_type_uncaught"
/* for what it's worth catch the exception generated by
* the typo, and return None
(1 row)
+/* check catching specific types of exceptions
+ */
+CREATE TABLE specific (
+ i integer PRIMARY KEY
+);
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "specific_pkey" for table "specific"
+CREATE FUNCTION specific_exception(i integer) RETURNS void AS
+$$
+from plpy import spiexceptions
+try:
+ plpy.execute("insert into specific values (%s)" % (i or "NULL"));
+except spiexceptions.NotNullViolation, e:
+ plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
+except spiexceptions.UniqueViolation, e:
+ plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
+$$ LANGUAGE plpythonu;
+SELECT specific_exception(2);
+ specific_exception
+--------------------
+
+(1 row)
+
+SELECT specific_exception(NULL);
+NOTICE: Violated the NOT NULL constraint, sqlstate 23502
+CONTEXT: PL/Python function "specific_exception"
+ specific_exception
+--------------------
+
+(1 row)
+
+SELECT specific_exception(2);
+NOTICE: Violated the UNIQUE constraint, sqlstate 23505
+CONTEXT: PL/Python function "specific_exception"
+ specific_exception
+--------------------
+
+(1 row)
+
/* manually starting subtransactions - a bad idea
*/
CREATE FUNCTION manual_subxact() RETURNS void AS $$
'plpy.execute("syntax error")'
LANGUAGE plpythonu;
SELECT sql_syntax_error();
-ERROR: plpy.SPIError: syntax error at or near "syntax"
+ERROR: spiexceptions.SyntaxError: syntax error at or near "syntax"
LINE 1: syntax error
^
QUERY: syntax error
return rv[0]'
LANGUAGE plpythonu;
SELECT exception_index_invalid_nested();
-ERROR: plpy.SPIError: function test5(unknown) does not exist
+ERROR: spiexceptions.UndefinedFunction: function test5(unknown) does not exist
LINE 1: SELECT test5('foo')
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
'
LANGUAGE plpythonu;
SELECT invalid_type_uncaught('rick');
-ERROR: plpy.SPIError: type "test" does not exist
+ERROR: spiexceptions.UndefinedObject: type "test" does not exist
CONTEXT: PL/Python function "invalid_type_uncaught"
/* for what it's worth catch the exception generated by
* the typo, and return None
(1 row)
+/* check catching specific types of exceptions
+ */
+CREATE TABLE specific (
+ i integer PRIMARY KEY
+);
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "specific_pkey" for table "specific"
+CREATE FUNCTION specific_exception(i integer) RETURNS void AS
+$$
+from plpy import spiexceptions
+try:
+ plpy.execute("insert into specific values (%s)" % (i or "NULL"));
+except spiexceptions.NotNullViolation, e:
+ plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
+except spiexceptions.UniqueViolation, e:
+ plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
+$$ LANGUAGE plpythonu;
+SELECT specific_exception(2);
+ specific_exception
+--------------------
+
+(1 row)
+
+SELECT specific_exception(NULL);
+NOTICE: Violated the NOT NULL constraint, sqlstate 23502
+CONTEXT: PL/Python function "specific_exception"
+ specific_exception
+--------------------
+
+(1 row)
+
+SELECT specific_exception(2);
+NOTICE: Violated the UNIQUE constraint, sqlstate 23505
+CONTEXT: PL/Python function "specific_exception"
+ specific_exception
+--------------------
+
+(1 row)
+
/* manually starting subtransactions - a bad idea
*/
CREATE FUNCTION manual_subxact() RETURNS void AS $$
TRUNCATE subtransaction_tbl;
SELECT subtransaction_test('SPI');
-ERROR: plpy.SPIError: invalid input syntax for integer: "oops"
+ERROR: spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops"
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
TRUNCATE subtransaction_tbl;
SELECT subtransaction_ctx_test('SPI');
-ERROR: plpy.SPIError: invalid input syntax for integer: "oops"
+ERROR: spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops"
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
return "ok"
$$ LANGUAGE plpythonu;
SELECT subtransaction_nested_test();
-ERROR: plpy.SPIError: syntax error at or near "error"
+ERROR: spiexceptions.SyntaxError: syntax error at or near "error"
LINE 1: error
^
QUERY: error
TRUNCATE subtransaction_tbl;
SELECT subtransaction_nested_test('t');
-NOTICE: Swallowed SPIError('syntax error at or near "error"',)
+NOTICE: Swallowed SyntaxError('syntax error at or near "error"',)
CONTEXT: PL/Python function "subtransaction_nested_test"
subtransaction_nested_test
----------------------------
return "ok"
$$ LANGUAGE plpythonu;
SELECT subtransaction_deeply_nested_test();
-NOTICE: Swallowed SPIError('syntax error at or near "error"',)
+NOTICE: Swallowed SyntaxError('syntax error at or near "error"',)
CONTEXT: PL/Python function "subtransaction_nested_test"
SQL statement "SELECT subtransaction_nested_test('t')"
PL/Python function "subtransaction_nested_test"
TRUNCATE subtransaction_tbl;
SELECT subtransaction_test('SPI');
-ERROR: plpy.SPIError: invalid input syntax for integer: "oops"
+ERROR: spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops"
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
return ", ".join(contents)
$$ LANGUAGE plpythonu;
select module_contents();
- module_contents
--------------------------------------------------------------------------------------------------------------------------------------------------------
- Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, quote_ident, quote_literal, quote_nullable, subtransaction, warning
+ module_contents
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Error, Fatal, SPIError, 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
--- /dev/null
+#!/usr/bin/perl
+#
+# Generate the spiexceptions.h header from errcodes.txt
+# Copyright (c) 2000-2011, PostgreSQL Global Development Group
+
+use warnings;
+use strict;
+
+print "/* autogenerated from src/backend/utils/errcodes.txt, do not edit */\n";
+print "/* there is deliberately not an #ifndef SPIEXCEPTIONS_H here */\n";
+
+open my $errcodes, $ARGV[0] or die;
+
+while (<$errcodes>) {
+ chomp;
+
+ # Skip comments
+ next if /^#/;
+ next if /^\s*$/;
+
+ # Skip section headers
+ next if /^Section:/;
+
+ die unless /^([^\s]{5})\s+([EWS])\s+([^\s]+)(?:\s+)?([^\s]+)?/;
+
+ (my $sqlstate,
+ my $type,
+ my $errcode_macro,
+ my $condition_name) = ($1, $2, $3, $4);
+
+ # Skip non-errors
+ next unless $type eq 'E';
+
+ # Skip lines without PL/pgSQL condition names
+ next unless defined($condition_name);
+
+ # Change some_error_condition to SomeErrorCondition
+ $condition_name =~ s/([a-z])([^_]*)(?:_|$)/\u$1$2/g;
+
+ print "{ \"spiexceptions.$condition_name\", " .
+ "\"$condition_name\", $errcode_macro },\n";
+}
+
+close $errcodes;
bool exited;
} PLySubtransactionObject;
+/* A list of all known exceptions, generated from backend/utils/errcodes.txt */
+typedef struct ExceptionMap
+{
+ char *name;
+ char *classname;
+ int sqlstate;
+} ExceptionMap;
+
+static const ExceptionMap exception_map[] = {
+#include "spiexceptions.h"
+ {NULL, NULL, 0}
+};
+
+/* A hash table mapping sqlstates to exceptions, for speedy lookup */
+static HTAB *PLy_spi_exceptions;
+
+typedef struct PLyExceptionEntry
+{
+ int sqlstate; /* hash key, must be first */
+ PyObject *exc; /* corresponding exception */
+} PLyExceptionEntry;
+
/* function declarations */
__attribute__((format(printf, 3, 5)));
/* like PLy_exception_set, but conserve more fields from ErrorData */
-static void PLy_spi_exception_set(ErrorData *edata);
+static void PLy_spi_exception_set(PyObject *excclass, ErrorData *edata);
/* Get the innermost python procedure called from the backend */
static char *PLy_procedure_name(PLyProcedure *);
{NULL, NULL, 0, NULL}
};
+static PyMethodDef PLy_exc_methods[] = {
+ {NULL, NULL, 0, NULL}
+};
+
#if PY_MAJOR_VERSION >= 3
static PyModuleDef PLy_module = {
PyModuleDef_HEAD_INIT, /* m_base */
-1, /* m_size */
PLy_methods, /* m_methods */
};
+
+static PyModuleDef PLy_exc_module = {
+ PyModuleDef_HEAD_INIT, /* m_base */
+ "spiexceptions", /* m_name */
+ NULL, /* m_doc */
+ -1, /* m_size */
+ PLy_exc_methods, /* m_methods */
+ NULL, /* m_reload */
+ NULL, /* m_traverse */
+ NULL, /* m_clear */
+ NULL /* m_free */
+};
#endif
/* plan object methods */
PG_CATCH();
{
ErrorData *edata;
+ PLyExceptionEntry *entry;
+ PyObject *exc;
/* Save error info */
MemoryContextSwitchTo(oldcontext);
*/
SPI_restore_connection();
+ /* 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);
+ exc = entry ? entry->exc : PLy_exc_spi_error;
/* Make Python raise the exception */
- PLy_spi_exception_set(edata);
+ PLy_spi_exception_set(exc, edata);
return NULL;
}
PG_END_TRY();
{
int k;
ErrorData *edata;
+ PLyExceptionEntry *entry;
+ PyObject *exc;
/* Save error info */
MemoryContextSwitchTo(oldcontext);
*/
SPI_restore_connection();
+ /* 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);
+ exc = entry ? entry->exc : PLy_exc_spi_error;
/* Make Python raise the exception */
- PLy_spi_exception_set(edata);
+ PLy_spi_exception_set(exc, edata);
return NULL;
}
PG_END_TRY();
}
PG_CATCH();
{
- ErrorData *edata;
+ ErrorData *edata;
+ PLyExceptionEntry *entry;
+ PyObject *exc;
/* Save error info */
MemoryContextSwitchTo(oldcontext);
*/
SPI_restore_connection();
+ /* 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);
+ exc = entry ? entry->exc : PLy_exc_spi_error;
/* Make Python raise the exception */
- PLy_spi_exception_set(edata);
+ PLy_spi_exception_set(exc, edata);
return NULL;
}
PG_END_TRY();
/*
* Add exceptions to the plpy module
*/
+
+/*
+ * Add all the autogenerated exceptions as subclasses of SPIError
+ */
+static void
+PLy_generate_spi_exceptions(PyObject *mod, PyObject *base)
+{
+ int i;
+
+ for (i = 0; exception_map[i].name != NULL; i++)
+ {
+ bool found;
+ PyObject *exc;
+ PLyExceptionEntry *entry;
+ PyObject *sqlstate;
+ PyObject *dict = PyDict_New();
+
+ sqlstate = PyString_FromString(unpack_sql_state(exception_map[i].sqlstate));
+ PyDict_SetItemString(dict, "sqlstate", sqlstate);
+ Py_DECREF(sqlstate);
+ exc = PyErr_NewException(exception_map[i].name, base, dict);
+ PyModule_AddObject(mod, exception_map[i].classname, exc);
+ entry = hash_search(PLy_spi_exceptions, &exception_map[i].sqlstate,
+ HASH_ENTER, &found);
+ entry->exc = exc;
+ Assert(!found);
+ }
+}
+
static void
PLy_add_exceptions(PyObject *plpy)
{
+ PyObject *excmod;
+ HASHCTL hash_ctl;
+
+#if PY_MAJOR_VERSION < 3
+ excmod = Py_InitModule("spiexceptions", PLy_exc_methods);
+#else
+ excmod = PyModule_Create(&PLy_exc_module);
+#endif
+ if (PyModule_AddObject(plpy, "spiexceptions", excmod) < 0)
+ PLy_elog(ERROR, "failed to add the spiexceptions module");
+
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);
PyModule_AddObject(plpy, "Fatal", PLy_exc_fatal);
Py_INCREF(PLy_exc_spi_error);
PyModule_AddObject(plpy, "SPIError", PLy_exc_spi_error);
+
+ memset(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = sizeof(int);
+ hash_ctl.entrysize = sizeof(PLyExceptionEntry);
+ hash_ctl.hash = tag_hash;
+ PLy_spi_exceptions = hash_create("SPI exceptions", 256,
+ &hash_ctl, HASH_ELEM | HASH_FUNCTION);
+
+ PLy_generate_spi_exceptions(excmod, PLy_exc_spi_error);
}
#if PY_MAJOR_VERSION >= 3
* internal query and error position.
*/
static void
-PLy_spi_exception_set(ErrorData *edata)
+PLy_spi_exception_set(PyObject *excclass, ErrorData *edata)
{
PyObject *args = NULL;
PyObject *spierror = NULL;
if (!args)
goto failure;
- /* create a new SPIError with the error message as the parameter */
- spierror = PyObject_CallObject(PLy_exc_spi_error, args);
+ /* create a new SPI exception with the error message as the parameter */
+ spierror = PyObject_CallObject(excclass, args);
if (!spierror)
goto failure;
if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1)
goto failure;
- PyErr_SetObject(PLy_exc_spi_error, spierror);
+ PyErr_SetObject(excclass, spierror);
Py_DECREF(args);
Py_DECREF(spierror);
SELECT valid_type('rick');
+/* check catching specific types of exceptions
+ */
+CREATE TABLE specific (
+ i integer PRIMARY KEY
+);
+
+CREATE FUNCTION specific_exception(i integer) RETURNS void AS
+$$
+from plpy import spiexceptions
+try:
+ plpy.execute("insert into specific values (%s)" % (i or "NULL"));
+except spiexceptions.NotNullViolation, e:
+ plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
+except spiexceptions.UniqueViolation, e:
+ plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
+$$ LANGUAGE plpythonu;
+
+SELECT specific_exception(2);
+SELECT specific_exception(NULL);
+SELECT specific_exception(2);
+
/* manually starting subtransactions - a bad idea
*/
CREATE FUNCTION manual_subxact() RETURNS void AS $$
);
}
+ if ($self->{options}->{python} && IsNewer('src\pl\plpython\spiexceptions.h','src\include\backend\errcodes.txt'))
+ {
+ print "Generating spiexceptions.h...\n";
+ system('perl src\pl\plpython\generate-spiexceptions.pl src\backend\utils\errcodes.txt > src\pl\plpython\spiexceptions.h');
+ }
+
if (IsNewer('src\include\utils\errcodes.h','src\backend\utils\errcodes.txt'))
{
print "Generating errcodes.h...\n";