]> granicus.if.org Git - postgresql/blobdiff - src/pl/plpython/plpython.c
Improve the recently-added support for properly pluralized error messages
[postgresql] / src / pl / plpython / plpython.c
index a2da9a6dcee2606de3b2cdadb8e3d375407c040d..345f3f65236619d6cd431fde70a9aaef4eabe709 100644 (file)
@@ -1,7 +1,7 @@
 /**********************************************************************
  * plpython.c - python as a procedural language for PostgreSQL
  *
- *     $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.110 2008/05/12 00:00:54 alvherre Exp $
+ *     $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.121 2009/06/04 18:33:08 tgl Exp $
  *
  *********************************************************************
  */
@@ -54,6 +54,7 @@ typedef int Py_ssize_t;
 #include "executor/spi.h"
 #include "funcapi.h"
 #include "fmgr.h"
+#include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_type.h"
 #include "tcop/tcopprot.h"
@@ -63,6 +64,10 @@ typedef int Py_ssize_t;
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
+/* define our text domain for translations */
+#undef TEXTDOMAIN
+#define TEXTDOMAIN PG_TEXTDOMAIN("plpython")
+
 #include <compile.h>
 #include <eval.h>
 
@@ -199,16 +204,21 @@ PG_FUNCTION_INFO_V1(plpython_call_handler);
 static void PLy_init_interp(void);
 static void PLy_init_plpy(void);
 
-/* call PyErr_SetString with a vprint interface */
-static void
-PLy_exception_set(PyObject *, const char *,...)
+/* call PyErr_SetString with a vprint interface and translation support */
+static void PLy_exception_set(PyObject *, const char *,...)
 __attribute__((format(printf, 2, 3)));
+/* same, with pluralized message */
+static void PLy_exception_set_plural(PyObject *, const char *, const char *,
+                                                                        unsigned long n,...)
+__attribute__((format(printf, 2, 5)))
+__attribute__((format(printf, 3, 5)));
 
 /* Get the innermost python procedure called from the backend */
 static char *PLy_procedure_name(PLyProcedure *);
 
 /* some utility functions */
-static void PLy_elog(int, const char *,...);
+static void PLy_elog(int, const char *,...)
+__attribute__((format(printf, 2, 3)));
 static char *PLy_traceback(int *);
 
 static void *PLy_malloc(size_t);
@@ -326,7 +336,7 @@ plpython_call_handler(PG_FUNCTION_ARGS)
        PLyProcedure *volatile proc = NULL;
 
        if (SPI_connect() != SPI_OK_CONNECT)
-               elog(ERROR, "could not connect to SPI manager");
+               elog(ERROR, "SPI_connect failed");
 
        save_curr_proc = PLy_curr_procedure;
 
@@ -412,7 +422,7 @@ PLy_trigger_handler(FunctionCallInfo fcinfo, PLyProcedure * proc)
                                ereport(ERROR,
                                                (errcode(ERRCODE_DATA_EXCEPTION),
                                        errmsg("unexpected return value from trigger procedure"),
-                                                errdetail("Expected None or a String.")));
+                                                errdetail("Expected None or a string.")));
 
                        srv = PyString_AsString(plrv);
                        if (pg_strcasecmp(srv, "SKIP") == 0)
@@ -425,7 +435,8 @@ PLy_trigger_handler(FunctionCallInfo fcinfo, PLyProcedure * proc)
                                        TRIGGER_FIRED_BY_UPDATE(tdata->tg_event))
                                        rv = PLy_modify_tuple(proc, plargs, tdata, rv);
                                else
