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