Update 8.1 and 8.0 plpython to work with Python 2.5. This backports several
authorTom Lane <[email protected]>
Mon, 28 Jul 2008 18:45:05 +0000 (18:45 +0000)
committerTom Lane <[email protected]>
Mon, 28 Jul 2008 18:45:05 +0000 (18:45 +0000)
fixes made during the 8.2 development cycle, but not backported at the time
for lack of confidence in the new coding.

I didn't touch 7.4 because it has more problems than this: the configure
probe for Python fails.

src/pl/plpython/expected/plpython_function.out [new file with mode: 0644]
src/pl/plpython/plpython.c
src/pl/plpython/sql/plpython_function.sql [new file with mode: 0644]

diff --git a/src/pl/plpython/expected/plpython_function.out b/src/pl/plpython/expected/plpython_function.out
new file mode 100644 (file)
index 0000000..1161e11
--- /dev/null
@@ -0,0 +1,290 @@
+CREATE FUNCTION global_test_one() returns text
+    AS
+'if not SD.has_key("global_test"):
+       SD["global_test"] = "set by global_test_one"
+if not GD.has_key("global_test"):
+       GD["global_test"] = "set by global_test_one"
+return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]'
+    LANGUAGE plpythonu;
+CREATE FUNCTION global_test_two() returns text
+    AS
+'if not SD.has_key("global_test"):
+       SD["global_test"] = "set by global_test_two"
+if not GD.has_key("global_test"):
+       GD["global_test"] = "set by global_test_two"
+return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]'
+    LANGUAGE plpythonu;
+CREATE FUNCTION static_test() returns int4
+    AS
+'if SD.has_key("call"):
+       SD["call"] = SD["call"] + 1
+else:
+       SD["call"] = 1
+return SD["call"]
+'
+    LANGUAGE plpythonu;
+-- import python modules
+CREATE FUNCTION import_fail() returns text
+    AS
+'try:
+       import foosocket
+except Exception, ex:
+       plpy.notice("import socket failed -- %s" % str(ex))
+       return "failed as expected"
+return "succeeded, that wasn''t supposed to happen"'
+    LANGUAGE plpythonu;
+CREATE FUNCTION import_succeed() returns text
+       AS
+'try:
+  import array
+  import bisect
+  import calendar
+  import cmath
+  import errno
+  import math
+  import md5
+  import operator
+  import random
+  import re
+  import sha
+  import string
+  import time
+except Exception, ex:
+       plpy.notice("import failed -- %s" % str(ex))
+       return "failed, that wasn''t supposed to happen"
+return "succeeded, as expected"'
+    LANGUAGE plpythonu;
+CREATE FUNCTION import_test_one(text) RETURNS text
+       AS
+'import sha
+digest = sha.new(args[0])
+return digest.hexdigest()'
+       LANGUAGE plpythonu;
+CREATE FUNCTION import_test_two(users) RETURNS text
+       AS
+'import sha
+plain = args[0]["fname"] + args[0]["lname"]
+digest = sha.new(plain);
+return "sha hash of " + plain + " is " + digest.hexdigest()'
+       LANGUAGE plpythonu;
+CREATE FUNCTION argument_test_one(users, text, text) RETURNS text
+       AS
+'keys = args[0].keys()
+keys.sort()
+out = []
+for key in keys:
+    out.append("%s: %s" % (key, args[0][key]))
+words = args[1] + " " + args[2] + " => {" + ", ".join(out) + "}"
+return words'
+       LANGUAGE plpythonu;
+-- these triggers are dedicated to HPHC of RI who
+-- decided that my kid's name was william not willem, and
+-- vigorously resisted all efforts at correction.  they have
+-- since gone bankrupt...
+CREATE FUNCTION users_insert() returns trigger
+       AS
+'if TD["new"]["fname"] == None or TD["new"]["lname"] == None:
+       return "SKIP"
+if TD["new"]["username"] == None:
+       TD["new"]["username"] = TD["new"]["fname"][:1] + "_" + TD["new"]["lname"]
+       rv = "MODIFY"
+else:
+       rv = None
+if TD["new"]["fname"] == "william":
+       TD["new"]["fname"] = TD["args"][0]
+       rv = "MODIFY"
+return rv'
+       LANGUAGE plpythonu;
+CREATE FUNCTION users_update() returns trigger
+       AS
+'if TD["event"] == "UPDATE":
+       if TD["old"]["fname"] != TD["new"]["fname"] and TD["old"]["fname"] == TD["args"][0]:
+               return "SKIP"
+return None'
+       LANGUAGE plpythonu;
+CREATE FUNCTION users_delete() RETURNS trigger
+       AS
+'if TD["old"]["fname"] == TD["args"][0]:
+       return "SKIP"
+return None'
+       LANGUAGE plpythonu;
+CREATE TRIGGER users_insert_trig BEFORE INSERT ON users FOR EACH ROW
+       EXECUTE PROCEDURE users_insert ('willem');
+CREATE TRIGGER users_update_trig BEFORE UPDATE ON users FOR EACH ROW
+       EXECUTE PROCEDURE users_update ('willem');
+CREATE TRIGGER users_delete_trig BEFORE DELETE ON users FOR EACH ROW
+       EXECUTE PROCEDURE users_delete ('willem');
+-- nested calls
+--
+CREATE FUNCTION nested_call_one(text) RETURNS text
+       AS
+'q = "SELECT nested_call_two(''%s'')" % args[0]
+r = plpy.execute(q)
+return r[0]'
+       LANGUAGE plpythonu ;
+CREATE FUNCTION nested_call_two(text) RETURNS text
+       AS
+'q = "SELECT nested_call_three(''%s'')" % args[0]
+r = plpy.execute(q)
+return r[0]'
+       LANGUAGE plpythonu ;
+CREATE FUNCTION nested_call_three(text) RETURNS text
+       AS
+'return args[0]'
+       LANGUAGE plpythonu ;
+-- some spi stuff
+CREATE FUNCTION spi_prepared_plan_test_one(text) RETURNS text
+       AS
+'if not SD.has_key("myplan"):
+       q = "SELECT count(*) FROM users WHERE lname = $1"
+       SD["myplan"] = plpy.prepare(q, [ "text" ])
+try:
+       rv = plpy.execute(SD["myplan"], [args[0]])
+       return "there are " + str(rv[0]["count"]) + " " + str(args[0]) + "s"
+except Exception, ex:
+       plpy.error(str(ex))
+return None
+'
+       LANGUAGE plpythonu;
+CREATE FUNCTION spi_prepared_plan_test_nested(text) RETURNS text
+       AS
+'if not SD.has_key("myplan"):
+       q = "SELECT spi_prepared_plan_test_one(''%s'') as count" % args[0]
+       SD["myplan"] = plpy.prepare(q)
+try:
+       rv = plpy.execute(SD["myplan"])
+       if len(rv):
+               return rv[0]["count"]
+except Exception, ex:
+       plpy.error(str(ex))
+return None
+'
+       LANGUAGE plpythonu;
+/* really stupid function just to get the module loaded
+*/
+CREATE FUNCTION stupid() RETURNS text AS 'return "zarkon"' LANGUAGE plpythonu;
+/* a typo
+*/
+CREATE FUNCTION invalid_type_uncaught(text) RETURNS text
+       AS
+'if not SD.has_key("plan"):
+       q = "SELECT fname FROM users WHERE lname = $1"
+       SD["plan"] = plpy.prepare(q, [ "test" ])
+rv = plpy.execute(SD["plan"], [ args[0] ])
+if len(rv):
+       return rv[0]["fname"]
+return None
+'
+       LANGUAGE plpythonu;
+/* for what it's worth catch the exception generated by
+ * the typo, and return None
+ */
+CREATE FUNCTION invalid_type_caught(text) RETURNS text
+       AS
+'if not SD.has_key("plan"):
+       q = "SELECT fname FROM users WHERE lname = $1"
+       try:
+               SD["plan"] = plpy.prepare(q, [ "test" ])
+       except plpy.SPIError, ex:
+               plpy.notice(str(ex))
+               return None
+rv = plpy.execute(SD["plan"], [ args[0] ])
+if len(rv):
+       return rv[0]["fname"]
+return None
+'
+       LANGUAGE plpythonu;
+/* for what it's worth catch the exception generated by
+ * the typo, and reraise it as a plain error
+ */
+CREATE FUNCTION invalid_type_reraised(text) RETURNS text
+       AS
+'if not SD.has_key("plan"):
+       q = "SELECT fname FROM users WHERE lname = $1"
+       try:
+               SD["plan"] = plpy.prepare(q, [ "test" ])
+       except plpy.SPIError, ex:
+               plpy.error(str(ex))
+rv = plpy.execute(SD["plan"], [ args[0] ])
+if len(rv):
+       return rv[0]["fname"]
+return None
+'
+       LANGUAGE plpythonu;
+/* no typo no messing about
+*/
+CREATE FUNCTION valid_type(text) RETURNS text
+       AS
+'if not SD.has_key("plan"):
+       SD["plan"] = plpy.prepare("SELECT fname FROM users WHERE lname = $1", [ "text" ])
+rv = plpy.execute(SD["plan"], [ args[0] ])
+if len(rv):
+       return rv[0]["fname"]
+return None
+'
+       LANGUAGE plpythonu;
+/* Flat out syntax error
+*/
+CREATE FUNCTION sql_syntax_error() RETURNS text
+        AS
+'plpy.execute("syntax error")'
+        LANGUAGE plpythonu;
+/* check the handling of uncaught python exceptions
+ */
+CREATE FUNCTION exception_index_invalid(text) RETURNS text
+       AS
+'return args[1]'
+       LANGUAGE plpythonu;
+/* check handling of nested exceptions
+ */
+CREATE FUNCTION exception_index_invalid_nested() RETURNS text
+       AS
+'rv = plpy.execute("SELECT test5(''foo'')")
+return rv[0]'
+       LANGUAGE plpythonu;
+CREATE FUNCTION join_sequences(sequences) RETURNS text
+       AS
+'if not args[0]["multipart"]:
+       return args[0]["sequence"]
+q = "SELECT sequence FROM xsequences WHERE pid = ''%s''" % args[0]["pid"]
+rv = plpy.execute(q)
+seq = args[0]["sequence"]
+for r in rv:
+       seq = seq + r["sequence"]
+return seq
+'
+       LANGUAGE plpythonu;
+--
+-- Universal Newline Support
+-- 
+CREATE OR REPLACE FUNCTION newline_lf() RETURNS integer AS
+'x = 100\ny = 23\nreturn x + y\n'
+LANGUAGE plpythonu;
+CREATE OR REPLACE FUNCTION newline_cr() RETURNS integer AS
+'x = 100\ry = 23\rreturn x + y\r'
+LANGUAGE plpythonu;
+CREATE OR REPLACE FUNCTION newline_crlf() RETURNS integer AS
+'x = 100\r\ny = 23\r\nreturn x + y\r\n'
+LANGUAGE plpythonu;
+--
+-- Unicode error handling
+--
+CREATE FUNCTION unicode_return_error() RETURNS text AS '
+return u"\\x80"
+' LANGUAGE plpythonu;
+CREATE FUNCTION unicode_trigger_error() RETURNS trigger AS '
+TD["new"]["testvalue"] = u"\\x80"
+return "MODIFY"
+' LANGUAGE plpythonu;
+CREATE TRIGGER unicode_test_bi BEFORE INSERT ON unicode_test
+  FOR EACH ROW EXECUTE PROCEDURE unicode_trigger_error();
+CREATE FUNCTION unicode_plan_error1() RETURNS text AS '
+plan = plpy.prepare("SELECT $1 AS testvalue", ["text"])
+rv = plpy.execute(plan, [u"\\x80"], 1)
+return rv[0]["testvalue"]
+' LANGUAGE plpythonu;
+CREATE FUNCTION unicode_plan_error2() RETURNS text AS '
+plan = plpy.prepare("SELECT $1 AS testvalue1, $2 AS testvalue2", ["text", "text"])
+rv = plpy.execute(plan, u"\\x80", 1)
+return rv[0]["testvalue1"]
+' LANGUAGE plpythonu;
index d8b7189d48a234178b668919393a0c5767ee3fde..fcdc44e7dd1636fd1ffc261898ef6a646eff3128 100644 (file)
  */
 
 #include <Python.h>
+
+/*
+ * Py_ssize_t compat for Python <= 2.4
+ */
+#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
+typedef int Py_ssize_t;
+#define PY_SSIZE_T_MAX INT_MAX
+#define PY_SSIZE_T_MIN INT_MIN
+#endif
+
 #include "postgres.h"
 
 /* system stuff */
@@ -1541,11 +1551,11 @@ static void PLy_result_dealloc(PyObject *);
 static PyObject *PLy_result_getattr(PyObject *, char *);
 static PyObject *PLy_result_nrows(PyObject *, PyObject *);
 static PyObject *PLy_result_status(PyObject *, PyObject *);
-static int     PLy_result_length(PyObject *);
-static PyObject *PLy_result_item(PyObject *, int);
-static PyObject *PLy_result_slice(PyObject *, int, int);
-static int     PLy_result_ass_item(PyObject *, int, PyObject *);
-static int     PLy_result_ass_slice(PyObject *, int, int, PyObject *);
+static Py_ssize_t PLy_result_length(PyObject *);
+static PyObject *PLy_result_item(PyObject *, Py_ssize_t);
+static PyObject *PLy_result_slice(PyObject *, Py_ssize_t, Py_ssize_t);
+static int     PLy_result_ass_item(PyObject *, Py_ssize_t, PyObject *);
+static int     PLy_result_ass_slice(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *);
 
 
 static PyObject *PLy_spi_prepare(PyObject *, PyObject *);
@@ -1565,9 +1575,9 @@ static PyTypeObject PLy_PlanType = {
        /*
         * methods
         */
-       (destructor) PLy_plan_dealloc,          /* tp_dealloc */
+       PLy_plan_dealloc,                       /* tp_dealloc */
        0,                                                      /* tp_print */
-       (getattrfunc) PLy_plan_getattr,         /* tp_getattr */
+       PLy_plan_getattr,                       /* tp_getattr */
        0,                                                      /* tp_setattr */
        0,                                                      /* tp_compare */
        0,                                                      /* tp_repr */
@@ -1580,7 +1590,7 @@ static PyTypeObject PLy_PlanType = {
        0,                                                      /* tp_getattro */
        0,                                                      /* tp_setattro */
        0,                                                      /* tp_as_buffer */
-       0,                                                      /* tp_xxx4 */
+       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,       /* tp_flags */
        PLy_plan_doc,                           /* tp_doc */
 };
 
@@ -1589,15 +1599,14 @@ static PyMethodDef PLy_plan_methods[] = {
        {NULL, NULL, 0, NULL}
 };
 
-
 static PySequenceMethods PLy_result_as_sequence = {
-       (inquiry) PLy_result_length,    /* sq_length */
-       (binaryfunc) 0,                         /* sq_concat */
-       (intargfunc) 0,                         /* sq_repeat */
-       (intargfunc) PLy_result_item,           /* sq_item */
-       (intintargfunc) PLy_result_slice,       /* sq_slice */
-       (intobjargproc) PLy_result_ass_item,            /* sq_ass_item */
-       (intintobjargproc) PLy_result_ass_slice,        /* sq_ass_slice */
+       PLy_result_length,              /* sq_length */
+       NULL,                                   /* sq_concat */
+       NULL,                                   /* sq_repeat */
+       PLy_result_item,                /* sq_item */
+       PLy_result_slice,               /* sq_slice */
+       PLy_result_ass_item,    /* sq_ass_item */
+       PLy_result_ass_slice,   /* sq_ass_slice */
 };
 
 static PyTypeObject PLy_ResultType = {
@@ -1610,9 +1619,9 @@ static PyTypeObject PLy_ResultType = {
        /*
         * methods
         */
-       (destructor) PLy_result_dealloc,        /* tp_dealloc */
+       PLy_result_dealloc,                     /* tp_dealloc */
        0,                                                      /* tp_print */
-       (getattrfunc) PLy_result_getattr,       /* tp_getattr */
+       PLy_result_getattr,                     /* tp_getattr */
        0,                                                      /* tp_setattr */
        0,                                                      /* tp_compare */
        0,                                                      /* tp_repr */
@@ -1625,7 +1634,7 @@ static PyTypeObject PLy_ResultType = {
        0,                                                      /* tp_getattro */
        0,                                                      /* tp_setattro */
        0,                                                      /* tp_as_buffer */
-       0,                                                      /* tp_xxx4 */
+       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,       /* tp_flags */
        PLy_result_doc,                         /* tp_doc */
 };
 
@@ -1698,7 +1707,7 @@ PLy_plan_dealloc(PyObject * arg)
                PLy_free(ob->args);
        }
 
-       PyMem_DEL(arg);
+       arg->ob_type->tp_free(arg);
 }
 
 
@@ -1753,7 +1762,7 @@ PLy_result_dealloc(PyObject * arg)
        Py_XDECREF(ob->rows);
        Py_XDECREF(ob->status);
 
-       PyMem_DEL(ob);
+       arg->ob_type->tp_free(arg);
 }
 
 static PyObject *
@@ -1780,7 +1789,7 @@ PLy_result_status(PyObject * self, PyObject * args)
        return ob->status;
 }
 
-static int
+static Py_ssize_t
 PLy_result_length(PyObject * arg)
 {
        PLyResultObject *ob = (PLyResultObject *) arg;
@@ -1789,7 +1798,7 @@ PLy_result_length(PyObject * arg)
 }
 
 static PyObject *
-PLy_result_item(PyObject * arg, int idx)
+PLy_result_item(PyObject * arg, Py_ssize_t idx)
 {
        PyObject   *rv;
        PLyResultObject *ob = (PLyResultObject *) arg;
@@ -1801,7 +1810,7 @@ PLy_result_item(PyObject * arg, int idx)
 }
 
 static int
-PLy_result_ass_item(PyObject * arg, int idx, PyObject * item)
+PLy_result_ass_item(PyObject * arg, Py_ssize_t idx, PyObject * item)
 {
        int                     rv;
        PLyResultObject *ob = (PLyResultObject *) arg;
@@ -1812,7 +1821,7 @@ PLy_result_ass_item(PyObject * arg, int idx, PyObject * item)
 }
 
 static PyObject *
-PLy_result_slice(PyObject * arg, int lidx, int hidx)
+PLy_result_slice(PyObject * arg, Py_ssize_t lidx, Py_ssize_t hidx)
 {
        PyObject   *rv;
        PLyResultObject *ob = (PLyResultObject *) arg;
@@ -1825,7 +1834,7 @@ PLy_result_slice(PyObject * arg, int lidx, int hidx)
 }
 
 static int