-                                       elog(WARNING, "ignoring modified tuple in DELETE trigger");
+                                       ereport(WARNING,
+                                                       (errmsg("PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored")));
                        }
                        else if (pg_strcasecmp(srv, "OK") != 0)
                        {
@@ -482,9 +493,11 @@ PLy_modify_tuple(PLyProcedure * proc, PyObject * pltd, TriggerData *tdata,
        PG_TRY();
        {
                if ((plntup = PyDict_GetItemString(pltd, "new")) == NULL)
-                       elog(ERROR, "TD[\"new\"] deleted, cannot modify tuple");
+                       ereport(ERROR, 
+                                       (errmsg("TD[\"new\"] deleted, cannot modify row")));
                if (!PyDict_Check(plntup))
-                       elog(ERROR, "TD[\"new\"] is not a dictionary object");
+                       ereport(ERROR,
+                                       (errmsg("TD[\"new\"] is not a dictionary")));
                Py_INCREF(plntup);
 
                plkeys = PyDict_Keys(plntup);
@@ -502,16 +515,18 @@ PLy_modify_tuple(PLyProcedure * proc, PyObject * pltd, TriggerData *tdata,
 
                        platt = PyList_GetItem(plkeys, i);
                        if (!PyString_Check(platt))
-                               elog(ERROR, "attribute name is not a string");
+                               ereport(ERROR,
+                                               (errmsg("name of TD[\"new\"] attribute at ordinal position %d is not a string", i)));
                        attn = SPI_fnumber(tupdesc, PyString_AsString(platt));
                        if (attn == SPI_ERROR_NOATTRIBUTE)
-                               elog(ERROR, "invalid attribute \"%s\" in tuple",
-                                        PyString_AsString(platt));
+                               ereport(ERROR,
+                                               (errmsg("key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row",
+                                                               PyString_AsString(platt))));
                        atti = attn - 1;
 
                        plval = PyDict_GetItem(plntup, platt);
                        if (plval == NULL)
-                               elog(FATAL, "python interpreter is probably corrupted");
+                               elog(FATAL, "Python interpreter is probably corrupted");
 
                        Py_INCREF(plval);
 
@@ -526,7 +541,7 @@ PLy_modify_tuple(PLyProcedure * proc, PyObject * pltd, TriggerData *tdata,
                        {
                                plstr = PyObject_Str(plval);
                                if (!plstr)
-                                       PLy_elog(ERROR, "function \"%s\" could not modify tuple",
+                                       PLy_elog(ERROR, "could not compute string representation of Python object in PL/Python function \"%s\" while modifying trigger row",
                                                         proc->proname);
                                src = PyString_AsString(plstr);
 
@@ -557,7 +572,7 @@ PLy_modify_tuple(PLyProcedure * proc, PyObject * pltd, TriggerData *tdata,
                rtup = SPI_modifytuple(tdata->tg_relation, otup, natts,
                                                           modattrs, modvalues, modnulls);
                if (rtup == NULL)
-                       elog(ERROR, "SPI_modifytuple failed -- error %d", SPI_result);
+                       elog(ERROR, "SPI_modifytuple failed: error %d", SPI_result);
        }
        PG_CATCH();
        {
@@ -608,7 +623,7 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc, HeapTuple *
        {
                pltdata = PyDict_New();
                if (!pltdata)
-                       PLy_elog(ERROR, "could not build arguments for trigger procedure");
+                       PLy_elog(ERROR, "could not create new dictionary while building trigger arguments");
 
                pltname = PyString_FromString(tdata->tg_trigger->tgname);
                PyDict_SetItemString(pltdata, "name", pltname);
@@ -816,7 +831,8 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc)
                                {
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                        errmsg("only value per call is allowed")));
+                                                        errmsg("unsupported set function return mode"),
+                                                        errdetail("PL/Python set-returning functions only support returning only value per call.")));
                                }
                                rsi->returnMode = SFRM_ValuePerCall;
 
@@ -829,7 +845,7 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_DATATYPE_MISMATCH),
                                                         errmsg("returned object cannot be iterated"),
-                                       errdetail("SETOF must be returned as iterable object")));
+                                       errdetail("PL/Python set-returning functions must return an iterable object.")));
                        }
 
                        /* Fetch next from iterator */
@@ -875,8 +891,7 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc)
                        if (plrv != Py_None)
                                ereport(ERROR,
                                                (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                          errmsg("invalid return value from plpython function"),
-                                                errdetail("Functions returning type \"void\" must return None.")));
+                                          errmsg("PL/Python function with return type \"void\" did not return None")));
 
                        fcinfo->isnull = false;
                        rv = (Datum) 0;
@@ -923,7 +938,7 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc)
                        fcinfo->isnull = false;
                        plrv_so = PyObject_Str(plrv);
                        if (!plrv_so)
