On 11/02/11 10:53, Jan Urbański wrote: > On 10/02/11 22:26, Steve Singer wrote:
Here's an updated patch with documentation. It's an incremental patch on top of the latest explicit-subxacts version. Cheers, Jan
diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml index 87be8c2..aee54bf 100644 *** a/doc/src/sgml/plpython.sgml --- b/doc/src/sgml/plpython.sgml *************** $$ LANGUAGE plpythonu; *** 950,960 **** 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 ! <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> ! construct. For example: <programlisting> CREATE FUNCTION try_adding_joe() RETURNS text AS $$ try: --- 950,959 ---- 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 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> construct. For example: <programlisting> CREATE FUNCTION try_adding_joe() RETURNS text AS $$ try: *************** CREATE FUNCTION try_adding_joe() RETURNS *** 966,971 **** --- 965,1007 ---- $$ LANGUAGE plpythonu; </programlisting> </para> + <para> + The actual class of the exception being raised corresponds to exact + condition that caused the error (refer to <xref linkend="errcodes-table"> + for a list of possible conditions). The + <literal>plpy.spiexceptions</literal> module 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: + plpy.execute("INSERT INTO fractions(frac) VALUES (%d / %d)" % + (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> + </para> + <para> + 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. You can differentiate + inside the <literal>except</literal> block by looking at + the <literal>sqlstate</literal> string attribute. + </para> </sect2> </sect1> diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile index 33dddc6..b3f0d0e 100644 *** a/src/pl/plpython/Makefile --- b/src/pl/plpython/Makefile *************** PSQLDIR = $(bindir) *** 86,94 **** --- 86,102 ---- include $(top_srcdir)/src/Makefile.shlib + # Force this dependency to be known (see src/pl/plpgsql/src/Makefile) + plpython.o: spiexceptions.h + + # Generate spiexceptions.h from utils/errcodes.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) *************** endif *** 134,143 **** submake: $(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X) ! clean distclean maintainer-clean: clean-lib rm -f $(OBJS) rm -rf results rm -f regression.diffs regression.out ifeq ($(PORTNAME), win32) rm -f python${pytverstr}.def endif --- 142,156 ---- submake: $(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X) ! clean distclean: clean-lib rm -f $(OBJS) rm -rf results rm -f regression.diffs regression.out + + # since we distribute spiexceptions.h, only remove it in maintainer-clean + maintainer-clean: clean distclean + rm -f spiexceptions.h + ifeq ($(PORTNAME), win32) rm -f python${pytverstr}.def endif diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out index 7597ca7..afbc6db 100644 *** a/src/pl/plpython/expected/plpython_error.out --- b/src/pl/plpython/expected/plpython_error.out *************** CREATE FUNCTION sql_syntax_error() RETUR *** 32,38 **** 'plpy.execute("syntax error")' LANGUAGE plpythonu; SELECT sql_syntax_error(); ! ERROR: plpy.SPIError: syntax error at or near "syntax" LINE 1: syntax error ^ QUERY: syntax error --- 32,38 ---- 'plpy.execute("syntax error")' LANGUAGE plpythonu; SELECT sql_syntax_error(); ! ERROR: spiexceptions.SyntaxError: syntax error at or near "syntax" LINE 1: syntax error ^ QUERY: syntax error *************** CREATE FUNCTION exception_index_invalid_ *** 54,60 **** return rv[0]' LANGUAGE plpythonu; SELECT exception_index_invalid_nested(); ! ERROR: plpy.SPIError: 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. --- 54,60 ---- return rv[0]' LANGUAGE plpythonu; SELECT exception_index_invalid_nested(); ! 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. *************** return None *** 74,80 **** ' LANGUAGE plpythonu; SELECT invalid_type_uncaught('rick'); ! ERROR: plpy.SPIError: 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 --- 74,80 ---- ' LANGUAGE plpythonu; SELECT invalid_type_uncaught('rick'); ! 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 *************** SELECT valid_type('rick'); *** 140,145 **** --- 140,183 ---- (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 $$ diff --git a/src/pl/plpython/expected/plpython_subxact.out b/src/pl/plpython/expected/plpython_subxact.out index 7a478fc..c8649fd 100644 *** a/src/pl/plpython/expected/plpython_subxact.out --- b/src/pl/plpython/expected/plpython_subxact.out *************** SELECT * FROM subxact_tbl; *** 43,49 **** TRUNCATE subxact_tbl; SELECT subxact_test('SPI'); ! ERROR: plpy.SPIError: invalid input syntax for integer: "oops" LINE 1: insert into subxact_tbl values('oops') ^ QUERY: insert into subxact_tbl values('oops') --- 43,49 ---- TRUNCATE subxact_tbl; SELECT subxact_test('SPI'); ! ERROR: spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops" LINE 1: insert into subxact_tbl values('oops') ^ QUERY: insert into subxact_tbl values('oops') *************** SELECT * FROM subxact_tbl; *** 90,96 **** TRUNCATE subxact_tbl; SELECT subxact_ctx_test('SPI'); ! ERROR: plpy.SPIError: invalid input syntax for integer: "oops" LINE 1: insert into subxact_tbl values('oops') ^ QUERY: insert into subxact_tbl values('oops') --- 90,96 ---- TRUNCATE subxact_tbl; SELECT subxact_ctx_test('SPI'); ! ERROR: spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops" LINE 1: insert into subxact_tbl values('oops') ^ QUERY: insert into subxact_tbl values('oops') *************** with plpy.subtransaction(): *** 128,134 **** return "ok" $$ LANGUAGE plpythonu; SELECT subxact_nested_test(); ! ERROR: plpy.SPIError: syntax error at or near "error" LINE 1: error ^ QUERY: error --- 128,134 ---- return "ok" $$ LANGUAGE plpythonu; SELECT subxact_nested_test(); ! ERROR: spiexceptions.SyntaxError: syntax error at or near "error" LINE 1: error ^ QUERY: error *************** SELECT * FROM subxact_tbl; *** 140,146 **** TRUNCATE subxact_tbl; SELECT subxact_nested_test('t'); ! NOTICE: Swallowed SPIError('syntax error at or near "error"',) CONTEXT: PL/Python function "subxact_nested_test" subxact_nested_test --------------------- --- 140,146 ---- TRUNCATE subxact_tbl; SELECT subxact_nested_test('t'); ! NOTICE: Swallowed SyntaxError('syntax error at or near "error"',) CONTEXT: PL/Python function "subxact_nested_test" subxact_nested_test --------------------- *************** with plpy.subtransaction(): *** 166,172 **** return "ok" $$ LANGUAGE plpythonu; SELECT subxact_deeply_nested_test(); ! NOTICE: Swallowed SPIError('syntax error at or near "error"',) CONTEXT: PL/Python function "subxact_nested_test" SQL statement "select subxact_nested_test('t')" PL/Python function "subxact_nested_test" --- 166,172 ---- return "ok" $$ LANGUAGE plpythonu; SELECT subxact_deeply_nested_test(); ! NOTICE: Swallowed SyntaxError('syntax error at or near "error"',) CONTEXT: PL/Python function "subxact_nested_test" SQL statement "select subxact_nested_test('t')" PL/Python function "subxact_nested_test" diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out index c7d875e..2cb2ef6 100644 *** 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, debug, error, execute, fatal, info, log, notice, prepare, 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, debug, error, execute, fatal, info, log, notice, prepare, spiexceptions, subtransaction, warning (1 row) CREATE FUNCTION elog_test() RETURNS void diff --git a/src/pl/plpython/generate-spiexceptions.pl b/src/pl/plpython/generate-spiexceptions.pl index ...cf050d1 . *** a/src/pl/plpython/generate-spiexceptions.pl --- b/src/pl/plpython/generate-spiexceptions.pl *************** *** 0 **** --- 1,44 ---- + #!/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; diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index 381e91c..0e5388e 100644 *** a/src/pl/plpython/plpython.c --- b/src/pl/plpython/plpython.c *************** typedef struct PLySubxactObject *** 261,266 **** --- 261,288 ---- bool exited; } PLySubxactObject; + /* 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 hashtable 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 */ #if PY_MAJOR_VERSION >= 3 *************** __attribute__((format(printf, 2, 5))) *** 302,308 **** __attribute__((format(printf, 3, 5))); /* like PLy_exception_set, but conserve more fields from ErrorData */ ! static void PLy_spi_exception_set(ErrorData *edata); /* Get the innermost python procedure called from the backend */ static char *PLy_procedure_name(PLyProcedure *); --- 324,330 ---- __attribute__((format(printf, 3, 5))); /* like PLy_exception_set, but conserve more fields from ErrorData */ ! 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 *); *************** static PyMethodDef PLy_methods[] = { *** 2805,2810 **** --- 2827,2836 ---- {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 */ *************** static PyModuleDef PLy_module = { *** 2813,2818 **** --- 2839,2856 ---- -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 */ *************** PLy_spi_prepare(PyObject *self, PyObject *** 3109,3115 **** } PG_CATCH(); { ! ErrorData *edata; /* Save error info */ MemoryContextSwitchTo(oldcontext); --- 3147,3155 ---- } PG_CATCH(); { ! ErrorData *edata; ! PLyExceptionEntry *entry; ! PyObject *exc; /* Save error info */ MemoryContextSwitchTo(oldcontext); *************** PLy_spi_prepare(PyObject *self, PyObject *** 3130,3137 **** */ SPI_restore_connection(); /* Make Python raise the exception */ ! PLy_spi_exception_set(edata); return NULL; } PG_END_TRY(); --- 3170,3183 ---- */ 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(exc, edata); return NULL; } PG_END_TRY(); *************** PLy_spi_execute_plan(PyObject *ob, PyObj *** 3280,3287 **** } PG_CATCH(); { int k; - ErrorData *edata; /* Save error info */ MemoryContextSwitchTo(oldcontext); --- 3326,3335 ---- } PG_CATCH(); { + ErrorData *edata; + PLyExceptionEntry *entry; + PyObject *exc; int k; /* Save error info */ MemoryContextSwitchTo(oldcontext); *************** PLy_spi_execute_plan(PyObject *ob, PyObj *** 3313,3320 **** */ SPI_restore_connection(); /* Make Python raise the exception */ ! PLy_spi_exception_set(edata); return NULL; } PG_END_TRY(); --- 3361,3374 ---- */ 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(exc, edata); return NULL; } PG_END_TRY(); *************** PLy_spi_execute_query(char *query, long *** 3374,3380 **** } PG_CATCH(); { ! ErrorData *edata; /* Save error info */ MemoryContextSwitchTo(oldcontext); --- 3428,3436 ---- } PG_CATCH(); { ! ErrorData *edata; ! PLyExceptionEntry *entry; ! PyObject *exc; /* Save error info */ MemoryContextSwitchTo(oldcontext); *************** PLy_spi_execute_query(char *query, long *** 3393,3400 **** */ SPI_restore_connection(); /* Make Python raise the exception */ ! PLy_spi_exception_set(edata); return NULL; } PG_END_TRY(); --- 3449,3462 ---- */ 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(exc, edata); return NULL; } PG_END_TRY(); *************** PLy_subxact_exit(PyObject *self, PyObjec *** 3622,3630 **** --- 3684,3729 ---- /* * 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); *************** PLy_add_exceptions(PyObject *plpy) *** 3635,3640 **** --- 3734,3748 ---- 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 *************** PLy_exception_set_plural(PyObject *exc, *** 3941,3947 **** * internal query and error position. */ static void ! PLy_spi_exception_set(ErrorData *edata) { PyObject *args = NULL; PyObject *spierror = NULL; --- 4049,4055 ---- * internal query and error position. */ static void ! PLy_spi_exception_set(PyObject *excclass, ErrorData *edata) { PyObject *args = NULL; PyObject *spierror = NULL; *************** PLy_spi_exception_set(ErrorData *edata) *** 3951,3958 **** if (!args) goto failure; ! /* create a new SPIError with the error message as the parameter */ ! spierror = PyObject_CallObject(PLy_exc_spi_error, args); if (!spierror) goto failure; --- 4059,4066 ---- 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; *************** PLy_spi_exception_set(ErrorData *edata) *** 3964,3970 **** if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1) goto failure; ! PyErr_SetObject(PLy_exc_spi_error, spierror); Py_DECREF(args); Py_DECREF(spierror); --- 4072,4078 ---- if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1) goto failure; ! PyErr_SetObject(excclass, spierror); Py_DECREF(args); Py_DECREF(spierror); diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql index 7861cd6..6d6a163 100644 *** a/src/pl/plpython/sql/plpython_error.sql --- b/src/pl/plpython/sql/plpython_error.sql *************** return None *** 131,136 **** --- 131,157 ---- 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 $$ diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm index 49504d7..e1fe4e9 100644 *** a/src/tools/msvc/Solution.pm --- b/src/tools/msvc/Solution.pm *************** s{PG_VERSION_STR "[^"]+"}{__STRINGIFY(x) *** 273,278 **** --- 273,284 ---- ); } + 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";
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers