[HACKERS] Rewriting PL/Python's typeio code

Поиск
Список
Период
Сортировка
От Tom Lane
Тема [HACKERS] Rewriting PL/Python's typeio code
Дата
Msg-id 24449.1509393613@sss.pgh.pa.us
обсуждение исходный текст
Ответы Re: Rewriting PL/Python's typeio code  (Anthony Bykov <a.bykov@postgrespro.ru>)
Список pgsql-hackers
I started to work on teaching PL/Python about domains over composite,
and soon found that it was a can of worms.  Aside from the difficulty
of shoehorning that in with a minimal patch, there were pre-existing
problems.  I found that it didn't do arrays of domains right either
(ok, that's an oversight in my recent commit c12d570fa), and there
are assorted bugs that have been there much longer.  For instance, if
you return a composite type containing a domain, it fails to enforce
domain constraints on the type's field.  Also, if a transform function
is in use, it missed enforcing domain constraints on the result.
And in many places it missed checking domain constraints on null values,
because the plpy_typeio code simply wasn't called for Py_None.

Plus the code was really messy and duplicative, e.g. domain_check was
called in three different places ... which wasn't enough.  It also did
a lot of repetitive catalog lookups.

So, I ended up rewriting/refactoring pretty heavily.  The new idea
is to solve these problems by making heavier use of recursion between
plpy_typeio's conversion functions, and in particular to treat domains
as if they were containers.  So now there's exactly one place to call
domain_check, in a conversion function that has first recursed to do
conversion of the base type.  Nulls are treated more honestly, and
the SQL-to-Python functions are more careful about not leaking memory.
Also, I solved some of the repetitive catalog lookup problems by
making the code rely as much as possible on the typcache (which I think
didn't exist when this code originated).  I added a couple of small
features to typcache to help with that.

This is a fairly large amount of code churn, and it could stand testing
by someone who's more Python-savvy than I am.  So I'll stick it into
the upcoming commitfest as a separate item.

            regards, tom lane

diff --git a/contrib/hstore_plpython/expected/hstore_plpython.out
b/contrib/hstore_plpython/expected/hstore_plpython.out
index df49cd5..1ab5fee 100644
*** a/contrib/hstore_plpython/expected/hstore_plpython.out
--- b/contrib/hstore_plpython/expected/hstore_plpython.out
*************** AS $$
*** 68,79 ****
  val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}]
  return val
  $$;
!  SELECT test2arr();
                             test2arr
  --------------------------------------------------------------
   {"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""}
  (1 row)

  -- test as part of prepare/execute
  CREATE FUNCTION test3() RETURNS void
  LANGUAGE plpythonu
--- 68,97 ----
  val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}]
  return val
  $$;
! SELECT test2arr();
                             test2arr
  --------------------------------------------------------------
   {"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""}
  (1 row)

+ -- test python -> domain over hstore
+ CREATE DOMAIN hstore_foo AS hstore CHECK(VALUE ? 'foo');
+ CREATE FUNCTION test2dom(fn text) RETURNS hstore_foo
+ LANGUAGE plpythonu
+ TRANSFORM FOR TYPE hstore
+ AS $$
+ return {'a': 1, fn: 'boo', 'c': None}
+ $$;
+ SELECT test2dom('foo');
+              test2dom
+ -----------------------------------
+  "a"=>"1", "c"=>NULL, "foo"=>"boo"
+ (1 row)
+
+ SELECT test2dom('bar');  -- fail
+ ERROR:  value for domain hstore_foo violates check constraint "hstore_foo_check"
+ CONTEXT:  while creating return value
+ PL/Python function "test2dom"
  -- test as part of prepare/execute
  CREATE FUNCTION test3() RETURNS void
  LANGUAGE plpythonu
diff --git a/contrib/hstore_plpython/sql/hstore_plpython.sql b/contrib/hstore_plpython/sql/hstore_plpython.sql
index 911bbd6..2c54ee6 100644
*** a/contrib/hstore_plpython/sql/hstore_plpython.sql
--- b/contrib/hstore_plpython/sql/hstore_plpython.sql
*************** val = [{'a': 1, 'b': 'boo', 'c': None},
*** 60,66 ****
  return val
  $$;

!  SELECT test2arr();


  -- test as part of prepare/execute
--- 60,80 ----
  return val
  $$;

! SELECT test2arr();
!
!
! -- test python -> domain over hstore
! CREATE DOMAIN hstore_foo AS hstore CHECK(VALUE ? 'foo');
!
! CREATE FUNCTION test2dom(fn text) RETURNS hstore_foo
! LANGUAGE plpythonu
! TRANSFORM FOR TYPE hstore
! AS $$
! return {'a': 1, fn: 'boo', 'c': None}
! $$;
!
! SELECT test2dom('foo');
! SELECT test2dom('bar');  -- fail


  -- test as part of prepare/execute
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 7aadc5d..f6450c4 100644
*** a/src/backend/utils/cache/typcache.c
--- b/src/backend/utils/cache/typcache.c
*************** lookup_type_cache(Oid type_id, int flags
*** 377,382 ****
--- 377,383 ----
          typentry->typstorage = typtup->typstorage;
          typentry->typtype = typtup->typtype;
          typentry->typrelid = typtup->typrelid;
+         typentry->typelem = typtup->typelem;

          /* If it's a domain, immediately thread it into the domain cache list */
          if (typentry->typtype == TYPTYPE_DOMAIN)
*************** load_typcache_tupdesc(TypeCacheEntry *ty
*** 791,796 ****
--- 792,803 ----
      Assert(typentry->tupDesc->tdrefcount > 0);
      typentry->tupDesc->tdrefcount++;

+     /*
+      * In future, we could take some pains to not increment the seqno if the
+      * tupdesc didn't really change; but for now it's not worth it.
+      */
+     typentry->tupDescSeqNo++;
+
      relation_close(rel, AccessShareLock);
  }

diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index ea799a8..c203dab 100644
*** a/src/include/utils/typcache.h
--- b/src/include/utils/typcache.h
*************** typedef struct TypeCacheEntry
*** 40,45 ****
--- 40,46 ----
      char        typstorage;
      char        typtype;
      Oid            typrelid;
+     Oid            typelem;

      /*
       * Information obtained from opfamily entries
*************** typedef struct TypeCacheEntry
*** 75,83 ****
      /*
       * Tuple descriptor if it's a composite type (row type).  NULL if not
       * composite or information hasn't yet been requested.  (NOTE: this is a
!      * reference-counted tupledesc.)
       */
      TupleDesc    tupDesc;

      /*
       * Fields computed when TYPECACHE_RANGE_INFO is requested.  Zeroes if not
--- 76,86 ----
      /*
       * Tuple descriptor if it's a composite type (row type).  NULL if not
       * composite or information hasn't yet been requested.  (NOTE: this is a
!      * reference-counted tupledesc.)  To simplify caching dependent info,
!      * tupDescSeqNo is incremented each time tupDesc is rebuilt in a session.
       */
      TupleDesc    tupDesc;
+     int64        tupDescSeqNo;

      /*
       * Fields computed when TYPECACHE_RANGE_INFO is requested.  Zeroes if not
diff --git a/src/pl/plpython/expected/plpython_types.out b/src/pl/plpython/expected/plpython_types.out
index 893de30..eda965a 100644
*** a/src/pl/plpython/expected/plpython_types.out
--- b/src/pl/plpython/expected/plpython_types.out
*************** SELECT * FROM test_type_conversion_array
*** 765,770 ****
--- 765,840 ----
  ERROR:  value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
  CONTEXT:  while creating return value
  PL/Python function "test_type_conversion_array_domain_check_violation"
+ --
+ -- Arrays of domains
+ --
+ CREATE FUNCTION test_read_uint2_array(x uint2[]) RETURNS uint2 AS $$
+ plpy.info(x, type(x))
+ return x[0]
+ $$ LANGUAGE plpythonu;
+ select test_read_uint2_array(array[1::uint2]);
+ INFO:  ([1], <type 'list'>)
+  test_read_uint2_array
+ -----------------------
+                      1
+ (1 row)
+
+ CREATE FUNCTION test_build_uint2_array(x int2) RETURNS uint2[] AS $$
+ return [x, x]
+ $$ LANGUAGE plpythonu;
+ select test_build_uint2_array(1::int2);
+  test_build_uint2_array
+ ------------------------
+  {1,1}
+ (1 row)
+
+ select test_build_uint2_array(-1::int2);  -- fail
+ ERROR:  value for domain uint2 violates check constraint "uint2_check"
+ CONTEXT:  while creating return value
+ PL/Python function "test_build_uint2_array"
+ --
+ -- ideally this would work, but for now it doesn't, because the return value
+ -- is [[2,4], [2,4]] which our conversion code thinks should become a 2-D
+ -- integer array, not an array of arrays.
+ --
+ CREATE FUNCTION test_type_conversion_domain_array(x integer[])
+   RETURNS ordered_pair_domain[] AS $$
+ return [x, x]
+ $$ LANGUAGE plpythonu;
+ select test_type_conversion_domain_array(array[2,4]);
+ ERROR:  return value of function with array return type is not a Python sequence
+ CONTEXT:  while creating return value
+ PL/Python function "test_type_conversion_domain_array"
+ select test_type_conversion_domain_array(array[4,2]);  -- fail
+ ERROR:  return value of function with array return type is not a Python sequence
+ CONTEXT:  while creating return value
+ PL/Python function "test_type_conversion_domain_array"
+ CREATE FUNCTION test_type_conversion_domain_array2(x ordered_pair_domain)
+   RETURNS integer AS $$
+ plpy.info(x, type(x))
+ return x[1]
+ $$ LANGUAGE plpythonu;
+ select test_type_conversion_domain_array2(array[2,4]);
+ INFO:  ([2, 4], <type 'list'>)
+  test_type_conversion_domain_array2
+ ------------------------------------
+                                   4
+ (1 row)
+
+ select test_type_conversion_domain_array2(array[4,2]);  -- fail
+ ERROR:  value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
+ CREATE FUNCTION test_type_conversion_array_domain_array(x ordered_pair_domain[])
+   RETURNS ordered_pair_domain AS $$
+ plpy.info(x, type(x))
+ return x[0]
+ $$ LANGUAGE plpythonu;
+ select test_type_conversion_array_domain_array(array[array[2,4]::ordered_pair_domain]);
+ INFO:  ([[2, 4]], <type 'list'>)
+  test_type_conversion_array_domain_array
+ -----------------------------------------
+  {2,4}
+ (1 row)
+
  ---
  --- Composite types
  ---
*************** SELECT test_composite_type_input(row(1,
*** 821,826 ****
--- 891,954 ----
  (1 row)

  --
+ -- Domains within composite
+ --
+ CREATE TYPE nnint_container AS (f1 int, f2 nnint);
+ CREATE FUNCTION nnint_test(x int, y int) RETURNS nnint_container AS $$
+ return {'f1': x, 'f2': y}
+ $$ LANGUAGE plpythonu;
+ SELECT nnint_test(null, 3);
+  nnint_test
+ ------------
+  (,3)
+ (1 row)
+
+ SELECT nnint_test(3, null);  -- fail
+ ERROR:  value for domain nnint violates check constraint "nnint_check"
+ CONTEXT:  while creating return value
+ PL/Python function "nnint_test"
+ --
+ -- Domains of composite
+ --
+ CREATE DOMAIN ordered_named_pair AS named_pair_2 CHECK((VALUE).i <= (VALUE).j);
+ CREATE FUNCTION read_ordered_named_pair(p ordered_named_pair) RETURNS integer AS $$
+ return p['i'] + p['j']
+ $$ LANGUAGE plpythonu;
+ SELECT read_ordered_named_pair(row(1, 2));
+  read_ordered_named_pair
+ -------------------------
+                        3
+ (1 row)
+
+ SELECT read_ordered_named_pair(row(2, 1));  -- fail
+ ERROR:  value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+ CREATE FUNCTION build_ordered_named_pair(i int, j int) RETURNS ordered_named_pair AS $$
+ return {'i': i, 'j': j}
+ $$ LANGUAGE plpythonu;
+ SELECT build_ordered_named_pair(1,2);
+  build_ordered_named_pair
+ --------------------------
+  (1,2)
+ (1 row)
+
+ SELECT build_ordered_named_pair(2,1);  -- fail
+ ERROR:  value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+ CONTEXT:  while creating return value
+ PL/Python function "build_ordered_named_pair"
+ CREATE FUNCTION build_ordered_named_pairs(i int, j int) RETURNS ordered_named_pair[] AS $$
+ return [{'i': i, 'j': j}, {'i': i, 'j': j+1}]
+ $$ LANGUAGE plpythonu;
+ SELECT build_ordered_named_pairs(1,2);
+  build_ordered_named_pairs
+ ---------------------------
+  {"(1,2)","(1,3)"}
+ (1 row)
+
+ SELECT build_ordered_named_pairs(2,1);  -- fail
+ ERROR:  value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+ CONTEXT:  while creating return value
+ PL/Python function "build_ordered_named_pairs"
+ --
  -- Prepared statements
  --
  CREATE OR REPLACE FUNCTION test_prep_bool_input() RETURNS int
diff --git a/src/pl/plpython/expected/plpython_types_3.out b/src/pl/plpython/expected/plpython_types_3.out
index 2d853bd..69f958c 100644
*** a/src/pl/plpython/expected/plpython_types_3.out
--- b/src/pl/plpython/expected/plpython_types_3.out
*************** SELECT * FROM test_type_conversion_array
*** 765,770 ****
--- 765,840 ----
  ERROR:  value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
  CONTEXT:  while creating return value
  PL/Python function "test_type_conversion_array_domain_check_violation"
+ --
+ -- Arrays of domains
+ --
+ CREATE FUNCTION test_read_uint2_array(x uint2[]) RETURNS uint2 AS $$
+ plpy.info(x, type(x))
+ return x[0]
+ $$ LANGUAGE plpythonu;
+ select test_read_uint2_array(array[1::uint2]);
+ INFO:  ([1], <class 'list'>)
+  test_read_uint2_array
+ -----------------------
+                      1
+ (1 row)
+
+ CREATE FUNCTION test_build_uint2_array(x int2) RETURNS uint2[] AS $$
+ return [x, x]
+ $$ LANGUAGE plpythonu;
+ select test_build_uint2_array(1::int2);
+  test_build_uint2_array
+ ------------------------
+  {1,1}
+ (1 row)
+
+ select test_build_uint2_array(-1::int2);  -- fail
+ ERROR:  value for domain uint2 violates check constraint "uint2_check"
+ CONTEXT:  while creating return value
+ PL/Python function "test_build_uint2_array"
+ --
+ -- ideally this would work, but for now it doesn't, because the return value
+ -- is [[2,4], [2,4]] which our conversion code thinks should become a 2-D
+ -- integer array, not an array of arrays.
+ --
+ CREATE FUNCTION test_type_conversion_domain_array(x integer[])
+   RETURNS ordered_pair_domain[] AS $$
+ return [x, x]
+ $$ LANGUAGE plpythonu;
+ select test_type_conversion_domain_array(array[2,4]);
+ ERROR:  return value of function with array return type is not a Python sequence
+ CONTEXT:  while creating return value
+ PL/Python function "test_type_conversion_domain_array"
+ select test_type_conversion_domain_array(array[4,2]);  -- fail
+ ERROR:  return value of function with array return type is not a Python sequence
+ CONTEXT:  while creating return value
+ PL/Python function "test_type_conversion_domain_array"
+ CREATE FUNCTION test_type_conversion_domain_array2(x ordered_pair_domain)
+   RETURNS integer AS $$
+ plpy.info(x, type(x))
+ return x[1]
+ $$ LANGUAGE plpythonu;
+ select test_type_conversion_domain_array2(array[2,4]);
+ INFO:  ([2, 4], <class 'list'>)
+  test_type_conversion_domain_array2
+ ------------------------------------
+                                   4
+ (1 row)
+
+ select test_type_conversion_domain_array2(array[4,2]);  -- fail
+ ERROR:  value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
+ CREATE FUNCTION test_type_conversion_array_domain_array(x ordered_pair_domain[])
+   RETURNS ordered_pair_domain AS $$
+ plpy.info(x, type(x))
+ return x[0]
+ $$ LANGUAGE plpythonu;
+ select test_type_conversion_array_domain_array(array[array[2,4]::ordered_pair_domain]);
+ INFO:  ([[2, 4]], <class 'list'>)
+  test_type_conversion_array_domain_array
+ -----------------------------------------
+  {2,4}
+ (1 row)
+
  ---
  --- Composite types
  ---
*************** SELECT test_composite_type_input(row(1,
*** 821,826 ****
--- 891,954 ----
  (1 row)

  --
+ -- Domains within composite
+ --
+ CREATE TYPE nnint_container AS (f1 int, f2 nnint);
+ CREATE FUNCTION nnint_test(x int, y int) RETURNS nnint_container AS $$
+ return {'f1': x, 'f2': y}
+ $$ LANGUAGE plpythonu;
+ SELECT nnint_test(null, 3);
+  nnint_test
+ ------------
+  (,3)
+ (1 row)
+
+ SELECT nnint_test(3, null);  -- fail
+ ERROR:  value for domain nnint violates check constraint "nnint_check"
+ CONTEXT:  while creating return value
+ PL/Python function "nnint_test"
+ --
+ -- Domains of composite
+ --
+ CREATE DOMAIN ordered_named_pair AS named_pair_2 CHECK((VALUE).i <= (VALUE).j);
+ CREATE FUNCTION read_ordered_named_pair(p ordered_named_pair) RETURNS integer AS $$
+ return p['i'] + p['j']
+ $$ LANGUAGE plpythonu;
+ SELECT read_ordered_named_pair(row(1, 2));
+  read_ordered_named_pair
+ -------------------------
+                        3
+ (1 row)
+
+ SELECT read_ordered_named_pair(row(2, 1));  -- fail
+ ERROR:  value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+ CREATE FUNCTION build_ordered_named_pair(i int, j int) RETURNS ordered_named_pair AS $$
+ return {'i': i, 'j': j}
+ $$ LANGUAGE plpythonu;
+ SELECT build_ordered_named_pair(1,2);
+  build_ordered_named_pair
+ --------------------------
+  (1,2)
+ (1 row)
+
+ SELECT build_ordered_named_pair(2,1);  -- fail
+ ERROR:  value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+ CONTEXT:  while creating return value
+ PL/Python function "build_ordered_named_pair"
+ CREATE FUNCTION build_ordered_named_pairs(i int, j int) RETURNS ordered_named_pair[] AS $$
+ return [{'i': i, 'j': j}, {'i': i, 'j': j+1}]
+ $$ LANGUAGE plpythonu;
+ SELECT build_ordered_named_pairs(1,2);
+  build_ordered_named_pairs
+ ---------------------------
+  {"(1,2)","(1,3)"}
+ (1 row)
+
+ SELECT build_ordered_named_pairs(2,1);  -- fail
+ ERROR:  value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+ CONTEXT:  while creating return value
+ PL/Python function "build_ordered_named_pairs"
+ --
  -- Prepared statements
  --
  CREATE OR REPLACE FUNCTION test_prep_bool_input() RETURNS int
diff --git a/src/pl/plpython/plpy_cursorobject.c b/src/pl/plpython/plpy_cursorobject.c
index 0108471..10ca786 100644
*** a/src/pl/plpython/plpy_cursorobject.c
--- b/src/pl/plpython/plpy_cursorobject.c
***************
*** 9,14 ****
--- 9,15 ----
  #include <limits.h>

  #include "access/xact.h"
+ #include "catalog/pg_type.h"
  #include "mb/pg_wchar.h"
  #include "utils/memutils.h"

*************** static PyObject *
*** 106,111 ****
--- 107,113 ----
  PLy_cursor_query(const char *query)
  {
      PLyCursorObject *cursor;
+     PLyExecutionContext *exec_ctx = PLy_current_execution_context();
      volatile MemoryContext oldcontext;
      volatile ResourceOwner oldowner;

*************** PLy_cursor_query(const char *query)
*** 116,122 ****
      cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
                                           "PL/Python cursor context",
                                           ALLOCSET_DEFAULT_SIZES);
!     PLy_typeinfo_init(&cursor->result, cursor->mcxt);

      oldcontext = CurrentMemoryContext;
      oldowner = CurrentResourceOwner;
--- 118,128 ----
      cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
                                           "PL/Python cursor context",
                                           ALLOCSET_DEFAULT_SIZES);
!
!     /* Initialize for converting result tuples to Python */
!     PLy_input_setup_func(&cursor->result, cursor->mcxt,
!                          RECORDOID, -1,
!                          exec_ctx->curr_proc);

      oldcontext = CurrentMemoryContext;
      oldowner = CurrentResourceOwner;
*************** PLy_cursor_query(const char *query)
*** 125,131 ****

      PG_TRY();
      {
-         PLyExecutionContext *exec_ctx = PLy_current_execution_context();
          SPIPlanPtr    plan;
          Portal        portal;

--- 131,136 ----
*************** PLy_cursor_plan(PyObject *ob, PyObject *
*** 166,171 ****
--- 171,177 ----
      volatile int nargs;
      int            i;
      PLyPlanObject *plan;
+     PLyExecutionContext *exec_ctx = PLy_current_execution_context();
      volatile MemoryContext oldcontext;
      volatile ResourceOwner oldowner;

*************** PLy_cursor_plan(PyObject *ob, PyObject *
*** 208,214 ****
      cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
                                           "PL/Python cursor context",
                                           ALLOCSET_DEFAULT_SIZES);
!     PLy_typeinfo_init(&cursor->result, cursor->mcxt);

      oldcontext = CurrentMemoryContext;
      oldowner = CurrentResourceOwner;
--- 214,224 ----
      cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
                                           "PL/Python cursor context",
                                           ALLOCSET_DEFAULT_SIZES);
!
!     /* Initialize for converting result tuples to Python */
!     PLy_input_setup_func(&cursor->result, cursor->mcxt,
!                          RECORDOID, -1,
!                          exec_ctx->curr_proc);

      oldcontext = CurrentMemoryContext;
      oldowner = CurrentResourceOwner;
*************** PLy_cursor_plan(PyObject *ob, PyObject *
*** 217,223 ****

      PG_TRY();
      {
-         PLyExecutionContext *exec_ctx = PLy_current_execution_context();
          Portal        portal;
          char       *volatile nulls;
          volatile int j;
--- 227,232 ----
*************** PLy_cursor_plan(PyObject *ob, PyObject *
*** 229,267 ****

          for (j = 0; j < nargs; j++)
          {
              PyObject   *elem;

              elem = PySequence_GetItem(args, j);
!             if (elem != Py_None)
              {
!                 PG_TRY();
!                 {
!                     plan->values[j] =
!                         plan->args[j].out.d.func(&(plan->args[j].out.d),
!                                                  -1,
!                                                  elem,
!                                                  false);
!                 }
!                 PG_CATCH();
!                 {
!                     Py_DECREF(elem);
!                     PG_RE_THROW();
!                 }
!                 PG_END_TRY();

!                 Py_DECREF(elem);
!                 nulls[j] = ' ';
              }
!             else
              {
                  Py_DECREF(elem);
!                 plan->values[j] =
!                     InputFunctionCall(&(plan->args[j].out.d.typfunc),
!                                       NULL,
!                                       plan->args[j].out.d.typioparam,
!                                       -1);
!                 nulls[j] = 'n';
              }
          }

          portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls,
--- 238,261 ----

          for (j = 0; j < nargs; j++)
          {
+             PLyObToDatum *arg = &plan->args[j];
              PyObject   *elem;

              elem = PySequence_GetItem(args, j);
!             PG_TRY();
              {
!                 bool        isnull;

!                 plan->values[j] = PLy_output_convert(arg, elem, &isnull);
!                 nulls[j] = isnull ? 'n' : ' ';
              }
!             PG_CATCH();
              {
                  Py_DECREF(elem);
!                 PG_RE_THROW();
              }
+             PG_END_TRY();
+             Py_DECREF(elem);
          }

          portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls,
*************** PLy_cursor_plan(PyObject *ob, PyObject *
*** 281,287 ****
          /* cleanup plan->values array */
          for (k = 0; k < nargs; k++)
          {
!             if (!plan->args[k].out.d.typbyval &&
                  (plan->values[k] != PointerGetDatum(NULL)))
              {
                  pfree(DatumGetPointer(plan->values[k]));
--- 275,281 ----
          /* cleanup plan->values array */
          for (k = 0; k < nargs; k++)
          {
!             if (!plan->args[k].typbyval &&
                  (plan->values[k] != PointerGetDatum(NULL)))
              {
                  pfree(DatumGetPointer(plan->values[k]));
*************** PLy_cursor_plan(PyObject *ob, PyObject *
*** 298,304 ****

      for (i = 0; i < nargs; i++)
      {
!         if (!plan->args[i].out.d.typbyval &&
              (plan->values[i] != PointerGetDatum(NULL)))
          {
              pfree(DatumGetPointer(plan->values[i]));
--- 292,298 ----

      for (i = 0; i < nargs; i++)
      {
!         if (!plan->args[i].typbyval &&
              (plan->values[i] != PointerGetDatum(NULL)))
          {
              pfree(DatumGetPointer(plan->values[i]));
*************** PLy_cursor_iternext(PyObject *self)
*** 339,344 ****
--- 333,339 ----
  {
      PLyCursorObject *cursor;
      PyObject   *ret;
+     PLyExecutionContext *exec_ctx = PLy_current_execution_context();
      volatile MemoryContext oldcontext;
      volatile ResourceOwner oldowner;
      Portal        portal;
*************** PLy_cursor_iternext(PyObject *self)
*** 374,384 ****
          }
          else
          {
!             if (cursor->result.is_rowtype != 1)
!                 PLy_input_tuple_funcs(&cursor->result, SPI_tuptable->tupdesc);

!             ret = PLyDict_FromTuple(&cursor->result, SPI_tuptable->vals[0],
!                                     SPI_tuptable->tupdesc);
          }

          SPI_freetuptable(SPI_tuptable);
--- 369,379 ----
          }
          else
          {
!             PLy_input_setup_tuple(&cursor->result, SPI_tuptable->tupdesc,
!                                   exec_ctx->curr_proc);

!             ret = PLy_input_from_tuple(&cursor->result, SPI_tuptable->vals[0],
!                                        SPI_tuptable->tupdesc);
          }

          SPI_freetuptable(SPI_tuptable);
*************** PLy_cursor_fetch(PyObject *self, PyObjec
*** 401,406 ****
--- 396,402 ----
      PLyCursorObject *cursor;
      int            count;
      PLyResultObject *ret;
+     PLyExecutionContext *exec_ctx = PLy_current_execution_context();
      volatile MemoryContext oldcontext;
      volatile ResourceOwner oldowner;
      Portal        portal;
*************** PLy_cursor_fetch(PyObject *self, PyObjec
*** 437,445 ****
      {
          SPI_cursor_fetch(portal, true, count);

-         if (cursor->result.is_rowtype != 1)
-             PLy_input_tuple_funcs(&cursor->result, SPI_tuptable->tupdesc);
-
          Py_DECREF(ret->status);
          ret->status = PyInt_FromLong(SPI_OK_FETCH);

--- 433,438 ----
*************** PLy_cursor_fetch(PyObject *self, PyObjec
*** 465,475 ****
              Py_DECREF(ret->rows);
              ret->rows = PyList_New(SPI_processed);

              for (i = 0; i < SPI_processed; i++)
              {
!                 PyObject   *row = PLyDict_FromTuple(&cursor->result,
!                                                     SPI_tuptable->vals[i],
!                                                     SPI_tuptable->tupdesc);

                  PyList_SetItem(ret->rows, i, row);
              }
--- 458,471 ----
              Py_DECREF(ret->rows);
              ret->rows = PyList_New(SPI_processed);

+             PLy_input_setup_tuple(&cursor->result, SPI_tuptable->tupdesc,
+                                   exec_ctx->curr_proc);
+
              for (i = 0; i < SPI_processed; i++)
              {
!                 PyObject   *row = PLy_input_from_tuple(&cursor->result,
!                                                        SPI_tuptable->vals[i],
!                                                        SPI_tuptable->tupdesc);

                  PyList_SetItem(ret->rows, i, row);
              }
diff --git a/src/pl/plpython/plpy_cursorobject.h b/src/pl/plpython/plpy_cursorobject.h
index 018b169..e4d2c0e 100644
*** a/src/pl/plpython/plpy_cursorobject.h
--- b/src/pl/plpython/plpy_cursorobject.h
*************** typedef struct PLyCursorObject
*** 12,18 ****
  {
      PyObject_HEAD
      char       *portalname;
!     PLyTypeInfo result;
      bool        closed;
      MemoryContext mcxt;
  } PLyCursorObject;
--- 12,18 ----
  {
      PyObject_HEAD
      char       *portalname;
!     PLyDatumToOb result;
      bool        closed;
      MemoryContext mcxt;
  } PLyCursorObject;
diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c
index 26f61dd..b84886d 100644
*** a/src/pl/plpython/plpy_exec.c
--- b/src/pl/plpython/plpy_exec.c
*************** PLy_exec_function(FunctionCallInfo fcinf
*** 202,208 ****
           * return value as a special "void datum" rather than NULL (as is the
           * case for non-void-returning functions).
           */
!         if (proc->result.out.d.typoid == VOIDOID)
          {
              if (plrv != Py_None)
                  ereport(ERROR,
--- 202,208 ----
           * return value as a special "void datum" rather than NULL (as is the
           * case for non-void-returning functions).
           */
!         if (proc->result.typoid == VOIDOID)
          {
              if (plrv != Py_None)
                  ereport(ERROR,
*************** PLy_exec_function(FunctionCallInfo fcinf
*** 212,259 ****
              fcinfo->isnull = false;
              rv = (Datum) 0;
          }
!         else if (plrv == Py_None)
          {
-             fcinfo->isnull = true;
-
              /*
               * In a SETOF function, the iteration-ending null isn't a real
               * value; don't pass it through the input function, which might
               * complain.
               */
!             if (srfstate && srfstate->iter == NULL)
!                 rv = (Datum) 0;
!             else if (proc->result.is_rowtype < 1)
!                 rv = InputFunctionCall(&proc->result.out.d.typfunc,
!                                        NULL,
!                                        proc->result.out.d.typioparam,
!                                        -1);
!             else
!                 /* Tuple as None */
!                 rv = (Datum) NULL;
!         }
!         else if (proc->result.is_rowtype >= 1)
!         {
!             TupleDesc    desc;
!
!             /* make sure it's not an unnamed record */
!             Assert((proc->result.out.d.typoid == RECORDOID &&
!                     proc->result.out.d.typmod != -1) ||
!                    (proc->result.out.d.typoid != RECORDOID &&
!                     proc->result.out.d.typmod == -1));
!
!             desc = lookup_rowtype_tupdesc(proc->result.out.d.typoid,
!                                           proc->result.out.d.typmod);
!
!             rv = PLyObject_ToCompositeDatum(&proc->result, desc, plrv, false);
!             fcinfo->isnull = (rv == (Datum) NULL);
!
!             ReleaseTupleDesc(desc);
          }
          else
          {
!             fcinfo->isnull = false;
!             rv = (proc->result.out.d.func) (&proc->result.out.d, -1, plrv, false);
          }
      }
      PG_CATCH();
--- 212,233 ----
              fcinfo->isnull = false;
              rv = (Datum) 0;
          }
!         else if (plrv == Py_None &&
!                  srfstate && srfstate->iter == NULL)
          {
              /*
               * In a SETOF function, the iteration-ending null isn't a real
               * value; don't pass it through the input function, which might
               * complain.
               */
!             fcinfo->isnull = true;
!             rv = (Datum) 0;
          }
          else
          {
!             /* Normal conversion of result */
!             rv = PLy_output_convert(&proc->result, plrv,
!                                     &fcinfo->isnull);
          }
      }
      PG_CATCH();
*************** PLy_exec_trigger(FunctionCallInfo fcinfo
*** 328,347 ****
      PyObject   *volatile plargs = NULL;
      PyObject   *volatile plrv = NULL;
      TriggerData *tdata;

      Assert(CALLED_AS_TRIGGER(fcinfo));

      /*
!      * Input/output conversion for trigger tuples.  Use the result TypeInfo
!      * variable to store the tuple conversion info.  We do this over again on
!      * each call to cover the possibility that the relation's tupdesc changed
!      * since the trigger was last called. PLy_input_tuple_funcs and
!      * PLy_output_tuple_funcs are responsible for not doing repetitive work.
       */
!     tdata = (TriggerData *) fcinfo->context;
!
!     PLy_input_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att);
!     PLy_output_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att);

      PG_TRY();
      {
--- 302,333 ----
      PyObject   *volatile plargs = NULL;
      PyObject   *volatile plrv = NULL;
      TriggerData *tdata;
+     TupleDesc    rel_descr;

      Assert(CALLED_AS_TRIGGER(fcinfo));
+     tdata = (TriggerData *) fcinfo->context;

      /*
!      * Input/output conversion for trigger tuples.  We use the result and
!      * resultin variables to store the tuple conversion info.  We do this over
!      * again on each call to cover the possibility that the relation's tupdesc
!      * changed since the trigger was last called.  The PLy_xxx_setup_func
!      * calls should only happen once, but PLy_input_setup_tuple and
!      * PLy_output_setup_tuple are responsible for not doing repetitive work.
       */
!     rel_descr = RelationGetDescr(tdata->tg_relation);
!     if (proc->result.typoid != rel_descr->tdtypeid)
!         PLy_output_setup_func(&proc->result, proc->mcxt,
!                               rel_descr->tdtypeid,
!                               rel_descr->tdtypmod,
!                               proc);
!     if (proc->resultin.typoid != rel_descr->tdtypeid)
!         PLy_input_setup_func(&proc->resultin, proc->mcxt,
!                              rel_descr->tdtypeid,
!                              rel_descr->tdtypmod,
!                              proc);
!     PLy_output_setup_tuple(&proc->result, rel_descr, proc);
!     PLy_input_setup_tuple(&proc->resultin, rel_descr, proc);

      PG_TRY();
      {
*************** PLy_function_build_args(FunctionCallInfo
*** 436,481 ****
          args = PyList_New(proc->nargs);
          for (i = 0; i < proc->nargs; i++)
          {
!             if (proc->args[i].is_rowtype > 0)
!             {
!                 if (fcinfo->argnull[i])
!                     arg = NULL;
!                 else
!                 {
!                     HeapTupleHeader td;
!                     Oid            tupType;
!                     int32        tupTypmod;
!                     TupleDesc    tupdesc;
!                     HeapTupleData tmptup;
!
!                     td = DatumGetHeapTupleHeader(fcinfo->arg[i]);
!                     /* Extract rowtype info and find a tupdesc */
!                     tupType = HeapTupleHeaderGetTypeId(td);
!                     tupTypmod = HeapTupleHeaderGetTypMod(td);
!                     tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
!
!                     /* Set up I/O funcs if not done yet */
!                     if (proc->args[i].is_rowtype != 1)
!                         PLy_input_tuple_funcs(&(proc->args[i]), tupdesc);
!
!                     /* Build a temporary HeapTuple control structure */
!                     tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
!                     tmptup.t_data = td;

!                     arg = PLyDict_FromTuple(&(proc->args[i]), &tmptup, tupdesc);
!                     ReleaseTupleDesc(tupdesc);
!                 }
!             }
              else
!             {
!                 if (fcinfo->argnull[i])
!                     arg = NULL;
!                 else
!                 {
!                     arg = (proc->args[i].in.d.func) (&(proc->args[i].in.d),
!                                                      fcinfo->arg[i]);
!                 }
!             }

              if (arg == NULL)
              {
--- 422,433 ----
          args = PyList_New(proc->nargs);
          for (i = 0; i < proc->nargs; i++)
          {
!             PLyDatumToOb *arginfo = &proc->args[i];

!             if (fcinfo->argnull[i])
!                 arg = NULL;
              else
!                 arg = PLy_input_convert(arginfo, fcinfo->arg[i]);

              if (arg == NULL)
              {
*************** PLy_function_build_args(FunctionCallInfo
*** 493,499 ****
          }

          /* Set up output conversion for functions returning RECORD */
!         if (proc->result.out.d.typoid == RECORDOID)
          {
              TupleDesc    desc;

--- 445,451 ----
          }

          /* Set up output conversion for functions returning RECORD */
!         if (proc->result.typoid == RECORDOID)
          {
              TupleDesc    desc;

*************** PLy_function_build_args(FunctionCallInfo
*** 504,510 ****
                                  "that cannot accept type record")));

              /* cache the output conversion functions */
!             PLy_output_record_funcs(&(proc->result), desc);
          }
      }
      PG_CATCH();
--- 456,462 ----
                                  "that cannot accept type record")));

              /* cache the output conversion functions */
!             PLy_output_setup_record(&proc->result, desc, proc);
          }
      }
      PG_CATCH();
*************** static PyObject *
*** 723,728 ****
--- 675,681 ----
  PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *rv)
  {
      TriggerData *tdata = (TriggerData *) fcinfo->context;
+     TupleDesc    rel_descr = RelationGetDescr(tdata->tg_relation);
      PyObject   *pltname,
                 *pltevent,
                 *pltwhen,
*************** PLy_trigger_build_args(FunctionCallInfo
*** 790,797 ****
                  pltevent = PyString_FromString("INSERT");

                  PyDict_SetItemString(pltdata, "old", Py_None);
!                 pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
!                                            tdata->tg_relation->rd_att);
                  PyDict_SetItemString(pltdata, "new", pytnew);
                  Py_DECREF(pytnew);
                  *rv = tdata->tg_trigtuple;
--- 743,751 ----
                  pltevent = PyString_FromString("INSERT");

                  PyDict_SetItemString(pltdata, "old", Py_None);
!                 pytnew = PLy_input_from_tuple(&proc->resultin,
!                                               tdata->tg_trigtuple,
!                                               rel_descr);
                  PyDict_SetItemString(pltdata, "new", pytnew);
                  Py_DECREF(pytnew);
                  *rv = tdata->tg_trigtuple;
*************** PLy_trigger_build_args(FunctionCallInfo
*** 801,808 ****
                  pltevent = PyString_FromString("DELETE");

                  PyDict_SetItemString(pltdata, "new", Py_None);
!                 pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
!                                            tdata->tg_relation->rd_att);
                  PyDict_SetItemString(pltdata, "old", pytold);
                  Py_DECREF(pytold);
                  *rv = tdata->tg_trigtuple;
--- 755,763 ----
                  pltevent = PyString_FromString("DELETE");

                  PyDict_SetItemString(pltdata, "new", Py_None);
!                 pytold = PLy_input_from_tuple(&proc->resultin,
!                                               tdata->tg_trigtuple,
!                                               rel_descr);
                  PyDict_SetItemString(pltdata, "old", pytold);
                  Py_DECREF(pytold);
                  *rv = tdata->tg_trigtuple;
*************** PLy_trigger_build_args(FunctionCallInfo
*** 811,822 ****
              {
                  pltevent = PyString_FromString("UPDATE");

!                 pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_newtuple,
!                                            tdata->tg_relation->rd_att);
                  PyDict_SetItemString(pltdata, "new", pytnew);
                  Py_DECREF(pytnew);
!                 pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
!                                            tdata->tg_relation->rd_att);
                  PyDict_SetItemString(pltdata, "old", pytold);
                  Py_DECREF(pytold);
                  *rv = tdata->tg_newtuple;
--- 766,779 ----
              {
                  pltevent = PyString_FromString("UPDATE");

!                 pytnew = PLy_input_from_tuple(&proc->resultin,
!                                               tdata->tg_newtuple,
!                                               rel_descr);
                  PyDict_SetItemString(pltdata, "new", pytnew);
                  Py_DECREF(pytnew);
!                 pytold = PLy_input_from_tuple(&proc->resultin,
!                                               tdata->tg_trigtuple,
!                                               rel_descr);
                  PyDict_SetItemString(pltdata, "old", pytold);
                  Py_DECREF(pytold);
                  *rv = tdata->tg_newtuple;
*************** PLy_trigger_build_args(FunctionCallInfo
*** 897,902 ****
--- 854,862 ----
      return pltdata;
  }

+ /*
+  * Apply changes requested by a MODIFY return from a trigger function.
+  */
  static HeapTuple
  PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
                   HeapTuple otup)
*************** PLy_modify_tuple(PLyProcedure *proc, PyO
*** 938,944 ****
          plkeys = PyDict_Keys(plntup);
          nkeys = PyList_Size(plkeys);

!         tupdesc = tdata->tg_relation->rd_att;

          modvalues = (Datum *) palloc0(tupdesc->natts * sizeof(Datum));
          modnulls = (bool *) palloc0(tupdesc->natts * sizeof(bool));
--- 898,904 ----
          plkeys = PyDict_Keys(plntup);
          nkeys = PyList_Size(plkeys);

!         tupdesc = RelationGetDescr(tdata->tg_relation);

          modvalues = (Datum *) palloc0(tupdesc->natts * sizeof(Datum));
          modnulls = (bool *) palloc0(tupdesc->natts * sizeof(bool));
*************** PLy_modify_tuple(PLyProcedure *proc, PyO
*** 950,956 ****
              char       *plattstr;
              int            attn;
              PLyObToDatum *att;
-             Form_pg_attribute attr;

              platt = PyList_GetItem(plkeys, i);
              if (PyString_Check(platt))
--- 910,915 ----
*************** PLy_modify_tuple(PLyProcedure *proc, PyO
*** 975,981 ****
                          (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                           errmsg("cannot set system attribute \"%s\"",
                                  plattstr)));
-             att = &proc->result.out.r.atts[attn - 1];

              plval = PyDict_GetItem(plntup, platt);
              if (plval == NULL)
--- 934,939 ----
*************** PLy_modify_tuple(PLyProcedure *proc, PyO
*** 983,1007 ****

              Py_INCREF(plval);

!             attr = TupleDescAttr(tupdesc, attn - 1);
!             if (plval != Py_None)
!             {
!                 modvalues[attn - 1] =
!                     (att->func) (att,
!                                  attr->atttypmod,
!                                  plval,
!                                  false);
!                 modnulls[attn - 1] = false;
!             }
!             else
!             {
!                 modvalues[attn - 1] =
!                     InputFunctionCall(&att->typfunc,
!                                       NULL,
!                                       att->typioparam,
!                                       attr->atttypmod);
!                 modnulls[attn - 1] = true;
!             }
              modrepls[attn - 1] = true;

              Py_DECREF(plval);
--- 941,952 ----

              Py_INCREF(plval);

!             /* We assume proc->result is set up to convert tuples properly */
!             att = &proc->result.u.tuple.atts[attn - 1];
!
!             modvalues[attn - 1] = PLy_output_convert(att,
!                                                      plval,
!                                                      &modnulls[attn - 1]);
              modrepls[attn - 1] = true;

              Py_DECREF(plval);
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
index 7df50c0..29db90e 100644
*** a/src/pl/plpython/plpy_main.c
--- b/src/pl/plpython/plpy_main.c
*************** plpython_inline_handler(PG_FUNCTION_ARGS
*** 318,324 ****
                                        ALLOCSET_DEFAULT_SIZES);
      proc.pyname = MemoryContextStrdup(proc.mcxt, "__plpython_inline_block");
      proc.langid = codeblock->langOid;
!     proc.result.out.d.typoid = VOIDOID;

      /*
       * Push execution context onto stack.  It is important that this get
--- 318,329 ----
                                        ALLOCSET_DEFAULT_SIZES);
      proc.pyname = MemoryContextStrdup(proc.mcxt, "__plpython_inline_block");
      proc.langid = codeblock->langOid;
!
!     /*
!      * This is currently sufficient to get PLy_exec_function to work, but
!      * someday we might need to be honest and use PLy_output_setup_func.
!      */
!     proc.result.typoid = VOIDOID;

      /*
       * Push execution context onto stack.  It is important that this get
diff --git a/src/pl/plpython/plpy_planobject.h b/src/pl/plpython/plpy_planobject.h
index 5adc957..729effb 100644
*** a/src/pl/plpython/plpy_planobject.h
--- b/src/pl/plpython/plpy_planobject.h
*************** typedef struct PLyPlanObject
*** 16,22 ****
      int            nargs;
      Oid           *types;
      Datum       *values;
!     PLyTypeInfo *args;
      MemoryContext mcxt;
  } PLyPlanObject;

--- 16,22 ----
      int            nargs;
      Oid           *types;
      Datum       *values;
!     PLyObToDatum *args;
      MemoryContext mcxt;
  } PLyPlanObject;

diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c
index 26acc88..0c0d6ce 100644
*** a/src/pl/plpython/plpy_procedure.c
--- b/src/pl/plpython/plpy_procedure.c
***************
*** 15,20 ****
--- 15,21 ----
  #include "utils/builtins.h"
  #include "utils/hsearch.h"
  #include "utils/inval.h"
+ #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/syscache.h"

***************
*** 29,35 ****
  static HTAB *PLy_procedure_cache = NULL;

  static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger);
- static bool PLy_procedure_argument_valid(PLyTypeInfo *arg);
  static bool PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup);
  static char *PLy_procedure_munge_source(const char *name, const char *src);

--- 30,35 ----
*************** PLy_procedure_create(HeapTuple procTup,
*** 165,170 ****
--- 165,171 ----
              *ptr = '_';
      }

+     /* Create long-lived context that all procedure info will live in */
      cxt = AllocSetContextCreate(TopMemoryContext,
                                  procName,
                                  ALLOCSET_DEFAULT_SIZES);
*************** PLy_procedure_create(HeapTuple procTup,
*** 188,198 ****
          proc->fn_tid = procTup->t_self;
          proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
          proc->is_setof = procStruct->proretset;
-         PLy_typeinfo_init(&proc->result, proc->mcxt);
          proc->src = NULL;
          proc->argnames = NULL;
!         for (i = 0; i < FUNC_MAX_ARGS; i++)
!             PLy_typeinfo_init(&proc->args[i], proc->mcxt);
          proc->nargs = 0;
          proc->langid = procStruct->prolang;
          protrftypes_datum = SysCacheGetAttr(PROCOID, procTup,
--- 189,197 ----
          proc->fn_tid = procTup->t_self;
          proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
          proc->is_setof = procStruct->proretset;
          proc->src = NULL;
          proc->argnames = NULL;
!         proc->args = NULL;
          proc->nargs = 0;
          proc->langid = procStruct->prolang;
          protrftypes_datum = SysCacheGetAttr(PROCOID, procTup,
*************** PLy_procedure_create(HeapTuple procTup,
*** 211,260 ****
           */
          if (!is_trigger)
          {
              HeapTuple    rvTypeTup;
              Form_pg_type rvTypeStruct;

!             rvTypeTup = SearchSysCache1(TYPEOID,
!                                         ObjectIdGetDatum(procStruct->prorettype));
              if (!HeapTupleIsValid(rvTypeTup))
!                 elog(ERROR, "cache lookup failed for type %u",
!                      procStruct->prorettype);
              rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup);

              /* Disallow pseudotype result, except for void or record */
              if (rvTypeStruct->typtype == TYPTYPE_PSEUDO)
              {
!                 if (procStruct->prorettype == TRIGGEROID)
                      ereport(ERROR,
                              (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                               errmsg("trigger functions can only be called as triggers")));
!                 else if (procStruct->prorettype != VOIDOID &&
!                          procStruct->prorettype != RECORDOID)
                      ereport(ERROR,
                              (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                               errmsg("PL/Python functions cannot return type %s",
!                                     format_type_be(procStruct->prorettype))));
              }

!             if (rvTypeStruct->typtype == TYPTYPE_COMPOSITE ||
!                 procStruct->prorettype == RECORDOID)
!             {
!                 /*
!                  * Tuple: set up later, during first call to
!                  * PLy_function_handler
!                  */
!                 proc->result.out.d.typoid = procStruct->prorettype;
!                 proc->result.out.d.typmod = -1;
!                 proc->result.is_rowtype = 2;
!             }
!             else
!             {
!                 /* do the real work */
!                 PLy_output_datum_func(&proc->result, rvTypeTup, proc->langid, proc->trftypes);
!             }

              ReleaseSysCache(rvTypeTup);
          }

          /*
           * Now get information required for input conversion of the
--- 210,257 ----
           */
          if (!is_trigger)
          {
+             Oid            rettype = procStruct->prorettype;
              HeapTuple    rvTypeTup;
              Form_pg_type rvTypeStruct;

!             rvTypeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettype));
              if (!HeapTupleIsValid(rvTypeTup))
!                 elog(ERROR, "cache lookup failed for type %u", rettype);
              rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup);

              /* Disallow pseudotype result, except for void or record */
              if (rvTypeStruct->typtype == TYPTYPE_PSEUDO)
              {
!                 if (rettype == VOIDOID ||
!                     rettype == RECORDOID)
!                      /* okay */ ;
!                 else if (rettype == TRIGGEROID || rettype == EVTTRIGGEROID)
                      ereport(ERROR,
                              (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                               errmsg("trigger functions can only be called as triggers")));
!                 else
                      ereport(ERROR,
                              (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                               errmsg("PL/Python functions cannot return type %s",
!                                     format_type_be(rettype))));
              }

!             /* set up output function for procedure result */
!             PLy_output_setup_func(&proc->result, proc->mcxt,
!                                   rettype, -1, proc);

              ReleaseSysCache(rvTypeTup);
          }
+         else
+         {
+             /*
+              * In a trigger function, we use proc->result and proc->resultin
+              * for converting tuples, but we don't yet have enough info to set
+              * them up.  PLy_exec_trigger will deal with it.
+              */
+             proc->result.typoid = InvalidOid;
+             proc->resultin.typoid = InvalidOid;
+         }

          /*
           * Now get information required for input conversion of the
*************** PLy_procedure_create(HeapTuple procTup,
*** 287,293 ****
--- 284,293 ----
                  }
              }

+             /* Allocate arrays for per-input-argument data */
              proc->argnames = (char **) palloc0(sizeof(char *) * proc->nargs);
+             proc->args = (PLyDatumToOb *) palloc0(sizeof(PLyDatumToOb) * proc->nargs);
+
              for (i = pos = 0; i < total; i++)
              {
                  HeapTuple    argTypeTup;
*************** PLy_procedure_create(HeapTuple procTup,
*** 306,333 ****
                      elog(ERROR, "cache lookup failed for type %u", types[i]);
                  argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup);

!                 /* check argument type is OK, set up I/O function info */
!                 switch (argTypeStruct->typtype)
!                 {
!                     case TYPTYPE_PSEUDO:
!                         /* Disallow pseudotype argument */
!                         ereport(ERROR,
!                                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
!                                  errmsg("PL/Python functions cannot accept type %s",
!                                         format_type_be(types[i]))));
!                         break;
!                     case TYPTYPE_COMPOSITE:
!                         /* we'll set IO funcs at first call */
!                         proc->args[pos].is_rowtype = 2;
!                         break;
!                     default:
!                         PLy_input_datum_func(&(proc->args[pos]),
!                                              types[i],
!                                              argTypeTup,
!                                              proc->langid,
!                                              proc->trftypes);
!                         break;
!                 }

                  /* get argument name */
                  proc->argnames[pos] = names ? pstrdup(names[i]) : NULL;
--- 306,322 ----
                      elog(ERROR, "cache lookup failed for type %u", types[i]);
                  argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup);

!                 /* disallow pseudotype arguments */
!                 if (argTypeStruct->typtype == TYPTYPE_PSEUDO)
!                     ereport(ERROR,
!                             (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
!                              errmsg("PL/Python functions cannot accept type %s",
!                                     format_type_be(types[i]))));
!
!                 /* set up I/O function info */
!                 PLy_input_setup_func(&proc->args[pos], proc->mcxt,
!                                      types[i], -1,    /* typmod not known */
!                                      proc);

                  /* get argument name */
                  proc->argnames[pos] = names ? pstrdup(names[i]) : NULL;
*************** PLy_procedure_delete(PLyProcedure *proc)
*** 425,477 ****
  }

  /*
-  * Check if our cached information about a datatype is still valid
-  */
- static bool
- PLy_procedure_argument_valid(PLyTypeInfo *arg)
- {
-     HeapTuple    relTup;
-     bool        valid;
-
-     /* Nothing to cache unless type is composite */
-     if (arg->is_rowtype != 1)
-         return true;
-
-     /*
-      * Zero typ_relid means that we got called on an output argument of a
-      * function returning an unnamed record type; the info for it can't
-      * change.
-      */
-     if (!OidIsValid(arg->typ_relid))
-         return true;
-
-     /* Else we should have some cached data */
-     Assert(TransactionIdIsValid(arg->typrel_xmin));
-     Assert(ItemPointerIsValid(&arg->typrel_tid));
-
-     /* Get the pg_class tuple for the data type */
-     relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
-     if (!HeapTupleIsValid(relTup))
-         elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
-
-     /* If it has changed, the cached data is not valid */
-     valid = (arg->typrel_xmin == HeapTupleHeaderGetRawXmin(relTup->t_data) &&
-              ItemPointerEquals(&arg->typrel_tid, &relTup->t_self));
-
-     ReleaseSysCache(relTup);
-
-     return valid;
- }
-
- /*
   * Decide whether a cached PLyProcedure struct is still valid
   */
  static bool
  PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
  {
-     int            i;
-     bool        valid;
-
      if (proc == NULL)
          return false;

--- 414,424 ----
*************** PLy_procedure_valid(PLyProcedure *proc,
*** 480,501 ****
            ItemPointerEquals(&proc->fn_tid, &procTup->t_self)))
          return false;

!     /* Else check the input argument datatypes */
!     valid = true;
!     for (i = 0; i < proc->nargs; i++)
!     {
!         valid = PLy_procedure_argument_valid(&proc->args[i]);
!
!         /* Short-circuit on first changed argument */
!         if (!valid)
!             break;
!     }
!
!     /* if the output type is composite, it might have changed */
!     if (valid)
!         valid = PLy_procedure_argument_valid(&proc->result);
!
!     return valid;
  }

  static char *
--- 427,433 ----
            ItemPointerEquals(&proc->fn_tid, &procTup->t_self)))
          return false;

!     return true;
  }

  static char *
diff --git a/src/pl/plpython/plpy_procedure.h b/src/pl/plpython/plpy_procedure.h
index d05944f..eddd6fe 100644
*** a/src/pl/plpython/plpy_procedure.h
--- b/src/pl/plpython/plpy_procedure.h
*************** typedef struct PLyProcedure
*** 31,42 ****
      ItemPointerData fn_tid;
      bool        fn_readonly;
      bool        is_setof;        /* true, if procedure returns result set */
!     PLyTypeInfo result;            /* also used to store info for trigger tuple
!                                  * type */
      char       *src;            /* textual procedure code, after mangling */
      char      **argnames;        /* Argument names */
!     PLyTypeInfo args[FUNC_MAX_ARGS];
!     int            nargs;
      Oid            langid;            /* OID of plpython pg_language entry */
      List       *trftypes;        /* OID list of transform types */
      PyObject   *code;            /* compiled procedure code */
--- 31,42 ----
      ItemPointerData fn_tid;
      bool        fn_readonly;
      bool        is_setof;        /* true, if procedure returns result set */
!     PLyObToDatum result;        /* Function result output conversion info */
!     PLyDatumToOb resultin;        /* For converting input tuples in a trigger */
      char       *src;            /* textual procedure code, after mangling */
      char      **argnames;        /* Argument names */
!     PLyDatumToOb *args;            /* Argument input conversion info */
!     int            nargs;            /* Number of elements in above arrays */
      Oid            langid;            /* OID of plpython pg_language entry */
      List       *trftypes;        /* OID list of transform types */
      PyObject   *code;            /* compiled procedure code */
diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c
index 955769c..69eb6b3 100644
*** a/src/pl/plpython/plpy_spi.c
--- b/src/pl/plpython/plpy_spi.c
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 46,51 ****
--- 46,52 ----
      PyObject   *list = NULL;
      PyObject   *volatile optr = NULL;
      char       *query;
+     PLyExecutionContext *exec_ctx = PLy_current_execution_context();
      volatile MemoryContext oldcontext;
      volatile ResourceOwner oldowner;
      volatile int nargs;
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 71,79 ****
      nargs = list ? PySequence_Length(list) : 0;

      plan->nargs = nargs;
!     plan->types = nargs ? palloc(sizeof(Oid) * nargs) : NULL;
!     plan->values = nargs ? palloc(sizeof(Datum) * nargs) : NULL;
!     plan->args = nargs ? palloc(sizeof(PLyTypeInfo) * nargs) : NULL;

      MemoryContextSwitchTo(oldcontext);

--- 72,80 ----
      nargs = list ? PySequence_Length(list) : 0;

      plan->nargs = nargs;
!     plan->types = nargs ? palloc0(sizeof(Oid) * nargs) : NULL;
!     plan->values = nargs ? palloc0(sizeof(Datum) * nargs) : NULL;
!     plan->args = nargs ? palloc0(sizeof(PLyObToDatum) * nargs) : NULL;

      MemoryContextSwitchTo(oldcontext);

*************** PLy_spi_prepare(PyObject *self, PyObject
*** 85,106 ****
      PG_TRY();
      {
          int            i;
-         PLyExecutionContext *exec_ctx = PLy_current_execution_context();
-
-         /*
-          * the other loop might throw an exception, if PLyTypeInfo member
-          * isn't properly initialized the Py_DECREF(plan) will go boom
-          */
-         for (i = 0; i < nargs; i++)
-         {
-             PLy_typeinfo_init(&plan->args[i], plan->mcxt);
-             plan->values[i] = PointerGetDatum(NULL);
-         }

          for (i = 0; i < nargs; i++)
          {
              char       *sptr;
-             HeapTuple    typeTup;
              Oid            typeId;
              int32        typmod;

--- 86,95 ----
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 124,134 ****

              parseTypeString(sptr, &typeId, &typmod, false);

-             typeTup = SearchSysCache1(TYPEOID,
-                                       ObjectIdGetDatum(typeId));
-             if (!HeapTupleIsValid(typeTup))
-                 elog(ERROR, "cache lookup failed for type %u", typeId);
-
              Py_DECREF(optr);

              /*
--- 113,118 ----
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 138,145 ****
              optr = NULL;

              plan->types[i] = typeId;
!             PLy_output_datum_func(&plan->args[i], typeTup, exec_ctx->curr_proc->langid,
exec_ctx->curr_proc->trftypes);
!             ReleaseSysCache(typeTup);
          }

          pg_verifymbstr(query, strlen(query), false);
--- 122,130 ----
              optr = NULL;

              plan->types[i] = typeId;
!             PLy_output_setup_func(&plan->args[i], plan->mcxt,
!                                   typeId, typmod,
!                                   exec_ctx->curr_proc);
          }

          pg_verifymbstr(query, strlen(query), false);
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 253,291 ****

          for (j = 0; j < nargs; j++)
          {
              PyObject   *elem;

              elem = PySequence_GetItem(list, j);
!             if (elem != Py_None)
              {
!                 PG_TRY();
!                 {
!                     plan->values[j] =
!                         plan->args[j].out.d.func(&(plan->args[j].out.d),
!                                                  -1,
!                                                  elem,
!                                                  false);
!                 }
!                 PG_CATCH();
!                 {
!                     Py_DECREF(elem);
!                     PG_RE_THROW();
!                 }
!                 PG_END_TRY();

!                 Py_DECREF(elem);
!                 nulls[j] = ' ';
              }
!             else
              {
                  Py_DECREF(elem);
!                 plan->values[j] =
!                     InputFunctionCall(&(plan->args[j].out.d.typfunc),
!                                       NULL,
!                                       plan->args[j].out.d.typioparam,
!                                       -1);
!                 nulls[j] = 'n';
              }
          }

          rv = SPI_execute_plan(plan->plan, plan->values, nulls,
--- 238,261 ----

          for (j = 0; j < nargs; j++)
          {
+             PLyObToDatum *arg = &plan->args[j];
              PyObject   *elem;

              elem = PySequence_GetItem(list, j);
!             PG_TRY();
              {
!                 bool        isnull;

!                 plan->values[j] = PLy_output_convert(arg, elem, &isnull);
!                 nulls[j] = isnull ? 'n' : ' ';
              }
!             PG_CATCH();
              {
                  Py_DECREF(elem);
!                 PG_RE_THROW();
              }
+             PG_END_TRY();
+             Py_DECREF(elem);
          }

          rv = SPI_execute_plan(plan->plan, plan->values, nulls,
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 306,312 ****
           */
          for (k = 0; k < nargs; k++)
          {
!             if (!plan->args[k].out.d.typbyval &&
                  (plan->values[k] != PointerGetDatum(NULL)))
              {
                  pfree(DatumGetPointer(plan->values[k]));
--- 276,282 ----
           */
          for (k = 0; k < nargs; k++)
          {
!             if (!plan->args[k].typbyval &&
                  (plan->values[k] != PointerGetDatum(NULL)))
              {
                  pfree(DatumGetPointer(plan->values[k]));
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 321,327 ****

      for (i = 0; i < nargs; i++)
      {
!         if (!plan->args[i].out.d.typbyval &&
              (plan->values[i] != PointerGetDatum(NULL)))
          {
              pfree(DatumGetPointer(plan->values[i]));
--- 291,297 ----

      for (i = 0; i < nargs; i++)
      {
!         if (!plan->args[i].typbyval &&
              (plan->values[i] != PointerGetDatum(NULL)))
          {
              pfree(DatumGetPointer(plan->values[i]));
*************** static PyObject *
*** 386,391 ****
--- 356,362 ----
  PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status)
  {
      PLyResultObject *result;
+     PLyExecutionContext *exec_ctx = PLy_current_execution_context();
      volatile MemoryContext oldcontext;

      result = (PLyResultObject *) PLy_result_new();
*************** PLy_spi_execute_fetch_result(SPITupleTab
*** 401,407 ****
      }
      else if (status > 0 && tuptable != NULL)
      {
!         PLyTypeInfo args;
          MemoryContext cxt;

          Py_DECREF(result->nrows);
--- 372,378 ----
      }
      else if (status > 0 && tuptable != NULL)
      {
!         PLyDatumToOb ininfo;
          MemoryContext cxt;

          Py_DECREF(result->nrows);
*************** PLy_spi_execute_fetch_result(SPITupleTab
*** 412,418 ****
          cxt = AllocSetContextCreate(CurrentMemoryContext,
                                      "PL/Python temp context",
                                      ALLOCSET_DEFAULT_SIZES);
!         PLy_typeinfo_init(&args, cxt);

          oldcontext = CurrentMemoryContext;
          PG_TRY();
--- 383,392 ----
          cxt = AllocSetContextCreate(CurrentMemoryContext,
                                      "PL/Python temp context",
                                      ALLOCSET_DEFAULT_SIZES);
!
!         /* Initialize for converting result tuples to Python */
!         PLy_input_setup_func(&ininfo, cxt, RECORDOID, -1,
!                              exec_ctx->curr_proc);

          oldcontext = CurrentMemoryContext;
          PG_TRY();
*************** PLy_spi_execute_fetch_result(SPITupleTab
*** 436,447 ****
                  Py_DECREF(result->rows);
                  result->rows = PyList_New(rows);

!                 PLy_input_tuple_funcs(&args, tuptable->tupdesc);
                  for (i = 0; i < rows; i++)
                  {
!                     PyObject   *row = PLyDict_FromTuple(&args,
!                                                         tuptable->vals[i],
!                                                         tuptable->tupdesc);

                      PyList_SetItem(result->rows, i, row);
                  }
--- 410,423 ----
                  Py_DECREF(result->rows);
                  result->rows = PyList_New(rows);

!                 PLy_input_setup_tuple(&ininfo, tuptable->tupdesc,
!                                       exec_ctx->curr_proc);
!
                  for (i = 0; i < rows; i++)
                  {
!                     PyObject   *row = PLy_input_from_tuple(&ininfo,
!                                                            tuptable->vals[i],
!                                                            tuptable->tupdesc);

                      PyList_SetItem(result->rows, i, row);
                  }
diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c
index e4af8cc..ce15270 100644
*** a/src/pl/plpython/plpy_typeio.c
--- b/src/pl/plpython/plpy_typeio.c
***************
*** 7,25 ****
  #include "postgres.h"

  #include "access/htup_details.h"
- #include "access/transam.h"
  #include "catalog/pg_type.h"
  #include "funcapi.h"
  #include "mb/pg_wchar.h"
! #include "parser/parse_type.h"
  #include "utils/array.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
- #include "utils/numeric.h"
- #include "utils/syscache.h"
- #include "utils/typcache.h"

  #include "plpython.h"

--- 7,21 ----
  #include "postgres.h"

  #include "access/htup_details.h"
  #include "catalog/pg_type.h"
  #include "funcapi.h"
  #include "mb/pg_wchar.h"
! #include "miscadmin.h"
  #include "utils/array.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"

  #include "plpython.h"

***************
*** 29,38 ****
  #include "plpy_main.h"


- /* I/O function caching */
- static void PLy_input_datum_func2(PLyDatumToOb *arg, MemoryContext arg_mcxt, Oid typeOid, HeapTuple typeTup, Oid
langid,List *trftypes); 
- static void PLy_output_datum_func2(PLyObToDatum *arg, MemoryContext arg_mcxt, HeapTuple typeTup, Oid langid, List
*trftypes);
-
  /* conversion from Datums to Python objects */
  static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d);
  static PyObject *PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d);
--- 25,30 ----
*************** static PyObject *PLyInt_FromInt32(PLyDat
*** 43,403 ****
  static PyObject *PLyLong_FromInt64(PLyDatumToOb *arg, Datum d);
  static PyObject *PLyLong_FromOid(PLyDatumToOb *arg, Datum d);
  static PyObject *PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d);
! static PyObject *PLyString_FromDatum(PLyDatumToOb *arg, Datum d);
  static PyObject *PLyObject_FromTransform(PLyDatumToOb *arg, Datum d);
  static PyObject *PLyList_FromArray(PLyDatumToOb *arg, Datum d);
  static PyObject *PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim,
                            char **dataptr_p, bits8 **bitmap_p, int *bitmask_p);

  /* conversion from Python objects to Datums */
! static Datum PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray);
! static Datum PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray);
! static Datum PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray);
! static Datum PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray);
! static Datum PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray);
! static Datum PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray);
  static void PLySequence_ToArray_recurse(PLyObToDatum *elm, PyObject *list,
                              int *dims, int ndim, int dim,
                              Datum *elems, bool *nulls, int *currelem);

! /* conversion from Python objects to composite Datums (used by triggers and SRFs) */
! static Datum PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string, bool inarray);
! static Datum PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping);
! static Datum PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence);
! static Datum PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object, bool inarray);

- void
- PLy_typeinfo_init(PLyTypeInfo *arg, MemoryContext mcxt)
- {
-     arg->is_rowtype = -1;
-     arg->in.r.natts = arg->out.r.natts = 0;
-     arg->in.r.atts = NULL;
-     arg->out.r.atts = NULL;
-     arg->typ_relid = InvalidOid;
-     arg->typrel_xmin = InvalidTransactionId;
-     ItemPointerSetInvalid(&arg->typrel_tid);
-     arg->mcxt = mcxt;
- }

  /*
   * Conversion functions.  Remember output from Python is input to
   * PostgreSQL, and vice versa.
   */
! void
! PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes)
  {
!     if (arg->is_rowtype > 0)
!         elog(ERROR, "PLyTypeInfo struct is initialized for Tuple");
!     arg->is_rowtype = 0;
!     PLy_input_datum_func2(&(arg->in.d), arg->mcxt, typeOid, typeTup, langid, trftypes);
  }

! void
! PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup, Oid langid, List *trftypes)
  {
!     if (arg->is_rowtype > 0)
!         elog(ERROR, "PLyTypeInfo struct is initialized for a Tuple");
!     arg->is_rowtype = 0;
!     PLy_output_datum_func2(&(arg->out.d), arg->mcxt, typeTup, langid, trftypes);
  }

! void
! PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
  {
!     int            i;
      PLyExecutionContext *exec_ctx = PLy_current_execution_context();
!     MemoryContext oldcxt;

!     oldcxt = MemoryContextSwitchTo(arg->mcxt);

!     if (arg->is_rowtype == 0)
!         elog(ERROR, "PLyTypeInfo struct is initialized for a Datum");
!     arg->is_rowtype = 1;

!     if (arg->in.r.natts != desc->natts)
!     {
!         if (arg->in.r.atts)
!             pfree(arg->in.r.atts);
!         arg->in.r.natts = desc->natts;
!         arg->in.r.atts = palloc0(desc->natts * sizeof(PLyDatumToOb));
!     }

!     /* Can this be an unnamed tuple? If not, then an Assert would be enough */
!     if (desc->tdtypmod != -1)
!         elog(ERROR, "received unnamed record type as input");

!     Assert(OidIsValid(desc->tdtypeid));

!     /*
!      * RECORDOID means we got called to create input functions for a tuple
!      * fetched by plpy.execute or for an anonymous record type
!      */
!     if (desc->tdtypeid != RECORDOID)
!     {
!         HeapTuple    relTup;

!         /* Get the pg_class tuple corresponding to the type of the input */
!         arg->typ_relid = typeidTypeRelid(desc->tdtypeid);
!         relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
!         if (!HeapTupleIsValid(relTup))
!             elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);

!         /* Remember XMIN and TID for later validation if cache is still OK */
!         arg->typrel_xmin = HeapTupleHeaderGetRawXmin(relTup->t_data);
!         arg->typrel_tid = relTup->t_self;

!         ReleaseSysCache(relTup);
      }

      for (i = 0; i < desc->natts; i++)
      {
-         HeapTuple    typeTup;
          Form_pg_attribute attr = TupleDescAttr(desc, i);

          if (attr->attisdropped)
              continue;

!         if (arg->in.r.atts[i].typoid == attr->atttypid)
              continue;            /* already set up this entry */

!         typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(attr->atttypid));
!         if (!HeapTupleIsValid(typeTup))
!             elog(ERROR, "cache lookup failed for type %u",
!                  attr->atttypid);
!
!         PLy_input_datum_func2(&(arg->in.r.atts[i]), arg->mcxt,
!                               attr->atttypid,
!                               typeTup,
!                               exec_ctx->curr_proc->langid,
!                               exec_ctx->curr_proc->trftypes);
!
!         ReleaseSysCache(typeTup);
      }
-
-     MemoryContextSwitchTo(oldcxt);
  }

  void
! PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
  {
      int            i;
-     PLyExecutionContext *exec_ctx = PLy_current_execution_context();
-     MemoryContext oldcxt;

!     oldcxt = MemoryContextSwitchTo(arg->mcxt);
!
!     if (arg->is_rowtype == 0)
!         elog(ERROR, "PLyTypeInfo struct is initialized for a Datum");
!     arg->is_rowtype = 1;
!
!     if (arg->out.r.natts != desc->natts)
!     {
!         if (arg->out.r.atts)
!             pfree(arg->out.r.atts);
!         arg->out.r.natts = desc->natts;
!         arg->out.r.atts = palloc0(desc->natts * sizeof(PLyObToDatum));
!     }

!     Assert(OidIsValid(desc->tdtypeid));

!     /*
!      * RECORDOID means we got called to create output functions for an
!      * anonymous record type
!      */
!     if (desc->tdtypeid != RECORDOID)
      {
!         HeapTuple    relTup;
!
!         /* Get the pg_class tuple corresponding to the type of the output */
!         arg->typ_relid = typeidTypeRelid(desc->tdtypeid);
!         relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
!         if (!HeapTupleIsValid(relTup))
!             elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
!
!         /* Remember XMIN and TID for later validation if cache is still OK */
!         arg->typrel_xmin = HeapTupleHeaderGetRawXmin(relTup->t_data);
!         arg->typrel_tid = relTup->t_self;
!
!         ReleaseSysCache(relTup);
      }

      for (i = 0; i < desc->natts; i++)
      {
-         HeapTuple    typeTup;
          Form_pg_attribute attr = TupleDescAttr(desc, i);

          if (attr->attisdropped)
              continue;

!         if (arg->out.r.atts[i].typoid == attr->atttypid)
              continue;            /* already set up this entry */

!         typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(attr->atttypid));
!         if (!HeapTupleIsValid(typeTup))
!             elog(ERROR, "cache lookup failed for type %u",
!                  attr->atttypid);
!
!         PLy_output_datum_func2(&(arg->out.r.atts[i]), arg->mcxt, typeTup,
!                                exec_ctx->curr_proc->langid,
!                                exec_ctx->curr_proc->trftypes);
!
!         ReleaseSysCache(typeTup);
      }
-
-     MemoryContextSwitchTo(oldcxt);
  }

  void
! PLy_output_record_funcs(PLyTypeInfo *arg, TupleDesc desc)
  {
      /*
!      * If the output record functions are already set, we just have to check
!      * if the record descriptor has not changed
       */
-     if ((arg->is_rowtype == 1) &&
-         (arg->out.d.typmod != -1) &&
-         (arg->out.d.typmod == desc->tdtypmod))
-         return;
-
-     /* bless the record to make it known to the typcache lookup code */
      BlessTupleDesc(desc);
-     /* save the freshly generated typmod */
-     arg->out.d.typmod = desc->tdtypmod;
-     /* proceed with normal I/O function caching */
-     PLy_output_tuple_funcs(arg, desc);

      /*
!      * it should change is_rowtype to 1, so we won't go through this again
!      * unless the output record description changes
       */
!     Assert(arg->is_rowtype == 1);
  }

  /*
!  * Transform a tuple into a Python dict object.
   */
! PyObject *
! PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc)
  {
!     PyObject   *volatile dict;
!     PLyExecutionContext *exec_ctx = PLy_current_execution_context();
!     MemoryContext scratch_context = PLy_get_scratch_context(exec_ctx);
!     MemoryContext oldcontext = CurrentMemoryContext;

!     if (info->is_rowtype != 1)
!         elog(ERROR, "PLyTypeInfo structure describes a datum");

!     dict = PyDict_New();
!     if (dict == NULL)
!         PLy_elog(ERROR, "could not create new dictionary");

!     PG_TRY();
      {
!         int            i;
!
!         /*
!          * Do the work in the scratch context to avoid leaking memory from the
!          * datatype output function calls.
!          */
!         MemoryContextSwitchTo(scratch_context);
!         for (i = 0; i < info->in.r.natts; i++)
!         {
!             char       *key;
!             Datum        vattr;
!             bool        is_null;
!             PyObject   *value;
!             Form_pg_attribute attr = TupleDescAttr(desc, i);
!
!             if (attr->attisdropped)
!                 continue;
!
!             key = NameStr(attr->attname);
!             vattr = heap_getattr(tuple, (i + 1), desc, &is_null);
!
!             if (is_null || info->in.r.atts[i].func == NULL)
!                 PyDict_SetItemString(dict, key, Py_None);
!             else
!             {
!                 value = (info->in.r.atts[i].func) (&info->in.r.atts[i], vattr);
!                 PyDict_SetItemString(dict, key, value);
!                 Py_DECREF(value);
!             }
!         }
!         MemoryContextSwitchTo(oldcontext);
!         MemoryContextReset(scratch_context);
      }
!     PG_CATCH();
      {
!         MemoryContextSwitchTo(oldcontext);
!         Py_DECREF(dict);
!         PG_RE_THROW();
      }
-     PG_END_TRY();
-
-     return dict;
- }
-
- /*
-  *    Convert a Python object to a composite Datum, using all supported
-  *    conversion methods: composite as a string, as a sequence, as a mapping or
-  *    as an object that has __getattr__ support.
-  */
- Datum
- PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv, bool inarray)
- {
-     Datum        datum;
-
-     if (PyString_Check(plrv) || PyUnicode_Check(plrv))
-         datum = PLyString_ToComposite(info, desc, plrv, inarray);
-     else if (PySequence_Check(plrv))
-         /* composite type as sequence (tuple, list etc) */
-         datum = PLySequence_ToComposite(info, desc, plrv);
-     else if (PyMapping_Check(plrv))
-         /* composite type as mapping (currently only dict) */
-         datum = PLyMapping_ToComposite(info, desc, plrv);
-     else
-         /* returned as smth, must provide method __getattr__(name) */
-         datum = PLyGenericObject_ToComposite(info, desc, plrv, inarray);
-
-     return datum;
- }
-
- static void
- PLy_output_datum_func2(PLyObToDatum *arg, MemoryContext arg_mcxt, HeapTuple typeTup, Oid langid, List *trftypes)
- {
-     Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
-     Oid            element_type;
-     Oid            base_type;
-     Oid            funcid;
-     MemoryContext oldcxt;
-
-     oldcxt = MemoryContextSwitchTo(arg_mcxt);
-
-     fmgr_info_cxt(typeStruct->typinput, &arg->typfunc, arg_mcxt);
-     arg->typoid = HeapTupleGetOid(typeTup);
-     arg->typmod = -1;
-     arg->typioparam = getTypeIOParam(typeTup);
-     arg->typbyval = typeStruct->typbyval;
-
-     element_type = get_base_element_type(arg->typoid);
-     base_type = getBaseType(element_type ? element_type : arg->typoid);

      /*
!      * Select a conversion function to convert Python objects to PostgreSQL
!      * datums.
       */
!
!     if ((funcid = get_transform_tosql(base_type, langid, trftypes)))
      {
          arg->func = PLyObject_ToTransform;
!         fmgr_info_cxt(funcid, &arg->typtransform, arg_mcxt);
      }
!     else if (typeStruct->typtype == TYPTYPE_COMPOSITE)
      {
          arg->func = PLyObject_ToComposite;
      }
      else
!         switch (base_type)
          {
              case BOOLOID:
                  arg->func = PLyObject_ToBool;
--- 35,399 ----
  static PyObject *PLyLong_FromInt64(PLyDatumToOb *arg, Datum d);
  static PyObject *PLyLong_FromOid(PLyDatumToOb *arg, Datum d);
  static PyObject *PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d);
! static PyObject *PLyString_FromScalar(PLyDatumToOb *arg, Datum d);
  static PyObject *PLyObject_FromTransform(PLyDatumToOb *arg, Datum d);
  static PyObject *PLyList_FromArray(PLyDatumToOb *arg, Datum d);
  static PyObject *PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim,
                            char **dataptr_p, bits8 **bitmap_p, int *bitmask_p);
+ static PyObject *PLyDict_FromComposite(PLyDatumToOb *arg, Datum d);
+ static PyObject *PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc);

  /* conversion from Python objects to Datums */
! static Datum PLyObject_ToBool(PLyObToDatum *arg, PyObject *plrv,
!                  bool *isnull, bool inarray);
! static Datum PLyObject_ToBytea(PLyObToDatum *arg, PyObject *plrv,
!                   bool *isnull, bool inarray);
! static Datum PLyObject_ToComposite(PLyObToDatum *arg, PyObject *plrv,
!                       bool *isnull, bool inarray);
! static Datum PLyObject_ToScalar(PLyObToDatum *arg, PyObject *plrv,
!                    bool *isnull, bool inarray);
! static Datum PLyObject_ToDomain(PLyObToDatum *arg, PyObject *plrv,
!                    bool *isnull, bool inarray);
! static Datum PLyObject_ToTransform(PLyObToDatum *arg, PyObject *plrv,
!                       bool *isnull, bool inarray);
! static Datum PLySequence_ToArray(PLyObToDatum *arg, PyObject *plrv,
!                     bool *isnull, bool inarray);
  static void PLySequence_ToArray_recurse(PLyObToDatum *elm, PyObject *list,
                              int *dims, int ndim, int dim,
                              Datum *elems, bool *nulls, int *currelem);

! /* conversion from Python objects to composite Datums */
! static Datum PLyString_ToComposite(PLyObToDatum *arg, PyObject *string, bool inarray);
! static Datum PLyMapping_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *mapping);
! static Datum PLySequence_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *sequence);
! static Datum PLyGenericObject_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *object, bool inarray);


  /*
   * Conversion functions.  Remember output from Python is input to
   * PostgreSQL, and vice versa.
   */
!
! /*
!  * Perform input conversion, given correctly-set-up state information.
!  *
!  * This is the outer-level entry point for any input conversion.  Internally,
!  * the conversion functions recurse directly to each other.
!  */
! PyObject *
! PLy_input_convert(PLyDatumToOb *arg, Datum val)
  {
!     PyObject   *result;
!     PLyExecutionContext *exec_ctx = PLy_current_execution_context();
!     MemoryContext scratch_context = PLy_get_scratch_context(exec_ctx);
!     MemoryContext oldcontext;
!
!     /*
!      * Do the work in the scratch context to avoid leaking memory from the
!      * datatype output function calls.  (The individual PLyDatumToObFunc
!      * functions can't reset the scratch context, because they recurse and an
!      * inner one might clobber data an outer one still needs.  So we do it
!      * once at the outermost recursion level.)
!      *
!      * We reset the scratch context before, not after, each conversion cycle.
!      * This way we aren't on the hook to release a Python refcount on the
!      * result object in case MemoryContextReset throws an error.
!      */
!     MemoryContextReset(scratch_context);
!
!     oldcontext = MemoryContextSwitchTo(scratch_context);
!
!     result = arg->func(arg, val);
!
!     MemoryContextSwitchTo(oldcontext);
!
!     return result;
  }

! /*
!  * Perform output conversion, given correctly-set-up state information.
!  *
!  * This is the outer-level entry point for any output conversion.  Internally,
!  * the conversion functions recurse directly to each other.
!  *
!  * The result, as well as any cruft generated along the way, are in the
!  * current memory context.  Caller is responsible for cleanup.
!  */
! Datum
! PLy_output_convert(PLyObToDatum *arg, PyObject *val, bool *isnull)
  {
!     /* at outer level, we are not considering an array element */
!     return arg->func(arg, val, isnull, false);
  }

! /*
!  * Transform a tuple into a Python dict object.
!  *
!  * Note: the tupdesc must match the one used to set up *arg.  We could
!  * insist that this function lookup the tupdesc from what is in *arg,
!  * but in practice all callers have the right tupdesc available.
!  */
! PyObject *
! PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc)
  {
!     PyObject   *dict;
      PLyExecutionContext *exec_ctx = PLy_current_execution_context();
!     MemoryContext scratch_context = PLy_get_scratch_context(exec_ctx);
!     MemoryContext oldcontext;

!     /*
!      * As in PLy_input_convert, do the work in the scratch context.
!      */
!     MemoryContextReset(scratch_context);

!     oldcontext = MemoryContextSwitchTo(scratch_context);

!     dict = PLyDict_FromTuple(arg, tuple, desc);

!     MemoryContextSwitchTo(oldcontext);

!     return dict;
! }

! /*
!  * Initialize, or re-initialize, per-column input info for a composite type.
!  *
!  * This is separate from PLy_input_setup_func() because in cases involving
!  * anonymous record types, we need to be passed the tupdesc explicitly.
!  * It's caller's responsibility that the tupdesc has adequate lifespan
!  * in such cases.  If the tupdesc is for a named composite or registered
!  * record type, it does not need to be long-lived.
!  */
! void
! PLy_input_setup_tuple(PLyDatumToOb *arg, TupleDesc desc, PLyProcedure *proc)
! {
!     int            i;

!     /* We should be working on a previously-set-up struct */
!     Assert(arg->func == PLyDict_FromComposite);

!     /* Save pointer to tupdesc, but only if this is an anonymous record type */
!     if (arg->typoid == RECORDOID && arg->typmod < 0)
!         arg->u.tuple.recdesc = desc;

!     /* (Re)allocate atts array as needed */
!     if (arg->u.tuple.natts != desc->natts)
!     {
!         if (arg->u.tuple.atts)
!             pfree(arg->u.tuple.atts);
!         arg->u.tuple.natts = desc->natts;
!         arg->u.tuple.atts = (PLyDatumToOb *)
!             MemoryContextAllocZero(arg->mcxt,
!                                    desc->natts * sizeof(PLyDatumToOb));
      }

+     /* Fill the atts entries, except for dropped columns */
      for (i = 0; i < desc->natts; i++)
      {
          Form_pg_attribute attr = TupleDescAttr(desc, i);
+         PLyDatumToOb *att = &arg->u.tuple.atts[i];

          if (attr->attisdropped)
              continue;

!         if (att->typoid == attr->atttypid && att->typmod == attr->atttypmod)
              continue;            /* already set up this entry */

!         PLy_input_setup_func(att, arg->mcxt,
!                              attr->atttypid, attr->atttypmod,
!                              proc);
      }
  }

+ /*
+  * Initialize, or re-initialize, per-column output info for a composite type.
+  *
+  * This is separate from PLy_output_setup_func() because in cases involving
+  * anonymous record types, we need to be passed the tupdesc explicitly.
+  * It's caller's responsibility that the tupdesc has adequate lifespan
+  * in such cases.  If the tupdesc is for a named composite or registered
+  * record type, it does not need to be long-lived.
+  */
  void
! PLy_output_setup_tuple(PLyObToDatum *arg, TupleDesc desc, PLyProcedure *proc)
  {
      int            i;

!     /* We should be working on a previously-set-up struct */
!     Assert(arg->func == PLyObject_ToComposite);

!     /* Save pointer to tupdesc, but only if this is an anonymous record type */
!     if (arg->typoid == RECORDOID && arg->typmod < 0)
!         arg->u.tuple.recdesc = desc;

!     /* (Re)allocate atts array as needed */
!     if (arg->u.tuple.natts != desc->natts)
      {
!         if (arg->u.tuple.atts)
!             pfree(arg->u.tuple.atts);
!         arg->u.tuple.natts = desc->natts;
!         arg->u.tuple.atts = (PLyObToDatum *)
!             MemoryContextAllocZero(arg->mcxt,
!                                    desc->natts * sizeof(PLyObToDatum));
      }

+     /* Fill the atts entries, except for dropped columns */
      for (i = 0; i < desc->natts; i++)
      {
          Form_pg_attribute attr = TupleDescAttr(desc, i);
+         PLyObToDatum *att = &arg->u.tuple.atts[i];

          if (attr->attisdropped)
              continue;

!         if (att->typoid == attr->atttypid && att->typmod == attr->atttypmod)
              continue;            /* already set up this entry */

!         PLy_output_setup_func(att, arg->mcxt,
!                               attr->atttypid, attr->atttypmod,
!                               proc);
      }
  }

+ /*
+  * Set up output info for a PL/Python function returning record.
+  *
+  * Note: the given tupdesc is not necessarily long-lived.
+  */
  void
! PLy_output_setup_record(PLyObToDatum *arg, TupleDesc desc, PLyProcedure *proc)
  {
+     /* Makes no sense unless RECORD */
+     Assert(arg->typoid == RECORDOID);
+     Assert(desc->tdtypeid == RECORDOID);
+
      /*
!      * Bless the record type if not already done.  We'd have to do this anyway
!      * to return a tuple, so we might as well force the issue so we can use
!      * the known-record-type code path.
       */
      BlessTupleDesc(desc);

      /*
!      * Update arg->typmod, and clear the recdesc link if it's changed. The
!      * next call of PLyObject_ToComposite will look up a long-lived tupdesc
!      * for the record type.
       */
!     arg->typmod = desc->tdtypmod;
!     if (arg->u.tuple.recdesc &&
!         arg->u.tuple.recdesc->tdtypmod != arg->typmod)
!         arg->u.tuple.recdesc = NULL;
!
!     /* Update derived data if necessary */
!     PLy_output_setup_tuple(arg, desc, proc);
  }

  /*
!  * Recursively initialize the PLyObToDatum structure(s) needed to construct
!  * a SQL value of the specified typeOid/typmod from a Python value.
!  * (But note that at this point we may have RECORDOID/-1, ie, an indeterminate
!  * record type.)
!  * proc is used to look up transform functions.
   */
! void
! PLy_output_setup_func(PLyObToDatum *arg, MemoryContext arg_mcxt,
!                       Oid typeOid, int32 typmod,
!                       PLyProcedure *proc)
  {
!     TypeCacheEntry *typentry;
!     char        typtype;
!     Oid            trfuncid;
!     Oid            typinput;

!     /* Since this is recursive, it could theoretically be driven to overflow */
!     check_stack_depth();

!     arg->typoid = typeOid;
!     arg->typmod = typmod;
!     arg->mcxt = arg_mcxt;

!     /*
!      * Fetch typcache entry for the target type, asking for whatever info
!      * we'll need later.  RECORD is a special case: just treat it as composite
!      * without bothering with the typcache entry.
!      */
!     if (typeOid != RECORDOID)
      {
!         typentry = lookup_type_cache(typeOid, TYPECACHE_DOMAIN_BASE_INFO);
!         typtype = typentry->typtype;
!         arg->typbyval = typentry->typbyval;
!         arg->typlen = typentry->typlen;
!         arg->typalign = typentry->typalign;
      }
!     else
      {
!         typentry = NULL;
!         typtype = TYPTYPE_COMPOSITE;
!         /* hard-wired knowledge about type RECORD: */
!         arg->typbyval = false;
!         arg->typlen = -1;
!         arg->typalign = 'd';
      }

      /*
!      * Choose conversion method.  Note that transform functions are checked
!      * for composite and scalar types, but not for arrays or domains.  This is
!      * somewhat historical, but we'd have a problem allowing them on domains,
!      * since we drill down through all levels of a domain nest without looking
!      * at the intermediate levels at all.
       */
!     if (typtype == TYPTYPE_DOMAIN)
!     {
!         /* Domain */
!         arg->func = PLyObject_ToDomain;
!         arg->u.domain.domain_info = NULL;
!         /* Recursively set up conversion info for the element type */
!         arg->u.domain.base = (PLyObToDatum *)
!             MemoryContextAllocZero(arg_mcxt, sizeof(PLyObToDatum));
!         PLy_output_setup_func(arg->u.domain.base, arg_mcxt,
!                               typentry->domainBaseType,
!                               typentry->domainBaseTypmod,
!                               proc);
!     }
!     else if (typentry &&
!              OidIsValid(typentry->typelem) && typentry->typlen == -1)
!     {
!         /* Standard varlena array (cf. get_element_type) */
!         arg->func = PLySequence_ToArray;
!         /* Get base type OID to insert into constructed array */
!         /* (note this might not be the same as the immediate child type) */
!         arg->u.array.elmbasetype = getBaseType(typentry->typelem);
!         /* Recursively set up conversion info for the element type */
!         arg->u.array.elm = (PLyObToDatum *)
!             MemoryContextAllocZero(arg_mcxt, sizeof(PLyObToDatum));
!         PLy_output_setup_func(arg->u.array.elm, arg_mcxt,
!                               typentry->typelem, typmod,
!                               proc);
!     }
!     else if ((trfuncid = get_transform_tosql(typeOid,
!                                              proc->langid,
!                                              proc->trftypes)))
      {
          arg->func = PLyObject_ToTransform;
!         fmgr_info_cxt(trfuncid, &arg->u.transform.typtransform, arg_mcxt);
      }
!     else if (typtype == TYPTYPE_COMPOSITE)
      {
+         /* Named composite type, or RECORD */
          arg->func = PLyObject_ToComposite;
+         /* We'll set up the per-field data later */
+         arg->u.tuple.recdesc = NULL;
+         arg->u.tuple.typentry = typentry;
+         arg->u.tuple.tupdescseq = typentry ? typentry->tupDescSeqNo - 1 : 0;
+         arg->u.tuple.atts = NULL;
+         arg->u.tuple.natts = 0;
+         /* Mark this invalid till needed, too */
+         arg->u.tuple.recinfunc.fn_oid = InvalidOid;
      }
      else
!     {
!         /* Scalar type, but we have a couple of special cases */
!         switch (typeOid)
          {
              case BOOLOID:
                  arg->func = PLyObject_ToBool;
*************** PLy_output_datum_func2(PLyObToDatum *arg
*** 406,471 ****
                  arg->func = PLyObject_ToBytea;
                  break;
              default:
!                 arg->func = PLyObject_ToDatum;
                  break;
          }
-
-     if (element_type)
-     {
-         char        dummy_delim;
-         Oid            funcid;
-
-         if (type_is_rowtype(element_type))
-             arg->func = PLyObject_ToComposite;
-
-         arg->elm = palloc0(sizeof(*arg->elm));
-         arg->elm->func = arg->func;
-         arg->elm->typtransform = arg->typtransform;
-         arg->func = PLySequence_ToArray;
-
-         arg->elm->typoid = element_type;
-         arg->elm->typmod = -1;
-         get_type_io_data(element_type, IOFunc_input,
-                          &arg->elm->typlen, &arg->elm->typbyval, &arg->elm->typalign, &dummy_delim,
-                          &arg->elm->typioparam, &funcid);
-         fmgr_info_cxt(funcid, &arg->elm->typfunc, arg_mcxt);
      }
-
-     MemoryContextSwitchTo(oldcxt);
  }

! static void
! PLy_input_datum_func2(PLyDatumToOb *arg, MemoryContext arg_mcxt, Oid typeOid, HeapTuple typeTup, Oid langid, List
*trftypes)
  {
!     Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
!     Oid            element_type;
!     Oid            base_type;
!     Oid            funcid;
!     MemoryContext oldcxt;
!
!     oldcxt = MemoryContextSwitchTo(arg_mcxt);

!     /* Get the type's conversion information */
!     fmgr_info_cxt(typeStruct->typoutput, &arg->typfunc, arg_mcxt);
!     arg->typoid = HeapTupleGetOid(typeTup);
!     arg->typmod = -1;
!     arg->typioparam = getTypeIOParam(typeTup);
!     arg->typbyval = typeStruct->typbyval;
!     arg->typlen = typeStruct->typlen;
!     arg->typalign = typeStruct->typalign;

!     /* Determine which kind of Python object we will convert to */

!     element_type = get_base_element_type(typeOid);
!     base_type = getBaseType(element_type ? element_type : typeOid);

!     if ((funcid = get_transform_fromsql(base_type, langid, trftypes)))
      {
          arg->func = PLyObject_FromTransform;
!         fmgr_info_cxt(funcid, &arg->typtransform, arg_mcxt);
      }
      else
!         switch (base_type)
          {
              case BOOLOID:
                  arg->func = PLyBool_FromBool;
--- 402,512 ----
                  arg->func = PLyObject_ToBytea;
                  break;
              default:
!                 arg->func = PLyObject_ToScalar;
!                 getTypeInputInfo(typeOid, &typinput, &arg->u.scalar.typioparam);
!                 fmgr_info_cxt(typinput, &arg->u.scalar.typfunc, arg_mcxt);
                  break;
          }
      }
  }

! /*
!  * Recursively initialize the PLyDatumToOb structure(s) needed to construct
!  * a Python value from a SQL value of the specified typeOid/typmod.
!  * (But note that at this point we may have RECORDOID/-1, ie, an indeterminate
!  * record type.)
!  * proc is used to look up transform functions.
!  */
! void
! PLy_input_setup_func(PLyDatumToOb *arg, MemoryContext arg_mcxt,
!                      Oid typeOid, int32 typmod,
!                      PLyProcedure *proc)
  {
!     TypeCacheEntry *typentry;
!     char        typtype;
!     Oid            trfuncid;
!     Oid            typoutput;
!     bool        typisvarlena;

!     /* Since this is recursive, it could theoretically be driven to overflow */
!     check_stack_depth();

!     arg->typoid = typeOid;
!     arg->typmod = typmod;
!     arg->mcxt = arg_mcxt;

!     /*
!      * Fetch typcache entry for the target type, asking for whatever info
!      * we'll need later.  RECORD is a special case: just treat it as composite
!      * without bothering with the typcache entry.
!      */
!     if (typeOid != RECORDOID)
!     {
!         typentry = lookup_type_cache(typeOid, TYPECACHE_DOMAIN_BASE_INFO);
!         typtype = typentry->typtype;
!         arg->typbyval = typentry->typbyval;
!         arg->typlen = typentry->typlen;
!         arg->typalign = typentry->typalign;
!     }
!     else
!     {
!         typentry = NULL;
!         typtype = TYPTYPE_COMPOSITE;
!         /* hard-wired knowledge about type RECORD: */
!         arg->typbyval = false;
!         arg->typlen = -1;
!         arg->typalign = 'd';
!     }

!     /*
!      * Choose conversion method.  Note that transform functions are checked
!      * for composite and scalar types, but not for arrays or domains.  This is
!      * somewhat historical, but we'd have a problem allowing them on domains,
!      * since we drill down through all levels of a domain nest without looking
!      * at the intermediate levels at all.
!      */
!     if (typtype == TYPTYPE_DOMAIN)
!     {
!         /* Domain --- we don't care, just recurse down to the base type */
!         PLy_input_setup_func(arg, arg_mcxt,
!                              typentry->domainBaseType,
!                              typentry->domainBaseTypmod,
!                              proc);
!     }
!     else if (typentry &&
!              OidIsValid(typentry->typelem) && typentry->typlen == -1)
!     {
!         /* Standard varlena array (cf. get_element_type) */
!         arg->func = PLyList_FromArray;
!         /* Recursively set up conversion info for the element type */
!         arg->u.array.elm = (PLyDatumToOb *)
!             MemoryContextAllocZero(arg_mcxt, sizeof(PLyDatumToOb));
!         PLy_input_setup_func(arg->u.array.elm, arg_mcxt,
!                              typentry->typelem, typmod,
!                              proc);
!     }
!     else if ((trfuncid = get_transform_fromsql(typeOid,
!                                                proc->langid,
!                                                proc->trftypes)))
      {
          arg->func = PLyObject_FromTransform;
!         fmgr_info_cxt(trfuncid, &arg->u.transform.typtransform, arg_mcxt);
!     }
!     else if (typtype == TYPTYPE_COMPOSITE)
!     {
!         /* Named composite type, or RECORD */
!         arg->func = PLyDict_FromComposite;
!         /* We'll set up the per-field data later */
!         arg->u.tuple.recdesc = NULL;
!         arg->u.tuple.typentry = typentry;
!         arg->u.tuple.tupdescseq = typentry ? typentry->tupDescSeqNo - 1 : 0;
!         arg->u.tuple.atts = NULL;
!         arg->u.tuple.natts = 0;
      }
      else
!     {
!         /* Scalar type, but we have a couple of special cases */
!         switch (typeOid)
          {
              case BOOLOID:
                  arg->func = PLyBool_FromBool;
*************** PLy_input_datum_func2(PLyDatumToOb *arg,
*** 495,524 ****
                  arg->func = PLyBytes_FromBytea;
                  break;
              default:
!                 arg->func = PLyString_FromDatum;
                  break;
          }
-
-     if (element_type)
-     {
-         char        dummy_delim;
-         Oid            funcid;
-
-         arg->elm = palloc0(sizeof(*arg->elm));
-         arg->elm->func = arg->func;
-         arg->elm->typtransform = arg->typtransform;
-         arg->func = PLyList_FromArray;
-         arg->elm->typoid = element_type;
-         arg->elm->typmod = -1;
-         get_type_io_data(element_type, IOFunc_output,
-                          &arg->elm->typlen, &arg->elm->typbyval, &arg->elm->typalign, &dummy_delim,
-                          &arg->elm->typioparam, &funcid);
-         fmgr_info_cxt(funcid, &arg->elm->typfunc, arg_mcxt);
      }
-
-     MemoryContextSwitchTo(oldcxt);
  }

  static PyObject *
  PLyBool_FromBool(PLyDatumToOb *arg, Datum d)
  {
--- 536,554 ----
                  arg->func = PLyBytes_FromBytea;
                  break;
              default:
!                 arg->func = PLyString_FromScalar;
!                 getTypeOutputInfo(typeOid, &typoutput, &typisvarlena);
!                 fmgr_info_cxt(typoutput, &arg->u.scalar.typfunc, arg_mcxt);
                  break;
          }
      }
  }

+
+ /*
+  * Special-purpose input converters.
+  */
+
  static PyObject *
  PLyBool_FromBool(PLyDatumToOb *arg, Datum d)
  {
*************** PLyBytes_FromBytea(PLyDatumToOb *arg, Da
*** 611,637 ****
      return PyBytes_FromStringAndSize(str, size);
  }

  static PyObject *
! PLyString_FromDatum(PLyDatumToOb *arg, Datum d)
  {
!     char       *x = OutputFunctionCall(&arg->typfunc, d);
      PyObject   *r = PyString_FromString(x);

      pfree(x);
      return r;
  }

  static PyObject *
  PLyObject_FromTransform(PLyDatumToOb *arg, Datum d)
  {
!     return (PyObject *) DatumGetPointer(FunctionCall1(&arg->typtransform, d));
  }

  static PyObject *
  PLyList_FromArray(PLyDatumToOb *arg, Datum d)
  {
      ArrayType  *array = DatumGetArrayTypeP(d);
!     PLyDatumToOb *elm = arg->elm;
      int            ndim;
      int           *dims;
      char       *dataptr;
--- 641,680 ----
      return PyBytes_FromStringAndSize(str, size);
  }

+
+ /*
+  * Generic input conversion using a SQL type's output function.
+  */
  static PyObject *
! PLyString_FromScalar(PLyDatumToOb *arg, Datum d)
  {
!     char       *x = OutputFunctionCall(&arg->u.scalar.typfunc, d);
      PyObject   *r = PyString_FromString(x);

      pfree(x);
      return r;
  }

+ /*
+  * Convert using a from-SQL transform function.
+  */
  static PyObject *
  PLyObject_FromTransform(PLyDatumToOb *arg, Datum d)
  {
!     Datum        t;
!
!     t = FunctionCall1(&arg->u.transform.typtransform, d);
!     return (PyObject *) DatumGetPointer(t);
  }

+ /*
+  * Convert a SQL array to a Python list.
+  */
  static PyObject *
  PLyList_FromArray(PLyDatumToOb *arg, Datum d)
  {
      ArrayType  *array = DatumGetArrayTypeP(d);
!     PLyDatumToOb *elm = arg->u.array.elm;
      int            ndim;
      int           *dims;
      char       *dataptr;
*************** PLyList_FromArray_recurse(PLyDatumToOb *
*** 737,759 ****
  }

  /*
   * Convert a Python object to a PostgreSQL bool datum.  This can't go
   * through the generic conversion function, because Python attaches a
   * Boolean value to everything, more things than the PostgreSQL bool
   * type can parse.
   */
  static Datum
! PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
  {
!     Datum        rv;
!
!     Assert(plrv != Py_None);
!     rv = BoolGetDatum(PyObject_IsTrue(plrv));
!
!     if (get_typtype(arg->typoid) == TYPTYPE_DOMAIN)
!         domain_check(rv, false, arg->typoid, &arg->typfunc.fn_extra, arg->typfunc.fn_mcxt);
!
!     return rv;
  }

  /*
--- 780,889 ----
  }

  /*
+  * Convert a composite SQL value to a Python dict.
+  */
+ static PyObject *
+ PLyDict_FromComposite(PLyDatumToOb *arg, Datum d)
+ {
+     PyObject   *dict;
+     HeapTupleHeader td;
+     Oid            tupType;
+     int32        tupTypmod;
+     TupleDesc    tupdesc;
+     HeapTupleData tmptup;
+
+     td = DatumGetHeapTupleHeader(d);
+     /* Extract rowtype info and find a tupdesc */
+     tupType = HeapTupleHeaderGetTypeId(td);
+     tupTypmod = HeapTupleHeaderGetTypMod(td);
+     tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+
+     /* Set up I/O funcs if not done yet */
+     PLy_input_setup_tuple(arg, tupdesc,
+                           PLy_current_execution_context()->curr_proc);
+
+     /* Build a temporary HeapTuple control structure */
+     tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
+     tmptup.t_data = td;
+
+     dict = PLyDict_FromTuple(arg, &tmptup, tupdesc);
+
+     ReleaseTupleDesc(tupdesc);
+
+     return dict;
+ }
+
+ /*
+  * Transform a tuple into a Python dict object.
+  */
+ static PyObject *
+ PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc)
+ {
+     PyObject   *volatile dict;
+
+     /* Simple sanity check that desc matches */
+     Assert(desc->natts == arg->u.tuple.natts);
+
+     dict = PyDict_New();
+     if (dict == NULL)
+         PLy_elog(ERROR, "could not create new dictionary");
+
+     PG_TRY();
+     {
+         int            i;
+
+         for (i = 0; i < arg->u.tuple.natts; i++)
+         {
+             PLyDatumToOb *att = &arg->u.tuple.atts[i];
+             Form_pg_attribute attr = TupleDescAttr(desc, i);
+             char       *key;
+             Datum        vattr;
+             bool        is_null;
+             PyObject   *value;
+
+             if (attr->attisdropped)
+                 continue;
+
+             key = NameStr(attr->attname);
+             vattr = heap_getattr(tuple, (i + 1), desc, &is_null);
+
+             if (is_null)
+                 PyDict_SetItemString(dict, key, Py_None);
+             else
+             {
+                 value = att->func(att, vattr);
+                 PyDict_SetItemString(dict, key, value);
+                 Py_DECREF(value);
+             }
+         }
+     }
+     PG_CATCH();
+     {
+         Py_DECREF(dict);
+         PG_RE_THROW();
+     }
+     PG_END_TRY();
+
+     return dict;
+ }
+
+ /*
   * Convert a Python object to a PostgreSQL bool datum.  This can't go
   * through the generic conversion function, because Python attaches a
   * Boolean value to everything, more things than the PostgreSQL bool
   * type can parse.
   */
  static Datum
! PLyObject_ToBool(PLyObToDatum *arg, PyObject *plrv,
!                  bool *isnull, bool inarray)
  {
!     if (plrv == Py_None)
!     {
!         *isnull = true;
!         return (Datum) 0;
!     }
!     *isnull = false;
!     return BoolGetDatum(PyObject_IsTrue(plrv));
  }

  /*
*************** PLyObject_ToBool(PLyObToDatum *arg, int3
*** 762,773 ****
   * with embedded nulls.  And it's faster this way.
   */
  static Datum
! PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
  {
      PyObject   *volatile plrv_so = NULL;
      Datum        rv;

!     Assert(plrv != Py_None);

      plrv_so = PyObject_Bytes(plrv);
      if (!plrv_so)
--- 892,909 ----
   * with embedded nulls.  And it's faster this way.
   */
  static Datum
! PLyObject_ToBytea(PLyObToDatum *arg, PyObject *plrv,
!                   bool *isnull, bool inarray)
  {
      PyObject   *volatile plrv_so = NULL;
      Datum        rv;

!     if (plrv == Py_None)
!     {
!         *isnull = true;
!         return (Datum) 0;
!     }
!     *isnull = false;

      plrv_so = PyObject_Bytes(plrv);
      if (!plrv_so)
*************** PLyObject_ToBytea(PLyObToDatum *arg, int
*** 793,801 ****

      Py_XDECREF(plrv_so);

-     if (get_typtype(arg->typoid) == TYPTYPE_DOMAIN)
-         domain_check(rv, false, arg->typoid, &arg->typfunc.fn_extra, arg->typfunc.fn_mcxt);
-
      return rv;
  }

--- 929,934 ----
*************** PLyObject_ToBytea(PLyObToDatum *arg, int
*** 806,850 ****
   * for obtaining PostgreSQL tuples.
   */
  static Datum
! PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
  {
      Datum        rv;
-     PLyTypeInfo info;
      TupleDesc    desc;
-     MemoryContext cxt;

!     if (typmod != -1)
!         elog(ERROR, "received unnamed record type as input");

!     /* Create a dummy PLyTypeInfo */
!     cxt = AllocSetContextCreate(CurrentMemoryContext,
!                                 "PL/Python temp context",
!                                 ALLOCSET_DEFAULT_SIZES);
!     MemSet(&info, 0, sizeof(PLyTypeInfo));
!     PLy_typeinfo_init(&info, cxt);
!     /* Mark it as needing output routines lookup */
!     info.is_rowtype = 2;

!     desc = lookup_rowtype_tupdesc(arg->typoid, arg->typmod);

      /*
!      * This will set up the dummy PLyTypeInfo's output conversion routines,
!      * since we left is_rowtype as 2. A future optimization could be caching
!      * that info instead of looking it up every time a tuple is returned from
!      * the function.
       */
!     rv = PLyObject_ToCompositeDatum(&info, desc, plrv, inarray);

      ReleaseTupleDesc(desc);

-     MemoryContextDelete(cxt);
-
      return rv;
  }


  /*
   * Convert Python object to C string in server encoding.
   */
  char *
  PLyObject_AsString(PyObject *plrv)
--- 939,1025 ----
   * for obtaining PostgreSQL tuples.
   */
  static Datum
! PLyObject_ToComposite(PLyObToDatum *arg, PyObject *plrv,
!                       bool *isnull, bool inarray)
  {
      Datum        rv;
      TupleDesc    desc;

!     if (plrv == Py_None)
!     {
!         *isnull = true;
!         return (Datum) 0;
!     }
!     *isnull = false;

!     /*
!      * The string conversion case doesn't require a tupdesc, nor per-field
!      * conversion data, so just go for it if that's the case to use.
!      */
!     if (PyString_Check(plrv) || PyUnicode_Check(plrv))
!         return PLyString_ToComposite(arg, plrv, inarray);

!     /*
!      * If we're dealing with a named composite type, we must look up the
!      * tupdesc every time, to protect against possible changes to the type.
!      * RECORD types can't change between calls; but we must still be willing
!      * to set up the info the first time, if nobody did yet.
!      */
!     if (arg->typoid != RECORDOID)
!     {
!         desc = lookup_rowtype_tupdesc(arg->typoid, arg->typmod);
!         /* We should have the descriptor of the type's typcache entry */
!         Assert(desc == arg->u.tuple.typentry->tupDesc);
!         /* Detect change of descriptor, update cache if needed */
!         if (arg->u.tuple.tupdescseq != arg->u.tuple.typentry->tupDescSeqNo)
!         {
!             PLy_output_setup_tuple(arg, desc,
!                                    PLy_current_execution_context()->curr_proc);
!             arg->u.tuple.tupdescseq = arg->u.tuple.typentry->tupDescSeqNo;
!         }
!     }
!     else
!     {
!         desc = arg->u.tuple.recdesc;
!         if (desc == NULL)
!         {
!             desc = lookup_rowtype_tupdesc(arg->typoid, arg->typmod);
!             arg->u.tuple.recdesc = desc;
!         }
!         else
!         {
!             /* Pin descriptor to match unpin below */
!             PinTupleDesc(desc);
!         }
!     }
!
!     /* Simple sanity check on our caching */
!     Assert(desc->natts == arg->u.tuple.natts);

      /*
!      * Convert, using the appropriate method depending on the type of the
!      * supplied Python object.
       */
!     if (PySequence_Check(plrv))
!         /* composite type as sequence (tuple, list etc) */
!         rv = PLySequence_ToComposite(arg, desc, plrv);
!     else if (PyMapping_Check(plrv))
!         /* composite type as mapping (currently only dict) */
!         rv = PLyMapping_ToComposite(arg, desc, plrv);
!     else
!         /* returned as smth, must provide method __getattr__(name) */
!         rv = PLyGenericObject_ToComposite(arg, desc, plrv, inarray);

      ReleaseTupleDesc(desc);

      return rv;
  }


  /*
   * Convert Python object to C string in server encoding.
+  *
+  * Note: this is exported for use by add-on transform modules.
   */
  char *
  PLyObject_AsString(PyObject *plrv)
*************** PLyObject_AsString(PyObject *plrv)
*** 901,974 ****


  /*
!  * Generic conversion function: Convert PyObject to cstring and
   * cstring into PostgreSQL type.
   */
  static Datum
! PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
  {
      char       *str;

!     Assert(plrv != Py_None);

      str = PLyObject_AsString(plrv);

!     /*
!      * If we are parsing a composite type within an array, and the string
!      * isn't a valid record literal, there's a high chance that the function
!      * did something like:
!      *
!      * CREATE FUNCTION .. RETURNS comptype[] AS $$ return [['foo', 'bar']] $$
!      * LANGUAGE plpython;
!      *
!      * Before PostgreSQL 10, that was interpreted as a single-dimensional
!      * array, containing record ('foo', 'bar'). PostgreSQL 10 added support
!      * for multi-dimensional arrays, and it is now interpreted as a
!      * two-dimensional array, containing two records, 'foo', and 'bar'.
!      * record_in() will throw an error, because "foo" is not a valid record
!      * literal.
!      *
!      * To make that less confusing to users who are upgrading from older
!      * versions, try to give a hint in the typical instances of that. If we
!      * are parsing an array of composite types, and we see a string literal
!      * that is not a valid record literal, give a hint. We only want to give
!      * the hint in the narrow case of a malformed string literal, not any
!      * error from record_in(), so check for that case here specifically.
!      *
!      * This check better match the one in record_in(), so that we don't forbid
!      * literals that are actually valid!
!      */
!     if (inarray && arg->typfunc.fn_oid == F_RECORD_IN)
!     {
!         char       *ptr = str;

-         /* Allow leading whitespace */
-         while (*ptr && isspace((unsigned char) *ptr))
-             ptr++;
-         if (*ptr++ != '(')
-             ereport(ERROR,
-                     (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-                      errmsg("malformed record literal: \"%s\"", str),
-                      errdetail("Missing left parenthesis."),
-                      errhint("To return a composite type in an array, return the composite type as a Python tuple,
e.g.,\"[('foo',)]\"."))); 
-     }

!     return InputFunctionCall(&arg->typfunc,
!                              str,
!                              arg->typioparam,
!                              typmod);
  }


  static Datum
! PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
  {
!     return FunctionCall1(&arg->typtransform, PointerGetDatum(plrv));
  }


  static Datum
! PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
  {
      ArrayType  *array;
      int            i;
--- 1076,1146 ----


  /*
!  * Generic output conversion function: convert PyObject to cstring and
   * cstring into PostgreSQL type.
   */
  static Datum
! PLyObject_ToScalar(PLyObToDatum *arg, PyObject *plrv,
!                    bool *isnull, bool inarray)
  {
      char       *str;

!     if (plrv == Py_None)
!     {
!         *isnull = true;
!         return (Datum) 0;
!     }
!     *isnull = false;

      str = PLyObject_AsString(plrv);

!     return InputFunctionCall(&arg->u.scalar.typfunc,
!                              str,
!                              arg->u.scalar.typioparam,
!                              arg->typmod);
! }


! /*
!  * Convert to a domain type.
!  */
! static Datum
! PLyObject_ToDomain(PLyObToDatum *arg, PyObject *plrv,
!                    bool *isnull, bool inarray)
! {
!     Datum        result;
!     PLyObToDatum *base = arg->u.domain.base;
!
!     result = base->func(base, plrv, isnull, inarray);
!     domain_check(result, *isnull, arg->typoid,
!                  &arg->u.domain.domain_info, arg->mcxt);
!     return result;
  }


+ /*
+  * Convert using a to-SQL transform function.
+  */
  static Datum
! PLyObject_ToTransform(PLyObToDatum *arg, PyObject *plrv,
!                       bool *isnull, bool inarray)
  {
!     if (plrv == Py_None)
!     {
!         *isnull = true;
!         return (Datum) 0;
!     }
!     *isnull = false;
!     return FunctionCall1(&arg->u.transform.typtransform, PointerGetDatum(plrv));
  }


+ /*
+  * Convert Python sequence to SQL array.
+  */
  static Datum
! PLySequence_ToArray(PLyObToDatum *arg, PyObject *plrv,
!                     bool *isnull, bool inarray)
  {
      ArrayType  *array;
      int            i;
*************** PLySequence_ToArray(PLyObToDatum *arg, i
*** 979,989 ****
      int            dims[MAXDIM];
      int            lbs[MAXDIM];
      int            currelem;
-     Datum        rv;
      PyObject   *pyptr = plrv;
      PyObject   *next;

!     Assert(plrv != Py_None);

      /*
       * Determine the number of dimensions, and their sizes.
--- 1151,1165 ----
      int            dims[MAXDIM];
      int            lbs[MAXDIM];
      int            currelem;
      PyObject   *pyptr = plrv;
      PyObject   *next;

!     if (plrv == Py_None)
!     {
!         *isnull = true;
!         return (Datum) 0;
!     }
!     *isnull = false;

      /*
       * Determine the number of dimensions, and their sizes.
*************** PLySequence_ToArray(PLyObToDatum *arg, i
*** 1049,1055 ****
      elems = palloc(sizeof(Datum) * len);
      nulls = palloc(sizeof(bool) * len);
      currelem = 0;
!     PLySequence_ToArray_recurse(arg->elm, plrv,
                                  dims, ndim, 0,
                                  elems, nulls, &currelem);

--- 1225,1231 ----
      elems = palloc(sizeof(Datum) * len);
      nulls = palloc(sizeof(bool) * len);
      currelem = 0;
!     PLySequence_ToArray_recurse(arg->u.array.elm, plrv,
                                  dims, ndim, 0,
                                  elems, nulls, &currelem);

*************** PLySequence_ToArray(PLyObToDatum *arg, i
*** 1061,1079 ****
                                 ndim,
                                 dims,
                                 lbs,
!                                get_base_element_type(arg->typoid),
!                                arg->elm->typlen,
!                                arg->elm->typbyval,
!                                arg->elm->typalign);

!     /*
!      * If the result type is a domain of array, the resulting array must be
!      * checked.
!      */
!     rv = PointerGetDatum(array);
!     if (get_typtype(arg->typoid) == TYPTYPE_DOMAIN)
!         domain_check(rv, false, arg->typoid, &arg->typfunc.fn_extra, arg->typfunc.fn_mcxt);
!     return rv;
  }

  /*
--- 1237,1248 ----
                                 ndim,
                                 dims,
                                 lbs,
!                                arg->u.array.elmbasetype,
!                                arg->u.array.elm->typlen,
!                                arg->u.array.elm->typbyval,
!                                arg->u.array.elm->typalign);

!     return PointerGetDatum(array);
  }

  /*
*************** PLySequence_ToArray_recurse(PLyObToDatum
*** 1110,1125 ****
          {
              PyObject   *obj = PySequence_GetItem(list, i);

!             if (obj == Py_None)
!             {
!                 nulls[*currelem] = true;
!                 elems[*currelem] = (Datum) 0;
!             }
!             else
!             {
!                 nulls[*currelem] = false;
!                 elems[*currelem] = elm->func(elm, -1, obj, true);
!             }
              Py_XDECREF(obj);
              (*currelem)++;
          }
--- 1279,1285 ----
          {
              PyObject   *obj = PySequence_GetItem(list, i);

!             elems[*currelem] = elm->func(elm, obj, &nulls[*currelem], true);
              Py_XDECREF(obj);
              (*currelem)++;
          }
*************** PLySequence_ToArray_recurse(PLyObToDatum
*** 1127,1168 ****
  }


  static Datum
! PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string, bool inarray)
  {
!     Datum        result;
!     HeapTuple    typeTup;
!     PLyTypeInfo locinfo;
!     PLyExecutionContext *exec_ctx = PLy_current_execution_context();
!     MemoryContext cxt;
!
!     /* Create a dummy PLyTypeInfo */
!     cxt = AllocSetContextCreate(CurrentMemoryContext,
!                                 "PL/Python temp context",
!                                 ALLOCSET_DEFAULT_SIZES);
!     MemSet(&locinfo, 0, sizeof(PLyTypeInfo));
!     PLy_typeinfo_init(&locinfo, cxt);
!
!     typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(desc->tdtypeid));
!     if (!HeapTupleIsValid(typeTup))
!         elog(ERROR, "cache lookup failed for type %u", desc->tdtypeid);

!     PLy_output_datum_func2(&locinfo.out.d, locinfo.mcxt, typeTup,
!                            exec_ctx->curr_proc->langid,
!                            exec_ctx->curr_proc->trftypes);

!     ReleaseSysCache(typeTup);

!     result = PLyObject_ToDatum(&locinfo.out.d, desc->tdtypmod, string, inarray);

!     MemoryContextDelete(cxt);

!     return result;
  }


  static Datum
! PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
  {
      Datum        result;
      HeapTuple    tuple;
--- 1287,1358 ----
  }


+ /*
+  * Convert a Python string to composite, using record_in.
+  */
  static Datum
! PLyString_ToComposite(PLyObToDatum *arg, PyObject *string, bool inarray)
  {
!     char       *str;

!     /*
!      * Set up call data for record_in, if we didn't already.  (We can't just
!      * use DirectFunctionCall, because record_in needs a fn_extra field.)
!      */
!     if (!OidIsValid(arg->u.tuple.recinfunc.fn_oid))
!         fmgr_info_cxt(F_RECORD_IN, &arg->u.tuple.recinfunc, arg->mcxt);

!     str = PLyObject_AsString(string);

!     /*
!      * If we are parsing a composite type within an array, and the string
!      * isn't a valid record literal, there's a high chance that the function
!      * did something like:
!      *
!      * CREATE FUNCTION .. RETURNS comptype[] AS $$ return [['foo', 'bar']] $$
!      * LANGUAGE plpython;
!      *
!      * Before PostgreSQL 10, that was interpreted as a single-dimensional
!      * array, containing record ('foo', 'bar'). PostgreSQL 10 added support
!      * for multi-dimensional arrays, and it is now interpreted as a
!      * two-dimensional array, containing two records, 'foo', and 'bar'.
!      * record_in() will throw an error, because "foo" is not a valid record
!      * literal.
!      *
!      * To make that less confusing to users who are upgrading from older
!      * versions, try to give a hint in the typical instances of that. If we
!      * are parsing an array of composite types, and we see a string literal
!      * that is not a valid record literal, give a hint. We only want to give
!      * the hint in the narrow case of a malformed string literal, not any
!      * error from record_in(), so check for that case here specifically.
!      *
!      * This check better match the one in record_in(), so that we don't forbid
!      * literals that are actually valid!
!      */
!     if (inarray)
!     {
!         char       *ptr = str;

!         /* Allow leading whitespace */
!         while (*ptr && isspace((unsigned char) *ptr))
!             ptr++;
!         if (*ptr++ != '(')
!             ereport(ERROR,
!                     (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
!                      errmsg("malformed record literal: \"%s\"", str),
!                      errdetail("Missing left parenthesis."),
!                      errhint("To return a composite type in an array, return the composite type as a Python tuple,
e.g.,\"[('foo',)]\"."))); 
!     }

!     return InputFunctionCall(&arg->u.tuple.recinfunc,
!                              str,
!                              arg->typoid,
!                              arg->typmod);
  }


  static Datum
! PLyMapping_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *mapping)
  {
      Datum        result;
      HeapTuple    tuple;
*************** PLyMapping_ToComposite(PLyTypeInfo *info
*** 1172,1181 ****

      Assert(PyMapping_Check(mapping));

-     if (info->is_rowtype == 2)
-         PLy_output_tuple_funcs(info, desc);
-     Assert(info->is_rowtype == 1);
-
      /* Build tuple */
      values = palloc(sizeof(Datum) * desc->natts);
      nulls = palloc(sizeof(bool) * desc->natts);
--- 1362,1367 ----
*************** PLyMapping_ToComposite(PLyTypeInfo *info
*** 1195,1221 ****

          key = NameStr(attr->attname);
          value = NULL;
!         att = &info->out.r.atts[i];
          PG_TRY();
          {
              value = PyMapping_GetItemString(mapping, key);
!             if (value == Py_None)
!             {
!                 values[i] = (Datum) NULL;
!                 nulls[i] = true;
!             }
!             else if (value)
!             {
!                 values[i] = (att->func) (att, -1, value, false);
!                 nulls[i] = false;
!             }
!             else
                  ereport(ERROR,
                          (errcode(ERRCODE_UNDEFINED_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;
          }
--- 1381,1399 ----

          key = NameStr(attr->attname);
          value = NULL;
!         att = &arg->u.tuple.atts[i];
          PG_TRY();
          {
              value = PyMapping_GetItemString(mapping, key);
!             if (!value)
                  ereport(ERROR,
                          (errcode(ERRCODE_UNDEFINED_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.")));

+             values[i] = att->func(att, value, &nulls[i], false);
+
              Py_XDECREF(value);
              value = NULL;
          }
*************** PLyMapping_ToComposite(PLyTypeInfo *info
*** 1239,1245 ****


  static Datum
! PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
  {
      Datum        result;
      HeapTuple    tuple;
--- 1417,1423 ----


  static Datum
! PLySequence_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *sequence)
  {
      Datum        result;
      HeapTuple    tuple;
*************** PLySequence_ToComposite(PLyTypeInfo *inf
*** 1266,1275 ****
                  (errcode(ERRCODE_DATATYPE_MISMATCH),
                   errmsg("length of returned sequence did not match number of columns in row")));

-     if (info->is_rowtype == 2)
-         PLy_output_tuple_funcs(info, desc);
-     Assert(info->is_rowtype == 1);
-
      /* Build tuple */
      values = palloc(sizeof(Datum) * desc->natts);
      nulls = palloc(sizeof(bool) * desc->natts);
--- 1444,1449 ----
*************** PLySequence_ToComposite(PLyTypeInfo *inf
*** 1287,1307 ****
          }

          value = NULL;
!         att = &info->out.r.atts[i];
          PG_TRY();
          {
              value = PySequence_GetItem(sequence, idx);
              Assert(value);
!             if (value == Py_None)
!             {
!                 values[i] = (Datum) NULL;
!                 nulls[i] = true;
!             }
!             else if (value)
!             {
!                 values[i] = (att->func) (att, -1, value, false);
!                 nulls[i] = false;
!             }

              Py_XDECREF(value);
              value = NULL;
--- 1461,1473 ----
          }

          value = NULL;
!         att = &arg->u.tuple.atts[i];
          PG_TRY();
          {
              value = PySequence_GetItem(sequence, idx);
              Assert(value);
!
!             values[i] = att->func(att, value, &nulls[i], false);

              Py_XDECREF(value);
              value = NULL;
*************** PLySequence_ToComposite(PLyTypeInfo *inf
*** 1328,1334 ****


  static Datum
! PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object, bool inarray)
  {
      Datum        result;
      HeapTuple    tuple;
--- 1494,1500 ----


  static Datum
! PLyGenericObject_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *object, bool inarray)
  {
      Datum        result;
      HeapTuple    tuple;
*************** PLyGenericObject_ToComposite(PLyTypeInfo
*** 1336,1345 ****
      bool       *nulls;
      volatile int i;

-     if (info->is_rowtype == 2)
-         PLy_output_tuple_funcs(info, desc);
-     Assert(info->is_rowtype == 1);
-
      /* Build tuple */
      values = palloc(sizeof(Datum) * desc->natts);
      nulls = palloc(sizeof(bool) * desc->natts);
--- 1502,1507 ----
*************** PLyGenericObject_ToComposite(PLyTypeInfo
*** 1359,1379 ****

          key = NameStr(attr->attname);
          value = NULL;
!         att = &info->out.r.atts[i];
          PG_TRY();
          {
              value = PyObject_GetAttrString(object, key);
!             if (value == Py_None)
!             {
!                 values[i] = (Datum) NULL;
!                 nulls[i] = true;
!             }
!             else if (value)
!             {
!                 values[i] = (att->func) (att, -1, value, false);
!                 nulls[i] = false;
!             }
!             else
              {
                  /*
                   * No attribute for this column in the object.
--- 1521,1531 ----

          key = NameStr(attr->attname);
          value = NULL;
!         att = &arg->u.tuple.atts[i];
          PG_TRY();
          {
              value = PyObject_GetAttrString(object, key);
!             if (!value)
              {
                  /*
                   * No attribute for this column in the object.
*************** PLyGenericObject_ToComposite(PLyTypeInfo
*** 1384,1390 ****
                   * array, with a composite type (123, 'foo') in it. But now
                   * it's interpreted as a two-dimensional array, and we try to
                   * interpret "123" as the composite type. See also similar
!                  * heuristic in PLyObject_ToDatum().
                   */
                  ereport(ERROR,
                          (errcode(ERRCODE_UNDEFINED_COLUMN),
--- 1536,1542 ----
                   * array, with a composite type (123, 'foo') in it. But now
                   * it's interpreted as a two-dimensional array, and we try to
                   * interpret "123" as the composite type. See also similar
!                  * heuristic in PLyObject_ToScalar().
                   */
                  ereport(ERROR,
                          (errcode(ERRCODE_UNDEFINED_COLUMN),
*************** PLyGenericObject_ToComposite(PLyTypeInfo
*** 1394,1399 ****
--- 1546,1553 ----
                           errhint("To return null in a column, let the returned object have an attribute named after
columnwith value None."))); 
              }

+             values[i] = att->func(att, value, &nulls[i], false);
+
              Py_XDECREF(value);
              value = NULL;
          }
diff --git a/src/pl/plpython/plpy_typeio.h b/src/pl/plpython/plpy_typeio.h
index 95f84d8..91870c9 100644
*** a/src/pl/plpython/plpy_typeio.h
--- b/src/pl/plpython/plpy_typeio.h
***************
*** 6,122 ****
  #define PLPY_TYPEIO_H

  #include "access/htup.h"
- #include "access/tupdesc.h"
  #include "fmgr.h"
! #include "storage/itemptr.h"

  /*
!  * Conversion from PostgreSQL Datum to a Python object.
   */
! struct PLyDatumToOb;
! typedef PyObject *(*PLyDatumToObFunc) (struct PLyDatumToOb *arg, Datum val);

! typedef struct PLyDatumToOb
  {
!     PLyDatumToObFunc func;
!     FmgrInfo    typfunc;        /* The type's output function */
!     FmgrInfo    typtransform;    /* from-SQL transform */
!     Oid            typoid;            /* The OID of the type */
!     int32        typmod;            /* The typmod of the type */
!     Oid            typioparam;
!     bool        typbyval;
!     int16        typlen;
!     char        typalign;
!     struct PLyDatumToOb *elm;
! } PLyDatumToOb;

  typedef struct PLyTupleToOb
  {
!     PLyDatumToOb *atts;
!     int            natts;
  } PLyTupleToOb;

! typedef union PLyTypeInput
  {
!     PLyDatumToOb d;
!     PLyTupleToOb r;
! } PLyTypeInput;

  /*
!  * Conversion from Python object to a PostgreSQL Datum.
   *
!  * The 'inarray' argument to the conversion function is true, if the
!  * converted value was in an array (Python list). It is used to give a
!  * better error message in some cases.
   */
! struct PLyObToDatum;
! typedef Datum (*PLyObToDatumFunc) (struct PLyObToDatum *arg, int32 typmod, PyObject *val, bool inarray);

! typedef struct PLyObToDatum
  {
!     PLyObToDatumFunc func;
!     FmgrInfo    typfunc;        /* The type's input function */
!     FmgrInfo    typtransform;    /* to-SQL transform */
!     Oid            typoid;            /* The OID of the type */
!     int32        typmod;            /* The typmod of the type */
!     Oid            typioparam;
!     bool        typbyval;
!     int16        typlen;
!     char        typalign;
!     struct PLyObToDatum *elm;
! } PLyObToDatum;

  typedef struct PLyObToTuple
  {
!     PLyObToDatum *atts;
!     int            natts;
  } PLyObToTuple;

! typedef union PLyTypeOutput
  {
!     PLyObToDatum d;
!     PLyObToTuple r;
! } PLyTypeOutput;

! /* all we need to move PostgreSQL data to Python objects,
!  * and vice versa
!  */
! typedef struct PLyTypeInfo
  {
!     PLyTypeInput in;
!     PLyTypeOutput out;
!
!     /*
!      * is_rowtype can be: -1 = not known yet (initial state); 0 = scalar
!      * datatype; 1 = rowtype; 2 = rowtype, but I/O functions not set up yet
!      */
!     int            is_rowtype;
!     /* used to check if the type has been modified */
!     Oid            typ_relid;
!     TransactionId typrel_xmin;
!     ItemPointerData typrel_tid;

!     /* context for subsidiary data (doesn't belong to this struct though) */
!     MemoryContext mcxt;
! } PLyTypeInfo;

- extern void PLy_typeinfo_init(PLyTypeInfo *arg, MemoryContext mcxt);

! extern void PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes);
! extern void PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup, Oid langid, List *trftypes);

! extern void PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc);
! extern void PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc);

! extern void PLy_output_record_funcs(PLyTypeInfo *arg, TupleDesc desc);

! /* conversion from Python objects to composite Datums */
! extern Datum PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv, bool isarray);

! /* conversion from heap tuples to Python dictionaries */
! extern PyObject *PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc);

! /* conversion from Python objects to C strings */
  extern char *PLyObject_AsString(PyObject *plrv);

  #endif                            /* PLPY_TYPEIO_H */
--- 6,174 ----
  #define PLPY_TYPEIO_H

  #include "access/htup.h"
  #include "fmgr.h"
! #include "utils/typcache.h"
!
! struct PLyProcedure;            /* avoid requiring plpy_procedure.h here */
!

  /*
!  * "Input" conversion from PostgreSQL Datum to a Python object.
!  *
!  * arg is the previously-set-up conversion data, val is the value to convert.
!  * val mustn't be NULL.
!  *
!  * Note: the conversion data structs should be regarded as private to
!  * plpy_typeio.c.  We declare them here only so that other modules can
!  * define structs containing them.
   */
! typedef struct PLyDatumToOb PLyDatumToOb;    /* forward reference */

! typedef PyObject *(*PLyDatumToObFunc) (PLyDatumToOb *arg, Datum val);
!
! typedef struct PLyScalarToOb
  {
!     FmgrInfo    typfunc;        /* lookup info for type's output function */
! } PLyScalarToOb;
!
! typedef struct PLyArrayToOb
! {
!     PLyDatumToOb *elm;            /* conversion info for array's element type */
! } PLyArrayToOb;

  typedef struct PLyTupleToOb
  {
!     /* If we're dealing with a RECORD type, actual descriptor is here: */
!     TupleDesc    recdesc;
!     /* If we're dealing with a named composite type, these fields are set: */
!     TypeCacheEntry *typentry;    /* typcache entry for type */
!     int64        tupdescseq;        /* last tupdesc seqno seen in typcache */
!     /* These fields are NULL/0 if not yet set: */
!     PLyDatumToOb *atts;            /* array of per-column conversion info */
!     int            natts;            /* length of array */
  } PLyTupleToOb;

! typedef struct PLyTransformToOb
  {
!     FmgrInfo    typtransform;    /* lookup info for from-SQL transform func */
! } PLyTransformToOb;
!
! struct PLyDatumToOb
! {
!     PLyDatumToObFunc func;        /* conversion control function */
!     Oid            typoid;            /* OID of the source type */
!     int32        typmod;            /* typmod of the source type */
!     bool        typbyval;        /* its physical representation details */
!     int16        typlen;
!     char        typalign;
!     MemoryContext mcxt;            /* context this info is stored in */
!     union                        /* conversion-type-specific data */
!     {
!         PLyScalarToOb scalar;
!         PLyArrayToOb array;
!         PLyTupleToOb tuple;
!         PLyTransformToOb transform;
!     }            u;
! };

  /*
!  * "Output" conversion from Python object to a PostgreSQL Datum.
   *
!  * arg is the previously-set-up conversion data, val is the value to convert.
!  *
!  * *isnull is set to true if val is Py_None, false otherwise.
!  * (The conversion function *must* be called even for Py_None,
!  * so that domain constraints can be checked.)
!  *
!  * inarray is true if the converted value was in an array (Python list).
!  * It is used to give a better error message in some cases.
   */
! typedef struct PLyObToDatum PLyObToDatum;    /* forward reference */

! typedef Datum (*PLyObToDatumFunc) (PLyObToDatum *arg, PyObject *val,
!                                    bool *isnull,
!                                    bool inarray);
!
! typedef struct PLyObToScalar
  {
!     FmgrInfo    typfunc;        /* lookup info for type's input function */
!     Oid            typioparam;        /* argument to pass to it */
! } PLyObToScalar;
!
! typedef struct PLyObToArray
! {
!     PLyObToDatum *elm;            /* conversion info for array's element type */
!     Oid            elmbasetype;    /* element base type */
! } PLyObToArray;

  typedef struct PLyObToTuple
  {
!     /* If we're dealing with a RECORD type, actual descriptor is here: */
!     TupleDesc    recdesc;
!     /* If we're dealing with a named composite type, these fields are set: */
!     TypeCacheEntry *typentry;    /* typcache entry for type */
!     int64        tupdescseq;        /* last tupdesc seqno seen in typcache */
!     /* These fields are NULL/0 if not yet set: */
!     PLyObToDatum *atts;            /* array of per-column conversion info */
!     int            natts;            /* length of array */
!     /* We might need to convert using record_in(); if so, cache info here */
!     FmgrInfo    recinfunc;        /* lookup info for record_in */
  } PLyObToTuple;

! typedef struct PLyObToDomain
  {
!     PLyObToDatum *base;            /* conversion info for domain's base type */
!     void       *domain_info;    /* cache space for domain_check() */
! } PLyObToDomain;

! typedef struct PLyObToTransform
  {
!     FmgrInfo    typtransform;    /* lookup info for to-SQL transform function */
! } PLyObToTransform;

! struct PLyObToDatum
! {
!     PLyObToDatumFunc func;        /* conversion control function */
!     Oid            typoid;            /* OID of the target type */
!     int32        typmod;            /* typmod of the target type */
!     bool        typbyval;        /* its physical representation details */
!     int16        typlen;
!     char        typalign;
!     MemoryContext mcxt;            /* context this info is stored in */
!     union                        /* conversion-type-specific data */
!     {
!         PLyObToScalar scalar;
!         PLyObToArray array;
!         PLyObToTuple tuple;
!         PLyObToDomain domain;
!         PLyObToTransform transform;
!     }            u;
! };


! extern PyObject *PLy_input_convert(PLyDatumToOb *arg, Datum val);
! extern Datum PLy_output_convert(PLyObToDatum *arg, PyObject *val,
!                    bool *isnull);

! extern PyObject *PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple,
!                      TupleDesc desc);

! extern void PLy_input_setup_func(PLyDatumToOb *arg, MemoryContext arg_mcxt,
!                      Oid typeOid, int32 typmod,
!                      struct PLyProcedure *proc);
! extern void PLy_output_setup_func(PLyObToDatum *arg, MemoryContext arg_mcxt,
!                       Oid typeOid, int32 typmod,
!                       struct PLyProcedure *proc);

! extern void PLy_input_setup_tuple(PLyDatumToOb *arg, TupleDesc desc,
!                       struct PLyProcedure *proc);
! extern void PLy_output_setup_tuple(PLyObToDatum *arg, TupleDesc desc,
!                        struct PLyProcedure *proc);

! extern void PLy_output_setup_record(PLyObToDatum *arg, TupleDesc desc,
!                         struct PLyProcedure *proc);

! /* conversion from Python objects to C strings --- exported for transforms */
  extern char *PLyObject_AsString(PyObject *plrv);

  #endif                            /* PLPY_TYPEIO_H */
diff --git a/src/pl/plpython/sql/plpython_types.sql b/src/pl/plpython/sql/plpython_types.sql
index 8c57297..cc0524e 100644
*** a/src/pl/plpython/sql/plpython_types.sql
--- b/src/pl/plpython/sql/plpython_types.sql
*************** $$ LANGUAGE plpythonu;
*** 387,392 ****
--- 387,441 ----
  SELECT * FROM test_type_conversion_array_domain_check_violation();


+ --
+ -- Arrays of domains
+ --
+
+ CREATE FUNCTION test_read_uint2_array(x uint2[]) RETURNS uint2 AS $$
+ plpy.info(x, type(x))
+ return x[0]
+ $$ LANGUAGE plpythonu;
+
+ select test_read_uint2_array(array[1::uint2]);
+
+ CREATE FUNCTION test_build_uint2_array(x int2) RETURNS uint2[] AS $$
+ return [x, x]
+ $$ LANGUAGE plpythonu;
+
+ select test_build_uint2_array(1::int2);
+ select test_build_uint2_array(-1::int2);  -- fail
+
+ --
+ -- ideally this would work, but for now it doesn't, because the return value
+ -- is [[2,4], [2,4]] which our conversion code thinks should become a 2-D
+ -- integer array, not an array of arrays.
+ --
+ CREATE FUNCTION test_type_conversion_domain_array(x integer[])
+   RETURNS ordered_pair_domain[] AS $$
+ return [x, x]
+ $$ LANGUAGE plpythonu;
+
+ select test_type_conversion_domain_array(array[2,4]);
+ select test_type_conversion_domain_array(array[4,2]);  -- fail
+
+ CREATE FUNCTION test_type_conversion_domain_array2(x ordered_pair_domain)
+   RETURNS integer AS $$
+ plpy.info(x, type(x))
+ return x[1]
+ $$ LANGUAGE plpythonu;
+
+ select test_type_conversion_domain_array2(array[2,4]);
+ select test_type_conversion_domain_array2(array[4,2]);  -- fail
+
+ CREATE FUNCTION test_type_conversion_array_domain_array(x ordered_pair_domain[])
+   RETURNS ordered_pair_domain AS $$
+ plpy.info(x, type(x))
+ return x[0]
+ $$ LANGUAGE plpythonu;
+
+ select test_type_conversion_array_domain_array(array[array[2,4]::ordered_pair_domain]);
+
+
  ---
  --- Composite types
  ---
*************** SELECT test_composite_type_input(row(1,
*** 431,436 ****
--- 480,527 ----


  --
+ -- Domains within composite
+ --
+
+ CREATE TYPE nnint_container AS (f1 int, f2 nnint);
+
+ CREATE FUNCTION nnint_test(x int, y int) RETURNS nnint_container AS $$
+ return {'f1': x, 'f2': y}
+ $$ LANGUAGE plpythonu;
+
+ SELECT nnint_test(null, 3);
+ SELECT nnint_test(3, null);  -- fail
+
+
+ --
+ -- Domains of composite
+ --
+
+ CREATE DOMAIN ordered_named_pair AS named_pair_2 CHECK((VALUE).i <= (VALUE).j);
+
+ CREATE FUNCTION read_ordered_named_pair(p ordered_named_pair) RETURNS integer AS $$
+ return p['i'] + p['j']
+ $$ LANGUAGE plpythonu;
+
+ SELECT read_ordered_named_pair(row(1, 2));
+ SELECT read_ordered_named_pair(row(2, 1));  -- fail
+
+ CREATE FUNCTION build_ordered_named_pair(i int, j int) RETURNS ordered_named_pair AS $$
+ return {'i': i, 'j': j}
+ $$ LANGUAGE plpythonu;
+
+ SELECT build_ordered_named_pair(1,2);
+ SELECT build_ordered_named_pair(2,1);  -- fail
+
+ CREATE FUNCTION build_ordered_named_pairs(i int, j int) RETURNS ordered_named_pair[] AS $$
+ return [{'i': i, 'j': j}, {'i': i, 'j': j+1}]
+ $$ LANGUAGE plpythonu;
+
+ SELECT build_ordered_named_pairs(1,2);
+ SELECT build_ordered_named_pairs(2,1);  -- fail
+
+
+ --
  -- Prepared statements
  --


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

В списке pgsql-hackers по дате отправления:

Предыдущее
От: Nikita Glukhov
Дата:
Сообщение: Re: [HACKERS] SQL/JSON in PostgreSQL
Следующее
От: Tom Lane
Дата:
Сообщение: Re: [HACKERS] Re: PANIC: invalid index offnum: 186 when processing BRIN indexes in VACUUM