-                               PLy_elog(ERROR, "function \"%s\" could not create return value", proc->proname);
+                               PLy_elog(ERROR, "could not create string representation of Python object in PL/Python function \"%s\" while creating return value", proc->proname);
                        plrv_sc = PyString_AsString(plrv_so);
                        rv = InputFunctionCall(&proc->result.out.d.typfunc,
                                                                   plrv_sc,
@@ -972,7 +987,7 @@ PLy_procedure_call(PLyProcedure * proc, char *kargs, PyObject * vargs)
        if (rv == NULL || PyErr_Occurred())
        {
                Py_XDECREF(rv);
-               PLy_elog(ERROR, "function \"%s\" failed", proc->proname);
+               PLy_elog(ERROR, "PL/Python function \"%s\" failed", proc->proname);
        }
 
        return rv;
@@ -1041,10 +1056,12 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc)
                                arg = Py_None;
                        }
 
-                       if (PyList_SetItem(args, i, arg) == -1 ||
-                               (proc->argnames &&
-                                PyDict_SetItemString(proc->globals, proc->argnames[i], arg) == -1))
-                               PLy_elog(ERROR, "problem setting up arguments for \"%s\"", proc->proname);
+                       if (PyList_SetItem(args, i, arg) == -1)
+                               PLy_elog(ERROR, "PyList_SetItem() failed for PL/Python function \"%s\" while setting up arguments", proc->proname);
+
+                       if (proc->argnames && proc->argnames[i] &&
+                               PyDict_SetItemString(proc->globals, proc->argnames[i], arg) == -1)
+                               PLy_elog(ERROR, "PyDict_SetItemString() failed for PL/Python function \"%s\" while setting up arguments", proc->proname);
                        arg = NULL;
                }
        }
@@ -1070,7 +1087,8 @@ PLy_function_delete_args(PLyProcedure * proc)
                return;
 
        for (i = 0; i < proc->nargs; i++)
-               PyDict_DelItemString(proc->globals, proc->argnames[i]);
+               if (proc->argnames[i])
+                       PyDict_DelItemString(proc->globals, proc->argnames[i]);
 }
 
 
@@ -1225,7 +1243,7 @@ PLy_procedure_create(HeapTuple procTup, Oid tgreloid, char *key)
                                else
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                  errmsg("plpython functions cannot return type %s",
+                                                  errmsg("PL/Python functions cannot return type %s",
                                                                  format_type_be(procStruct->prorettype))));
                        }
 
@@ -1271,19 +1289,22 @@ PLy_procedure_create(HeapTuple procTup, Oid tgreloid, char *key)
                                /* proc->nargs was initialized to 0 above */
                                for (i = 0; i < total; i++)
                                {
-                                       if (modes[i] != 'o')
+                                       if (modes[i] != PROARGMODE_OUT &&
+                                               modes[i] != PROARGMODE_TABLE)
                                                (proc->nargs)++;
                                }
                        }
 
-                       proc->argnames = (char **) PLy_malloc(sizeof(char *) * proc->nargs);
+                       proc->argnames = (char **) PLy_malloc0(sizeof(char *) * proc->nargs);
                        for (i = pos = 0; i < total; i++)
                        {
                                HeapTuple       argTypeTup;
                                Form_pg_type argTypeStruct;
 
-                               if (modes && modes[i] == 'o')   /* skip OUT arguments */
-                                       continue;
+                               if (modes &&
+                                       (modes[i] == PROARGMODE_OUT ||
+                                        modes[i] == PROARGMODE_TABLE))
+                                       continue;       /* skip OUT arguments */
 
                                Assert(types[i] == procStruct->proargtypes.values[pos]);
 
@@ -1301,7 +1322,7 @@ PLy_procedure_create(HeapTuple procTup, Oid tgreloid, char *key)
                                                /* Disallow pseudotype argument */
                                                ereport(ERROR,
                                                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                                errmsg("plpython functions cannot take type %s",
+                                                                errmsg("PL/Python functions cannot accept type %s",
                                                                 format_type_be(types[i]))));
                                                break;
                                        case TYPTYPE_COMPOSITE:
@@ -1395,7 +1416,7 @@ PLy_procedure_compile(PLyProcedure * proc, const char *src)
        else
                Py_XDECREF(crv);
 
-       PLy_elog(ERROR, "could not compile function \"%s\"", proc->proname);
+       PLy_elog(ERROR, "could not compile PL/Python function \"%s\"", proc->proname);
 }
 
 static char *
@@ -1472,8 +1493,9 @@ PLy_procedure_delete(PLyProcedure * proc)
                PLy_free(proc->argnames);
 }
 