-PLy_result_ass_slice(PyObject * arg, int lidx, int hidx, PyObject * slice)
+PLy_result_ass_slice(PyObject * arg, Py_ssize_t lidx, Py_ssize_t hidx, PyObject * slice)
 {
        int                     rv;
        PLyResultObject *ob = (PLyResultObject *) arg;
@@ -2036,13 +2045,14 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, long limit)
        PG_TRY();
        {
                char       *nulls = palloc(nargs * sizeof(char));
+               volatile int j;
 
-               for (i = 0; i < nargs; i++)
+               for (j = 0; j < nargs; j++)
                {
                        PyObject   *elem,
                                           *so;
 
-                       elem = PySequence_GetItem(list, i);
+                       elem = PySequence_GetItem(list, j);
                        if (elem != Py_None)
                        {
                                so = PyObject_Str(elem);
@@ -2055,10 +2065,10 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, long limit)
                                {
                                        char *sv = PyString_AsString(so);
 
-                                       plan->values[i] =
-                                               FunctionCall3(&(plan->args[i].out.d.typfunc),
+                                       plan->values[j] =
+                                               FunctionCall3(&(plan->args[j].out.d.typfunc),
                                                                          CStringGetDatum(sv),
-                                                               ObjectIdGetDatum(plan->args[i].out.d.typioparam),
+                                                               ObjectIdGetDatum(plan->args[j].out.d.typioparam),
                                                                          Int32GetDatum(-1));
                                }
                                PG_CATCH();
@@ -2069,13 +2079,13 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, long limit)
                                PG_END_TRY();
 
                                Py_DECREF(so);
-                               nulls[i] = ' ';
+                               nulls[j] = ' ';
                        }
                        else
                        {
                                Py_DECREF(elem);
-                               plan->values[i] = (Datum) 0;
-                               nulls[i] = 'n';
+                               plan->values[j] = (Datum) 0;
+                               nulls[j] = 'n';
                        }
                }
 
@@ -2086,6 +2096,8 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, long limit)
        }
        PG_CATCH();
        {
+               int             k;
+
                MemoryContextSwitchTo(oldcontext);
                PLy_error_in_progress = CopyErrorData();
                FlushErrorState();
@@ -2093,13 +2105,13 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, long limit)
                /*
                 * cleanup plan->values array
                 */
-               for (i = 0; i < nargs; i++)
+               for (k = 0; k < nargs; k++)
                {
-                       if (!plan->args[i].out.d.typbyval &&
-                               (plan->values[i] != (Datum) NULL))
+                       if (!plan->args[k].out.d.typbyval &&
+                               (plan->values[k] != (Datum) NULL))
                        {
-                               pfree(DatumGetPointer(plan->values[i]));
-                               plan->values[i] = (Datum) NULL;
+                               pfree(DatumGetPointer(plan->values[k]));
+                               plan->values[k] = (Datum) NULL;
                        }
                }
 
@@ -2319,7 +2331,11 @@ PLy_init_plpy(void)
        /*
         * initialize plpy module
         */
-       PLy_PlanType.ob_type = PLy_ResultType.ob_type = &PyType_Type;
+       if (PyType_Ready(&PLy_PlanType) < 0)
+               elog(ERROR, "could not init PLy_PlanType");
+       if (PyType_Ready(&PLy_ResultType) < 0)
+               elog(ERROR, "could not init PLy_ResultType");
+
        plpy = Py_InitModule("plpy", PLy_methods);
        plpy_dict = PyModule_GetDict(plpy);
 
diff --git a/src/pl/plpython/sql/plpython_function.sql b/src/pl/plpython/sql/plpython_function.sql
new file mode 100644 (file)
index 0000000..70e681d
--- /dev/null
@@ -0,0 +1,342 @@
+
+
+CREATE FUNCTION global_test_one() returns text
+    AS
+'if not SD.has_key("global_test"):
+       SD["global_test"] = "set by global_test_one"
+if not GD.has_key("global_test"):
+       GD["global_test"] = "set by global_test_one"
+return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]'
+    LANGUAGE plpythonu;
+
+CREATE FUNCTION global_test_two() returns text
+    AS
+'if not SD.has_key("global_test"):
+       SD["global_test"] = "set by global_test_two"
+if not GD.has_key("global_test"):
+       GD["global_test"] = "set by global_test_two"
+return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]'
+    LANGUAGE plpythonu;
+
+
+CREATE FUNCTION static_test() returns int4
+    AS
+'if SD.has_key("call"):
+       SD["call"] = SD["call"] + 1
+else:
+       SD["call"] = 1
+return SD["call"]
+'
+    LANGUAGE plpythonu;
+
+-- import python modules
+
+CREATE FUNCTION import_fail() returns text
+    AS
+'try:
+       import foosocket
+except Exception, ex:
+       plpy.notice("import socket failed -- %s" % str(ex))
+       return "failed as expected"
+return "succeeded, that wasn''t supposed to happen"'
+    LANGUAGE plpythonu;
+
+
+CREATE FUNCTION import_succeed() returns text
+       AS
+'try:
+  import array
+  import bisect
+  import calendar
+  import cmath
+  import errno
+  import math
+  import md5
+  import operator
+  import random
+  import re
+  import sha
+  import string
+  import time
+except Exception, ex:
+       plpy.notice("import failed -- %s" % str(ex))
+       return "failed, that wasn''t supposed to happen"
+return "succeeded, as expected"'
+    LANGUAGE plpythonu;
+
+CREATE FUNCTION import_test_one(text) RETURNS text
+       AS
+'import sha
+digest = sha.new(args[0])
+return digest.hexdigest()'
+       LANGUAGE plpythonu;
+
+CREATE FUNCTION import_test_two(users) RETURNS text
+       AS
+'import sha
+plain = args[0]["fname"] + args[0]["lname"]
+digest = sha.new(plain);
+return "sha hash of " + plain + " is " + digest.hexdigest()'
+       LANGUAGE plpythonu;
+
+CREATE FUNCTION argument_test_one(users, text, text) RETURNS text
+       AS
+'keys = args[0].keys()
+keys.sort()
+out = []
+for key in keys:
+    out.append("%s: %s" % (key, args[0][key]))
+words = args[1] + " " + args[2] + " => {" + ", ".join(out) + "}"
+return words'
+       LANGUAGE plpythonu;
+
+
+-- these triggers are dedicated to HPHC of RI who
+-- decided that my kid's name was william not willem, and
+-- vigorously resisted all efforts at correction.  they have
+-- since gone bankrupt...
+
+CREATE FUNCTION users_insert() returns trigger
+       AS
+'if TD["new"]["fname"] == None or TD["new"]["lname"] == None:
+       return "SKIP"
+if TD["new"]["username"] == None:
+       TD["new"]["username"] = TD["new"]["fname"][:1] + "_" + TD["new"]["lname"]
+       rv = "MODIFY"
+else:
+       rv = None
+if TD["new"]["fname"] == "william":
+       TD["new"]["fname"] = TD["args"][0]
+       rv = "MODIFY"
+return rv'
+       LANGUAGE plpythonu;
+
+
+CREATE FUNCTION users_update() returns trigger
+       AS
+'if TD["event"] == "UPDATE":
+       if TD["old"]["fname"] != TD["new"]["fname"] and TD["old"]["fname"] == TD["args"][0]:
+               return "SKIP"
+return None'
+       LANGUAGE plpythonu;
+
+
+CREATE FUNCTION users_delete() RETURNS trigger
+       AS
+'if TD["old"]["fname"] == TD["args"][0]:
+       return "SKIP"
+return None'
+       LANGUAGE plpythonu;
+
+
+CREATE TRIGGER users_insert_trig BEFORE INSERT ON users FOR EACH ROW
+       EXECUTE PROCEDURE users_insert ('willem');
+
+CREATE TRIGGER users_update_trig BEFORE UPDATE ON users FOR EACH ROW
+       EXECUTE PROCEDURE users_update ('willem');
+
+CREATE TRIGGER users_delete_trig BEFORE DELETE ON users FOR EACH ROW
+       EXECUTE PROCEDURE users_delete ('willem');
+
+
+-- nested calls
+--
+
+CREATE FUNCTION nested_call_one(text) RETURNS text
+       AS
+'q = "SELECT nested_call_two(''%s'')" % args[0]
+r = plpy.execute(q)
+return r[0]'
+       LANGUAGE plpythonu ;
+
+CREATE FUNCTION nested_call_two(text) RETURNS text
+       AS
+'q = "SELECT nested_call_three(''%s'')" % args[0]
+r = plpy.execute(q)
+return r[0]'
+       LANGUAGE plpythonu ;
+
+CREATE FUNCTION nested_call_three(text) RETURNS text
+       AS
+'return args[0]'
+       LANGUAGE plpythonu ;
+
+-- some spi stuff
+
+CREATE FUNCTION spi_prepared_plan_test_one(text) RETURNS text
+       AS
+'if not SD.has_key("myplan"):
+       q = "SELECT count(*) FROM users WHERE lname = $1"
+       SD["myplan"] = plpy.prepare(q, [ "text" ])
+try:
+       rv = plpy.execute(SD["myplan"], [args[0]])
+       return "there are " + str(rv[0]["count"]) + " " + str(args[0]) + "s"
+except Exception, ex:
+       plpy.error(str(ex))
+return None
+'
+       LANGUAGE plpythonu;
+
+CREATE FUNCTION spi_prepared_plan_test_nested(text) RETURNS text
+       AS
+'if not SD.has_key("myplan"):
+       q = "SELECT spi_prepared_plan_test_one(''%s'') as count" % args[0]
+       SD["myplan"] = plpy.prepare(q)
+try:
+       rv = plpy.execute(SD["myplan"])
+       if len(rv):
+               return rv[0]["count"]
+except Exception, ex:
+       plpy.error(str(ex))
+return None
+'
+       LANGUAGE plpythonu;
+
+
+/* really stupid function just to get the module loaded
+*/
+CREATE FUNCTION stupid() RETURNS text AS 'return "zarkon"' LANGUAGE plpythonu;
+
+/* a typo
+*/
+CREATE FUNCTION invalid_type_uncaught(text) RETURNS text
+       AS
+'if not SD.has_key("plan"):
+       q = "SELECT fname FROM users WHERE lname = $1"
+       SD["plan"] = plpy.prepare(q, [ "test" ])
+rv = plpy.execute(SD["plan"], [ args[0] ])
+if len(rv):
+       return rv[0]["fname"]
+return None
+'
+       LANGUAGE plpythonu;
+
+/* for what it's worth catch the exception generated by
+ * the typo, and return None
+ */
+CREATE FUNCTION invalid_type_caught(text) RETURNS text
+       AS
+'if not SD.has_key("plan"):
+       q = "SELECT fname FROM users WHERE lname = $1"
+       try:
+               SD["plan"] = plpy.prepare(q, [ "test" ])
+       except plpy.SPIError, ex:
+               plpy.notice(str(ex))
+               return None
+rv = plpy.execute(SD["plan"], [ args[0] ])
+if len(rv):
+       return rv[0]["fname"]
+return None
+'
+       LANGUAGE plpythonu;
+
+/* for what it's worth catch the exception generated by
+ * the typo, and reraise it as a plain error
+ */
+CREATE FUNCTION invalid_type_reraised(text) RETURNS text
+       AS
+'if not SD.has_key("plan"):
+       q = "SELECT fname FROM users WHERE lname = $1"
+       try:
+               SD["plan"] = plpy.prepare(q, [ "test" ])
+       except plpy.SPIError, ex:
+               plpy.error(str(ex))
+rv = plpy.execute(SD["plan"], [ args[0] ])
+if len(rv):
+       return rv[0]["fname"]
+return None
+'
+       LANGUAGE plpythonu;
+
+
+/* no typo no messing about
+*/
+CREATE FUNCTION valid_type(text) RETURNS text
+       AS
+'if not SD.has_key("plan"):
+       SD["plan"] = plpy.prepare("SELECT fname FROM users WHERE lname = $1", [ "text" ])
+rv = plpy.execute(SD["plan"], [ args[0] ])
+if len(rv):
+       return rv[0]["fname"]
+return None
+'
+       LANGUAGE plpythonu;
+/* Flat out syntax error
+*/
+CREATE FUNCTION sql_syntax_error() RETURNS text
+        AS
+'plpy.execute("syntax error")'
+        LANGUAGE plpythonu;
+
+/* check the handling of uncaught python exceptions
+ */
+CREATE FUNCTION exception_index_invalid(text) RETURNS text
+       AS
+'return args[1]'
+       LANGUAGE plpythonu;
+
+/* check handling of nested exceptions
+ */
+CREATE FUNCTION exception_index_invalid_nested() RETURNS text
+       AS
+'rv = plpy.execute("SELECT test5(''foo'')")
+return rv[0]'
+       LANGUAGE plpythonu;
+
+
+CREATE FUNCTION join_sequences(sequences) RETURNS text
+       AS
+'if not args[0]["multipart"]:
+       return args[0]["sequence"]
+q = "SELECT sequence FROM xsequences WHERE pid = ''%s''" % args[0]["pid"]
+rv = plpy.execute(q)
+seq = args[0]["sequence"]
+for r in rv:
+       seq = seq + r["sequence"]
+return seq
+'
+       LANGUAGE plpythonu;
+
+--
+-- Universal Newline Support
+-- 
+
+CREATE OR REPLACE FUNCTION newline_lf() RETURNS integer AS
+'x = 100\ny = 23\nreturn x + y\n'
+LANGUAGE plpythonu;
+
+CREATE OR REPLACE FUNCTION newline_cr() RETURNS integer AS
+'x = 100\ry = 23\rreturn x + y\r'
+LANGUAGE plpythonu;
+
+CREATE OR REPLACE FUNCTION newline_crlf() RETURNS integer AS
+'x = 100\r\ny = 23\r\nreturn x + y\r\n'
+LANGUAGE plpythonu;
+
+--
+-- Unicode error handling
+--
+
+CREATE FUNCTION unicode_return_error() RETURNS text AS '
+return u"\\x80"
+' LANGUAGE plpythonu;
+
+CREATE FUNCTION unicode_trigger_error() RETURNS trigger AS '
+TD["new"]["testvalue"] = u"\\x80"
+return "MODIFY"
+' LANGUAGE plpythonu;
+
+CREATE TRIGGER unicode_test_bi BEFORE INSERT ON unicode_test
+  FOR EACH ROW EXECUTE PROCEDURE unicode_trigger_error();
+
+CREATE FUNCTION unicode_plan_error1() RETURNS text AS '
+plan = plpy.prepare("SELECT $1 AS testvalue", ["text"])
+rv = plpy.execute(plan, [u"\\x80"], 1)
+return rv[0]["testvalue"]
+' LANGUAGE plpythonu;
+
+CREATE FUNCTION unicode_plan_error2() RETURNS text AS '
+plan = plpy.prepare("SELECT $1 AS testvalue1, $2 AS testvalue2", ["text", "text"])
+rv = plpy.execute(plan, u"\\x80", 1)
+return rv[0]["testvalue1"]
+' LANGUAGE plpythonu;