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 column with 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

Reply via email to