-/* conversion functions.  remember output from python is
- * input to postgresql, and vis versa.
+/*
+ * Conversion functions.  Remember output from Python is input to
+ * PostgreSQL, and vice versa.
  */
 static void
 PLy_input_tuple_funcs(PLyTypeInfo * arg, TupleDesc desc)
@@ -1706,7 +1728,7 @@ PLyDict_FromTuple(PLyTypeInfo * info, HeapTuple tuple, TupleDesc desc)
 
        dict = PyDict_New();
        if (dict == NULL)
-               PLy_elog(ERROR, "could not create tuple dictionary");
+               PLy_elog(ERROR, "could not create new dictionary");
 
        PG_TRY();
        {
@@ -1758,7 +1780,7 @@ PLyMapping_ToTuple(PLyTypeInfo * info, PyObject * mapping)
        TupleDesc       desc;
        HeapTuple       tuple;
        Datum      *values;
-       char       *nulls;
+       bool       *nulls;
        volatile int i;
 
        Assert(PyMapping_Check(mapping));
@@ -1770,7 +1792,7 @@ PLyMapping_ToTuple(PLyTypeInfo * info, PyObject * mapping)
 
        /* Build tuple */
        values = palloc(sizeof(Datum) * desc->natts);
-       nulls = palloc(sizeof(char) * desc->natts);
+       nulls = palloc(sizeof(bool) * desc->natts);
        for (i = 0; i < desc->natts; ++i)
        {
                char       *key;
@@ -1785,7 +1807,7 @@ PLyMapping_ToTuple(PLyTypeInfo * info, PyObject * mapping)
                        if (value == Py_None)
                        {
                                values[i] = (Datum) NULL;
-                               nulls[i] = 'n';
+                               nulls[i] = true;
                        }
                        else if (value)
                        {
@@ -1793,7 +1815,7 @@ PLyMapping_ToTuple(PLyTypeInfo * info, PyObject * mapping)
 
                                so = PyObject_Str(value);
                                if (so == NULL)
-                                       PLy_elog(ERROR, "cannot convert mapping type");
+                                       PLy_elog(ERROR, "could not compute string representation of Python object");
                                valuestr = PyString_AsString(so);
 
                                values[i] = InputFunctionCall(&info->out.r.atts[i].typfunc
@@ -1802,14 +1824,14 @@ PLyMapping_ToTuple(PLyTypeInfo * info, PyObject * mapping)
                                                                                          ,-1);
                                Py_DECREF(so);
                                so = NULL;
-                               nulls[i] = ' ';
+                               nulls[i] = false;
                        }
                        else
                                ereport(ERROR,
                                                (errcode(ERRCODE_UNDEFINED_COLUMN),
-                                                errmsg("no mapping found with key \"%s\"", key),
-                                                errhint("to return null in specific column, "
-                                         "add value None to map with key named after column")));
+                                                errmsg("key \"%s\" not found in mapping", key),
+                                                errhint("To return null in a column, "
+                                         "add the value None to the mapping with the key named after the column.")));
 
                        Py_XDECREF(value);
                        value = NULL;
@@ -1823,7 +1845,7 @@ PLyMapping_ToTuple(PLyTypeInfo * info, PyObject * mapping)
                PG_END_TRY();
        }
 
-       tuple = heap_formtuple(desc, values, nulls);
+       tuple = heap_form_tuple(desc, values, nulls);
        ReleaseTupleDesc(desc);
        pfree(values);
        pfree(nulls);
@@ -1838,7 +1860,7 @@ PLySequence_ToTuple(PLyTypeInfo * info, PyObject * sequence)
        TupleDesc       desc;
        HeapTuple       tuple;
        Datum      *values;
-       char       *nulls;
+       bool       *nulls;
        volatile int i;
 
        Assert(PySequence_Check(sequence));
@@ -1852,7 +1874,7 @@ PLySequence_ToTuple(PLyTypeInfo * info, PyObject * sequence)
        if (PySequence_Length(sequence) != desc->natts)
                ereport(ERROR,
                                (errcode(ERRCODE_DATATYPE_MISMATCH),
-               errmsg("returned sequence's length must be same as tuple's length")));
+               errmsg("length of returned sequence did not match number of columns in row")));
 
        if (info->is_rowtype == 2)
                PLy_output_tuple_funcs(info, desc);
@@ -1860,7 +1882,7 @@ PLySequence_ToTuple(PLyTypeInfo * info, PyObject * sequence)
 
        /* Build tuple */
        values = palloc(sizeof(Datum) * desc->natts);
-       nulls = palloc(sizeof(char) * desc->natts);
+       nulls = palloc(sizeof(bool) * desc->natts);
        for (i = 0; i < desc->natts; ++i)
        {
                PyObject   *volatile value,
@@ -1874,7 +1896,7 @@ PLySequence_ToTuple(PLyTypeInfo * info, PyObject * sequence)
                        if (value == Py_None)
                        {
                                values[i] = (Datum) NULL;
-                               nulls[i] = 'n';
+                               nulls[i] = true;
                        }
                        else if (value)
                        {
@@ -1882,7 +1904,7 @@ PLySequence_ToTuple(PLyTypeInfo * info, PyObject * sequence)
 
                                so = PyObject_Str(value);
                                if (so == NULL)
-                                       PLy_elog(ERROR, "cannot convert sequence type");
+                                       PLy_elog(ERROR, "could not compute string representation of Python object");
                                valuestr = PyString_AsString(so);
                                values[i] = InputFunctionCall(&info->out.r.atts[i].typfunc
                                                                                          ,valuestr
@@ -1890,7 +1912,7 @@ PLySequence_ToTuple(PLyTypeInfo * info, PyObject * sequence)
                                                                                          ,-1);
                                Py_DECREF(so);
                                so = NULL;
-                               nulls[i] = ' ';
+                               nulls[i] = false;
                        }
 
                        Py_XDECREF(value);
@@ -1905,7 +1927,7 @@ PLySequence_ToTuple(PLyTypeInfo * info, PyObject * sequence)
                PG_END_TRY();
        }
 
-       tuple = heap_formtuple(desc, values, nulls);
+       tuple = heap_form_tuple(desc, values, nulls);
        ReleaseTupleDesc(desc);
        pfree(values);
        pfree(nulls);
@@ -1920,7 +1942,7 @@ PLyObject_ToTuple(PLyTypeInfo * info, PyObject * object)
        TupleDesc       desc;
        HeapTuple       tuple;
        Datum      *values;
-       char       *nulls;
+       bool       *nulls;
        volatile int i;
 
        desc = lookup_rowtype_tupdesc(info->out.d.typoid, -1);
@@ -1930,7 +1952,7 @@ PLyObject_ToTuple(PLyTypeInfo * info, PyObject * object)
 
        /* Build tuple */
        values = palloc(sizeof(Datum) * desc->natts);
-       nulls = palloc(sizeof(char) * desc->natts);
+       nulls = palloc(sizeof(bool) * desc->natts);
        for (i = 0; i < desc->natts; ++i)
        {
                char       *key;
@@ -1945,7 +1967,7 @@ PLyObject_ToTuple(PLyTypeInfo * info, PyObject * object)
                        if (value == Py_None)
                        {
                                values[i] = (Datum) NULL;
-                               nulls[i] = 'n';
+                               nulls[i] = true;
                        }
                        else if (value)
                        {
@@ -1953,7 +1975,7 @@ PLyObject_ToTuple(PLyTypeInfo * info, PyObject * object)
 
                                so = PyObject_Str(value);
                                if (so == NULL)
-                                       PLy_elog(ERROR, "cannot convert object type");
+                                       PLy_elog(ERROR, "could not compute string representation of Python object");
                                valuestr = PyString_AsString(so);
                                values[i] = InputFunctionCall(&info->out.r.atts[i].typfunc
                                                                                          ,valuestr
@@ -1961,15 +1983,15 @@ PLyObject_ToTuple(PLyTypeInfo * info, PyObject * object)
                                                                                          ,-1);
                                Py_DECREF(so);
                                so = NULL;
-                               nulls[i] = ' ';
+                               nulls[i] = false;
                        }
                        else
                                ereport(ERROR,
                                                (errcode(ERRCODE_UNDEFINED_COLUMN),
-                                                errmsg("no attribute named \"%s\"", key),
-                                                errhint("to return null in specific column, "
-                                                          "let returned object to have attribute named "
-                                                                "after column with value None")));
+                                                errmsg("attribute \"%s\" does not exist in Python object", key),
+                                                errhint("To return null in a column, "
+                                                                "let the returned object have an attribute named "
+                                                                "after column with value None.")));
 
                        Py_XDECREF(value);
                        value = NULL;
@@ -1983,7 +2005,7 @@ PLyObject_ToTuple(PLyTypeInfo * info, PyObject * object)
                PG_END_TRY();
        }
 
-       tuple = heap_formtuple(desc, values, nulls);
+       tuple = heap_form_tuple(desc, values, nulls);
        ReleaseTupleDesc(desc);
        pfree(values);
        pfree(nulls);
@@ -2189,7 +2211,7 @@ PLy_plan_status(PyObject * self, PyObject * args)
                return Py_True;
                /* return PyInt_FromLong(self->status); */
        }
-       PyErr_SetString(PLy_exc_error, "plan.status() takes no arguments");
+       PLy_exception_set(PLy_exc_error, "plan.status takes no arguments");
        return NULL;
 }
 
@@ -2319,21 +2341,21 @@ PLy_spi_prepare(PyObject * self, PyObject * args)
        /* Can't execute more if we have an unhandled error */
        if (PLy_error_in_progress)
        {
-               PyErr_SetString(PLy_exc_error, "Transaction aborted.");
+               PLy_exception_set(PLy_exc_error, "transaction aborted");
                return NULL;
        }
 
        if (!PyArg_ParseTuple(args, "s|O", &query, &list))
        {
-               PyErr_SetString(PLy_exc_spi_error,
-                                               "Invalid arguments for plpy.prepare()");
+               PLy_exception_set(PLy_exc_spi_error,
+                                                 "invalid arguments for plpy.prepare");
                return NULL;
        }
 
        if (list && (!PySequence_Check(list)))
        {
-               PyErr_SetString(PLy_exc_spi_error,
-                                        "Second argument in plpy.prepare() must be a sequence");
+               PLy_exception_set(PLy_exc_spi_error,
+                                                 "second argument of plpy.prepare must be a sequence");
                return NULL;
        }
 
@@ -2377,7 +2399,8 @@ PLy_spi_prepare(PyObject * self, PyObject * args)
 
                                        optr = PySequence_GetItem(list, i);
                                        if (!PyString_Check(optr))
-                                               elog(ERROR, "Type names must be strings.");
+                                               ereport(ERROR,
+                                                               (errmsg("plpy.prepare: type name at ordinal position %d is not a string", i)));
                                        sptr = PyString_AsString(optr);
 
                                        /********************************************************
@@ -2402,7 +2425,9 @@ PLy_spi_prepare(PyObject * self, PyObject * args)
                                        if (typeStruct->typtype != TYPTYPE_COMPOSITE)
                                                PLy_output_datum_func(&plan->args[i], typeTup);
                                        else
-                                               elog(ERROR, "tuples not handled in plpy.prepare, yet.");
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                                errmsg("plpy.prepare does not support composite types")));
                                        ReleaseSysCache(typeTup);
                                }
                        }
@@ -2429,10 +2454,10 @@ PLy_spi_prepare(PyObject * self, PyObject * args)
                Py_DECREF(plan);
                Py_XDECREF(optr);
                if (!PyErr_Occurred())
-                       PyErr_SetString(PLy_exc_spi_error,
-                                                       "Unknown error in PLy_spi_prepare");
+                       PLy_exception_set(PLy_exc_spi_error,
+                                                         "unrecognized error in PLy_spi_prepare");
                /* XXX this oughta be replaced with errcontext mechanism */
-               PLy_elog(WARNING, "in function %s:",
+               PLy_elog(WARNING, "in PL/Python function \"%s\"",
                                 PLy_procedure_name(PLy_curr_procedure));
                return NULL;
        }
@@ -2455,7 +2480,7 @@ PLy_spi_execute(PyObject * self, PyObject * args)
        /* Can't execute more if we have an unhandled error */
        if (PLy_error_in_progress)
        {
-               PyErr_SetString(PLy_exc_error, "Transaction aborted.");
+               PLy_exception_set(PLy_exc_error, "transaction aborted");
                return NULL;
        }
 
@@ -2468,7 +2493,7 @@ PLy_spi_execute(PyObject * self, PyObject * args)
                is_PLyPlanObject(plan))
                return PLy_spi_execute_plan(plan, list, limit);
 
-       PyErr_SetString(PLy_exc_error, "Expected a query or plan.");
+       PLy_exception_set(PLy_exc_error, "plpy.execute expected a query or a plan");
        return NULL;
 }
 
@@ -2485,9 +2510,7 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, long limit)
        {
                if (!PySequence_Check(list) || PyString_Check(list))
                {
-                       char       *msg = "plpy.execute() takes a sequence as its second argument";
-
-                       PyErr_SetString(PLy_exc_spi_error, msg);
+                       PLy_exception_set(PLy_exc_spi_error, "plpy.execute takes a sequence as its second argument");
                        return NULL;
                }
                nargs = PySequence_Length(list);
@@ -2503,12 +2526,14 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, long limit)
                PyObject   *so = PyObject_Str(list);
 
                if (!so)
-                       PLy_elog(ERROR, "function \"%s\" could not execute plan",
+                       PLy_elog(ERROR, "PL/Python function \"%s\" could not execute plan",
                                         PLy_procedure_name(PLy_curr_procedure));
                sv = PyString_AsString(so);
-               PLy_exception_set(PLy_exc_spi_error,
-                                                 "Expected sequence of %d arguments, got %d. %s",
-                                                 plan->nargs, nargs, sv);
+               PLy_exception_set_plural(PLy_exc_spi_error,
+                                                                "Expected sequence of %d argument, got %d: %s",
+                                                                "Expected sequence of %d arguments, got %d: %s",
+                                                                plan->nargs,
+                                                                plan->nargs, nargs, sv);
                Py_DECREF(so);
 
                return NULL;
@@ -2530,7 +2555,7 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, long limit)
                        {
                                so = PyObject_Str(elem);
                                if (!so)
-                                       PLy_elog(ERROR, "function \"%s\" could not execute plan",
+                                       PLy_elog(ERROR, "PL/Python function \"%s\" could not execute plan",
                                                         PLy_procedure_name(PLy_curr_procedure));
                                Py_DECREF(elem);
 
@@ -2593,10 +2618,10 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, long limit)
                }
 
                if (!PyErr_Occurred())
-                       PyErr_SetString(PLy_exc_error,
-                                                       "Unknown error in PLy_spi_execute_plan");
+                       PLy_exception_set(PLy_exc_error,
+                                                         "unrecognized error in PLy_spi_execute_plan");
                /* XXX this oughta be replaced with errcontext mechanism */
-               PLy_elog(WARNING, "in function %s:",
+               PLy_elog(WARNING, "in PL/Python function \"%s\"",
                                 PLy_procedure_name(PLy_curr_procedure));
                return NULL;
        }
@@ -2640,10 +2665,10 @@ PLy_spi_execute_query(char *query, long limit)
                PLy_error_in_progress = CopyErrorData();
                FlushErrorState();
                if (!PyErr_Occurred())
-                       PyErr_SetString(PLy_exc_spi_error,
-                                                       "Unknown error in PLy_spi_execute_query");
+                       PLy_exception_set(PLy_exc_spi_error,
+                                                         "unrecognized error in PLy_spi_execute_query");
                /* XXX this oughta be replaced with errcontext mechanism */
-               PLy_elog(WARNING, "in function %s:",
+               PLy_elog(WARNING, "in PL/Python function \"%s\"",
                                 PLy_procedure_name(PLy_curr_procedure));
                return NULL;
        }
@@ -2711,8 +2736,8 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status)
                        PLy_error_in_progress = CopyErrorData();
                        FlushErrorState();
                        if (!PyErr_Occurred())
-                               PyErr_SetString(PLy_exc_error,
-                                                       "Unknown error in PLy_spi_execute_fetch_result");
+                               PLy_exception_set(PLy_exc_error,
+                                                                 "unrecognized error in PLy_spi_execute_fetch_result");
                        Py_DECREF(result);
                        PLy_typeinfo_dealloc(&args);
                        return NULL;
@@ -2742,6 +2767,8 @@ _PG_init(void)
        if (inited)
                return;
 
+       pg_bindtextdomain(TEXTDOMAIN);
+
        Py_Initialize();
        PLy_init_interp();
        PLy_init_plpy();
@@ -2761,7 +2788,7 @@ PLy_init_interp(void)
 
        mainmod = PyImport_AddModule("__main__");
        if (mainmod == NULL || PyErr_Occurred())
-               PLy_elog(ERROR, "could not import \"__main__\" module.");
+               PLy_elog(ERROR, "could not import \"__main__\" module");
        Py_INCREF(mainmod);
        PLy_interp_globals = PyModule_GetDict(mainmod);
        PLy_interp_safe_globals = PyDict_New();
@@ -2784,9 +2811,9 @@ PLy_init_plpy(void)
         * initialize plpy module
         */
        if (PyType_Ready(&PLy_PlanType) < 0)
-               elog(ERROR, "could not init PLy_PlanType");
+               elog(ERROR, "could not initialize PLy_PlanType");
        if (PyType_Ready(&PLy_ResultType) < 0)
-               elog(ERROR, "could not init PLy_ResultType");
+               elog(ERROR, "could not initialize PLy_ResultType");
 
        plpy = Py_InitModule("plpy", PLy_methods);
        plpy_dict = PyModule_GetDict(plpy);
@@ -2808,7 +2835,7 @@ PLy_init_plpy(void)
        plpy_mod = PyImport_AddModule("plpy");
        PyDict_SetItemString(main_dict, "plpy", plpy_mod);
        if (PyErr_Occurred())
-               elog(ERROR, "could not init plpy");
+               elog(ERROR, "could not initialize plpy");
 }
 
 /* the python interface to the elog function
@@ -2870,7 +2897,7 @@ PLy_output(volatile int level, PyObject * self, PyObject * args)
        if (so == NULL || ((sv = PyString_AsString(so)) == NULL))
        {
                level = ERROR;
-               sv = "could not parse error message in `plpy.elog'";
+               sv = dgettext(TEXTDOMAIN, "could not parse error message in plpy.elog");
        }
 
        oldcontext = CurrentMemoryContext;
@@ -2920,8 +2947,8 @@ PLy_procedure_name(PLyProcedure * proc)
        return proc->proname;
 }
 
-/* output a python traceback/exception via the postgresql elog
- * function.  not pretty.
+/*
+ * Call PyErr_SetString with a vprint interface and translation support
  */
 static void
 PLy_exception_set(PyObject * exc, const char *fmt,...)
@@ -2930,7 +2957,27 @@ PLy_exception_set(PyObject * exc, const char *fmt,...)
        va_list         ap;
 
        va_start(ap, fmt);
-       vsnprintf(buf, sizeof(buf), fmt, ap);
+       vsnprintf(buf, sizeof(buf), dgettext(TEXTDOMAIN, fmt), ap);
+       va_end(ap);
+
+       PyErr_SetString(exc, buf);
+}
+
+/*
+ * The same, pluralized.
+ */
+static void
+PLy_exception_set_plural(PyObject *exc,
+                                                const char *fmt_singular, const char *fmt_plural,
+                                                unsigned long n,...)
+{
+       char            buf[1024];
+       va_list         ap;
+
+       va_start(ap, n);
+       vsnprintf(buf, sizeof(buf),
+                         dngettext(TEXTDOMAIN, fmt_singular, fmt_plural, n),
+                         ap);
        va_end(ap);
 
        PyErr_SetString(exc, buf);
@@ -2956,7 +3003,7 @@ PLy_elog(int elevel, const char *fmt,...)
                bool            success;
 
                va_start(ap, fmt);
-               success = appendStringInfoVA(&emsg, fmt, ap);
+               success = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap);
                va_end(ap);
                if (success)
                        break;
@@ -2966,7 +3013,7 @@ PLy_elog(int elevel, const char *fmt,...)
        PG_TRY();
        {
                ereport(elevel,
-                               (errmsg("plpython: %s", emsg.data),
+                               (errmsg("PL/Python: %s", emsg.data),
                                 (xmsg) ? errdetail("%s", xmsg) : 0));
        }
        PG_CATCH();
@@ -3016,7 +3063,7 @@ PLy_traceback(int *xlevel)
        if (v && ((vob = PyObject_Str(v)) != NULL))
                vstr = PyString_AsString(vob);
        else
-               vstr = "Unknown";
+               vstr = "unknown";
 
        /*
         * I'm not sure what to do if eob is NULL here -- we can't call PLy_elog
@@ -3024,7 +3071,7 @@ PLy_traceback(int *xlevel)
         * recursion.  I'm not even sure if eob could be NULL here -- would an
         * Assert() be more appropriate?
         */
-       estr = eob ? PyString_AsString(eob) : "Unknown Exception";
+       estr = eob ? PyString_AsString(eob) : "unrecognized exception";
        initStringInfo(&xstr);
        appendStringInfo(&xstr, "%s: %s", estr, vstr);