diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c
index d828401..e999a8e 100644
*** a/contrib/hstore/hstore_io.c
--- b/contrib/hstore/hstore_io.c
*************** typedef struct RecordIOData
*** 752,757 ****
--- 752,759 ----
  {
  	Oid			record_type;
  	int32		record_typmod;
+ 	/* this field is used only if target type is domain over composite: */
+ 	void	   *domain_info;	/* opaque cache for domain checks */
  	int			ncolumns;
  	ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER];
  } RecordIOData;
*************** hstore_from_record(PG_FUNCTION_ARGS)
*** 780,788 ****
  		Oid			argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
  
  		/*
! 		 * have no tuple to look at, so the only source of type info is the
! 		 * argtype. The lookup_rowtype_tupdesc call below will error out if we
! 		 * don't have a known composite type oid here.
  		 */
  		tupType = argtype;
  		tupTypmod = -1;
--- 782,792 ----
  		Oid			argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
  
  		/*
! 		 * We have no tuple to look at, so the only source of type info is the
! 		 * argtype --- which might be domain over composite, but we don't care
! 		 * here, since we have no need to be concerned about domain
! 		 * constraints.  The lookup_rowtype_tupdesc_domain call below will
! 		 * error out if we don't have a known composite type oid here.
  		 */
  		tupType = argtype;
  		tupTypmod = -1;
*************** hstore_from_record(PG_FUNCTION_ARGS)
*** 793,804 ****
  	{
  		rec = PG_GETARG_HEAPTUPLEHEADER(0);
  
! 		/* Extract type info from the tuple itself */
  		tupType = HeapTupleHeaderGetTypeId(rec);
  		tupTypmod = HeapTupleHeaderGetTypMod(rec);
  	}
  
! 	tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
  	ncolumns = tupdesc->natts;
  
  	/*
--- 797,811 ----
  	{
  		rec = PG_GETARG_HEAPTUPLEHEADER(0);
  
! 		/*
! 		 * Extract type info from the tuple itself -- this will work even for
! 		 * anonymous record types.
! 		 */
  		tupType = HeapTupleHeaderGetTypeId(rec);
  		tupTypmod = HeapTupleHeaderGetTypMod(rec);
  	}
  
! 	tupdesc = lookup_rowtype_tupdesc_domain(tupType, tupTypmod, false);
  	ncolumns = tupdesc->natts;
  
  	/*
*************** hstore_populate_record(PG_FUNCTION_ARGS)
*** 943,951 ****
  		rec = NULL;
  
  		/*
! 		 * have no tuple to look at, so the only source of type info is the
! 		 * argtype. The lookup_rowtype_tupdesc call below will error out if we
! 		 * don't have a known composite type oid here.
  		 */
  		tupType = argtype;
  		tupTypmod = -1;
--- 950,958 ----
  		rec = NULL;
  
  		/*
! 		 * We have no tuple to look at, so the only source of type info is the
! 		 * argtype.  The lookup_rowtype_tupdesc_domain call below will error
! 		 * out if we don't have a known composite type oid here.
  		 */
  		tupType = argtype;
  		tupTypmod = -1;
*************** hstore_populate_record(PG_FUNCTION_ARGS)
*** 957,963 ****
  		if (PG_ARGISNULL(1))
  			PG_RETURN_POINTER(rec);
  
! 		/* Extract type info from the tuple itself */
  		tupType = HeapTupleHeaderGetTypeId(rec);
  		tupTypmod = HeapTupleHeaderGetTypMod(rec);
  	}
--- 964,973 ----
  		if (PG_ARGISNULL(1))
  			PG_RETURN_POINTER(rec);
  
! 		/*
! 		 * Extract type info from the tuple itself -- this will work even for
! 		 * anonymous record types.
! 		 */
  		tupType = HeapTupleHeaderGetTypeId(rec);
  		tupTypmod = HeapTupleHeaderGetTypMod(rec);
  	}
*************** hstore_populate_record(PG_FUNCTION_ARGS)
*** 975,981 ****
  	if (HS_COUNT(hs) == 0 && rec)
  		PG_RETURN_POINTER(rec);
  
! 	tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
  	ncolumns = tupdesc->natts;
  
  	if (rec)
--- 985,995 ----
  	if (HS_COUNT(hs) == 0 && rec)
  		PG_RETURN_POINTER(rec);
  
! 	/*
! 	 * Lookup the input record's tupdesc.  For the moment, we don't worry
! 	 * about whether it is a domain over composite.
! 	 */
! 	tupdesc = lookup_rowtype_tupdesc_domain(tupType, tupTypmod, false);
  	ncolumns = tupdesc->natts;
  
  	if (rec)
*************** hstore_populate_record(PG_FUNCTION_ARGS)
*** 1002,1007 ****
--- 1016,1022 ----
  		my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
  		my_extra->record_type = InvalidOid;
  		my_extra->record_typmod = 0;
+ 		my_extra->domain_info = NULL;
  	}
  
  	if (my_extra->record_type != tupType ||
*************** hstore_populate_record(PG_FUNCTION_ARGS)
*** 1103,1108 ****
--- 1118,1134 ----
  
  	rettuple = heap_form_tuple(tupdesc, values, nulls);
  
+ 	/*
+ 	 * If the target type is domain over composite, all we know at this point
+ 	 * is that we've made a valid value of the base composite type.  Must
+ 	 * check domain constraints before deciding we're done.
+ 	 */
+ 	if (argtype != tupdesc->tdtypeid)
+ 		domain_check(HeapTupleGetDatum(rettuple), false,
+ 					 argtype,
+ 					 &my_extra->domain_info,
+ 					 fcinfo->flinfo->fn_mcxt);
+ 
  	ReleaseTupleDesc(tupdesc);
  
  	PG_RETURN_DATUM(HeapTupleGetDatum(rettuple));
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index b397e18..3d46098 100644
*** a/doc/src/sgml/datatype.sgml
--- b/doc/src/sgml/datatype.sgml
*************** SET xmloption TO { DOCUMENT | CONTENT };
*** 4379,4386 ****
      underlying type &mdash; for example, any operator or function that
      can be applied to the underlying type will work on the domain type.
      The underlying type can be any built-in or user-defined base type,
!     enum type, array or range type, or another domain.  (Currently, domains
!     over composite types are not implemented.)
     </para>
  
     <para>
--- 4379,4385 ----
      underlying type &mdash; for example, any operator or function that
      can be applied to the underlying type will work on the domain type.
      The underlying type can be any built-in or user-defined base type,
!     enum type, array type, composite type, range type, or another domain.
     </para>
  
     <para>
diff --git a/doc/src/sgml/rowtypes.sgml b/doc/src/sgml/rowtypes.sgml
index 7e436a4..f80d44b 100644
*** a/doc/src/sgml/rowtypes.sgml
--- b/doc/src/sgml/rowtypes.sgml
*************** CREATE TABLE inventory_item (
*** 84,91 ****
    restriction of the current implementation: since no constraints are
    associated with a composite type, the constraints shown in the table
    definition <emphasis>do not apply</emphasis> to values of the composite type
!   outside the table.  (A partial workaround is to use domain
!   types as members of composite types.)
   </para>
   </sect2>
  
--- 84,92 ----
    restriction of the current implementation: since no constraints are
    associated with a composite type, the constraints shown in the table
    definition <emphasis>do not apply</emphasis> to values of the composite type
!   outside the table.  (To work around this, create a domain over the composite
!   type, and apply the desired constraints as <literal>CHECK</literal>
!   constraints of the domain.)
   </para>
   </sect2>
  
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index d9fccaa..9bdb72c 100644
*** a/doc/src/sgml/xfunc.sgml
--- b/doc/src/sgml/xfunc.sgml
*************** CREATE FUNCTION tf1 (accountno integer, 
*** 353,358 ****
--- 353,383 ----
  $$ LANGUAGE SQL;
  </programlisting>
      </para>
+ 
+     <para>
+      A <acronym>SQL</acronym> function must return exactly its declared
+      result type.  This may require inserting an explicit cast.
+      For example, suppose we wanted the
+      previous <function>add_em</function> function to return
+      type <type>float8</type> instead.  This won't work:
+ 
+ <programlisting>
+ CREATE FUNCTION add_em(integer, integer) RETURNS float8 AS $$
+     SELECT $1 + $2;
+ $$ LANGUAGE SQL;
+ </programlisting>
+ 
+      even though in other contexts <productname>PostgreSQL</productname>
+      would be willing to insert an implicit cast to
+      convert <type>integer</type> to <type>float8</type>.
+      We need to write it as
+ 
+ <programlisting>
+ CREATE FUNCTION add_em(integer, integer) RETURNS float8 AS $$
+     SELECT ($1 + $2)::float8;
+ $$ LANGUAGE SQL;
+ </programlisting>
+     </para>
     </sect2>
  
     <sect2 id="xfunc-sql-composite-functions">
*************** $$ LANGUAGE SQL;
*** 452,464 ****
        </listitem>
        <listitem>
         <para>
!         You must typecast the expressions to match the
!         definition of the composite type, or you will get errors like this:
  <screen>
  <computeroutput>
  ERROR:  function declared to return emp returns varchar instead of text at column 1
  </computeroutput>
  </screen>
         </para>
        </listitem>
       </itemizedlist>
--- 477,492 ----
        </listitem>
        <listitem>
         <para>
!         We must ensure each expression's type matches the corresponding
!         column of the composite type, inserting a cast if necessary.
!         Otherwise we'll get errors like this:
  <screen>
  <computeroutput>
  ERROR:  function declared to return emp returns varchar instead of text at column 1
  </computeroutput>
  </screen>
+         As with the base-type case, the function will not insert any casts
+         automatically.
         </para>
        </listitem>
       </itemizedlist>
*************** $$ LANGUAGE SQL;
*** 478,483 ****
--- 506,516 ----
       in this situation, but it is a handy alternative in some cases
       &mdash; for example, if we need to compute the result by calling
       another function that returns the desired composite value.
+      Another example is that if we are trying to write a function that
+      returns a domain over composite, rather than a plain composite type,
+      it is always necessary to write it as returning a single column,
+      since there is no other way to produce a value that is exactly of
+      the domain type.
      </para>
  
      <para>
diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index 245a374..1bd8a58 100644
*** a/src/backend/catalog/pg_inherits.c
--- b/src/backend/catalog/pg_inherits.c
*************** has_superclass(Oid relationId)
*** 301,306 ****
--- 301,311 ----
  /*
   * Given two type OIDs, determine whether the first is a complex type
   * (class type) that inherits from the second.
+  *
+  * This essentially asks whether the first type is guaranteed to be coercible
+  * to the second.  Therefore, we allow the first type to be a domain over a
+  * complex type that inherits from the second; that creates no difficulties.
+  * But the second type cannot be a domain.
   */
  bool
  typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId)
*************** typeInheritsFrom(Oid subclassTypeId, Oid
*** 314,322 ****
  	ListCell   *queue_item;
  
  	/* We need to work with the associated relation OIDs */
! 	subclassRelid = typeidTypeRelid(subclassTypeId);
  	if (subclassRelid == InvalidOid)
! 		return false;			/* not a complex type */
  	superclassRelid = typeidTypeRelid(superclassTypeId);
  	if (superclassRelid == InvalidOid)
  		return false;			/* not a complex type */
--- 319,327 ----
  	ListCell   *queue_item;
  
  	/* We need to work with the associated relation OIDs */
! 	subclassRelid = typeOrDomainTypeRelid(subclassTypeId);
  	if (subclassRelid == InvalidOid)
! 		return false;			/* not a complex type or domain over one */
  	superclassRelid = typeidTypeRelid(superclassTypeId);
  	if (superclassRelid == InvalidOid)
  		return false;			/* not a complex type */
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 571856e..47916cf 100644
*** a/src/backend/catalog/pg_proc.c
--- b/src/backend/catalog/pg_proc.c
*************** ProcedureCreate(const char *procedureNam
*** 262,268 ****
  	 */
  	if (parameterCount == 1 &&
  		OidIsValid(parameterTypes->values[0]) &&
! 		(relid = typeidTypeRelid(parameterTypes->values[0])) != InvalidOid &&
  		get_attnum(relid, procedureName) != InvalidAttrNumber)
  		ereport(ERROR,
  				(errcode(ERRCODE_DUPLICATE_COLUMN),
--- 262,268 ----
  	 */
  	if (parameterCount == 1 &&
  		OidIsValid(parameterTypes->values[0]) &&
! 		(relid = typeOrDomainTypeRelid(parameterTypes->values[0])) != InvalidOid &&
  		get_attnum(relid, procedureName) != InvalidAttrNumber)
  		ereport(ERROR,
  				(errcode(ERRCODE_DUPLICATE_COLUMN),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2d4dcd7..09a7001 100644
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
*************** find_typed_table_dependencies(Oid typeOi
*** 5091,5096 ****
--- 5091,5098 ----
   * isn't suitable, throw an error.  Currently, we require that the type
   * originated with CREATE TYPE AS.  We could support any row type, but doing so
   * would require handling a number of extra corner cases in the DDL commands.
+  * (Also, allowing domain-over-composite would open up a can of worms about
+  * whether and how the domain's constraints should apply to derived tables.)
   */
  void
  check_of_type(HeapTuple typetuple)
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index c1b87e0..7df942b 100644
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
*************** DefineDomain(CreateDomainStmt *stmt)
*** 798,810 ****
  	basetypeoid = HeapTupleGetOid(typeTup);
  
  	/*
! 	 * Base type must be a plain base type, another domain, an enum or a range
! 	 * type. Domains over pseudotypes would create a security hole.  Domains
! 	 * over composite types might be made to work in the future, but not
! 	 * today.
  	 */
  	typtype = baseType->typtype;
  	if (typtype != TYPTYPE_BASE &&
  		typtype != TYPTYPE_DOMAIN &&
  		typtype != TYPTYPE_ENUM &&
  		typtype != TYPTYPE_RANGE)
--- 798,813 ----
  	basetypeoid = HeapTupleGetOid(typeTup);
  
  	/*
! 	 * Base type must be a plain base type, a composite type, another domain,
! 	 * an enum or a range type.  Domains over pseudotypes would create a
! 	 * security hole.  (It would be shorter to code this to just check for
! 	 * pseudotypes; but it seems safer to call out the specific typtypes that
! 	 * are supported, rather than assume that all future typtypes would be
! 	 * automatically supported.)
  	 */
  	typtype = baseType->typtype;
  	if (typtype != TYPTYPE_BASE &&
+ 		typtype != TYPTYPE_COMPOSITE &&
  		typtype != TYPTYPE_DOMAIN &&
  		typtype != TYPTYPE_ENUM &&
  		typtype != TYPTYPE_RANGE)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index c5e97ef..a0f537b 100644
*** a/src/backend/executor/execExprInterp.c
--- b/src/backend/executor/execExprInterp.c
*************** ExecEvalWholeRowVar(ExprState *state, Ex
*** 3469,3476 ****
  			 * generates an INT4 NULL regardless of the dropped column type).
  			 * If we find a dropped column and cannot verify that case (1)
  			 * holds, we have to use the slow path to check (2) for each row.
  			 */
! 			var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
  
  			slot_tupdesc = slot->tts_tupleDescriptor;
  
--- 3469,3480 ----
  			 * generates an INT4 NULL regardless of the dropped column type).
  			 * If we find a dropped column and cannot verify that case (1)
  			 * holds, we have to use the slow path to check (2) for each row.
+ 			 *
+ 			 * If vartype is a domain over composite, just look through that
+ 			 * to the base composite type.
  			 */
! 			var_tupdesc = lookup_rowtype_tupdesc_domain(variable->vartype,
! 														-1, false);
  
  			slot_tupdesc = slot->tts_tupleDescriptor;
  
diff --git a/src/backend/executor/execSRF.c b/src/backend/executor/execSRF.c
index cce771d..aabe26c 100644
*** a/src/backend/executor/execSRF.c
--- b/src/backend/executor/execSRF.c
*************** init_sexpr(Oid foid, Oid input_collation
*** 734,740 ****
  		/* Must save tupdesc in sexpr's context */
  		oldcontext = MemoryContextSwitchTo(sexprCxt);
  
! 		if (functypclass == TYPEFUNC_COMPOSITE)
  		{
  			/* Composite data type, e.g. a table's row type */
  			Assert(tupdesc);
--- 734,741 ----
  		/* Must save tupdesc in sexpr's context */
  		oldcontext = MemoryContextSwitchTo(sexprCxt);
  
! 		if (functypclass == TYPEFUNC_COMPOSITE ||
! 			functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
  		{
  			/* Composite data type, e.g. a table's row type */
  			Assert(tupdesc);
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 42a4ca9..98eb777 100644
*** a/src/backend/executor/functions.c
--- b/src/backend/executor/functions.c
*************** check_sql_fn_retval(Oid func_id, Oid ret
*** 1665,1671 ****
  	}
  	else if (fn_typtype == TYPTYPE_COMPOSITE || rettype == RECORDOID)
  	{
! 		/* Returns a rowtype */
  		TupleDesc	tupdesc;
  		int			tupnatts;	/* physical number of columns in tuple */
  		int			tuplogcols; /* # of nondeleted columns in tuple */
--- 1665,1679 ----
  	}
  	else if (fn_typtype == TYPTYPE_COMPOSITE || rettype == RECORDOID)
  	{
! 		/*
! 		 * Returns a rowtype.
! 		 *
! 		 * Note that we will not consider a domain over composite to be a
! 		 * "rowtype" return type; it goes through the scalar case above.  This
! 		 * is because SQL functions don't provide any implicit casting to the
! 		 * result type, so there is no way to produce a domain-over-composite
! 		 * result except by computing it as an explicit single-column result.
! 		 */
  		TupleDesc	tupdesc;
  		int			tupnatts;	/* physical number of columns in tuple */
  		int			tuplogcols; /* # of nondeleted columns in tuple */
*************** check_sql_fn_retval(Oid func_id, Oid ret
*** 1711,1717 ****
  			}
  		}
  
! 		/* Is the rowtype fixed, or determined only at runtime? */
  		if (get_func_result_type(func_id, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
  		{
  			/*
--- 1719,1728 ----
  			}
  		}
  
! 		/*
! 		 * Is the rowtype fixed, or determined only at runtime?  (Note we
! 		 * cannot see TYPEFUNC_COMPOSITE_DOMAIN here.)
! 		 */
  		if (get_func_result_type(func_id, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
  		{
  			/*
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index 9f87a7e..de476ac 100644
*** a/src/backend/executor/nodeFunctionscan.c
--- b/src/backend/executor/nodeFunctionscan.c
*************** ExecInitFunctionScan(FunctionScan *node,
*** 383,389 ****
  											&funcrettype,
  											&tupdesc);
  
! 		if (functypclass == TYPEFUNC_COMPOSITE)
  		{
  			/* Composite data type, e.g. a table's row type */
  			Assert(tupdesc);
--- 383,390 ----
  											&funcrettype,
  											&tupdesc);
  
! 		if (functypclass == TYPEFUNC_COMPOSITE ||
! 			functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
  		{
  			/* Composite data type, e.g. a table's row type */
  			Assert(tupdesc);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index b58eb0f..7a67653 100644
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
*************** makeVarFromTargetEntry(Index varno,
*** 120,127 ****
   * table entry, and varattno == 0 to signal that it references the whole
   * tuple.  (Use of zero here is unclean, since it could easily be confused
   * with error cases, but it's not worth changing now.)  The vartype indicates
!  * a rowtype; either a named composite type, or RECORD.  This function
!  * encapsulates the logic for determining the correct rowtype OID to use.
   *
   * If allowScalar is true, then for the case where the RTE is a single function
   * returning a non-composite result type, we produce a normal Var referencing
--- 120,129 ----
   * table entry, and varattno == 0 to signal that it references the whole
   * tuple.  (Use of zero here is unclean, since it could easily be confused
   * with error cases, but it's not worth changing now.)  The vartype indicates
!  * a rowtype; either a named composite type, or a domain over a named
!  * composite type (only possible if the RTE is a function returning that),
!  * or RECORD.  This function encapsulates the logic for determining the
!  * correct rowtype OID to use.
   *
   * If allowScalar is true, then for the case where the RTE is a single function
   * returning a non-composite result type, we produce a normal Var referencing
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 7961362..5344f61 100644
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
*************** CommuteRowCompareExpr(RowCompareExpr *cl
*** 2356,2361 ****
--- 2356,2365 ----
   * is still what it was when the expression was parsed.  This is needed to
   * guard against improper simplification after ALTER COLUMN TYPE.  (XXX we
   * may well need to make similar checks elsewhere?)
+  *
+  * rowtypeid may come from a whole-row Var, and therefore it can be a domain
+  * over composite, but for this purpose we only care about checking the type
+  * of a contained field.
   */
  static bool
  rowtype_field_matches(Oid rowtypeid, int fieldnum,
*************** rowtype_field_matches(Oid rowtypeid, int
*** 2368,2374 ****
  	/* No issue for RECORD, since there is no way to ALTER such a type */
  	if (rowtypeid == RECORDOID)
  		return true;
! 	tupdesc = lookup_rowtype_tupdesc(rowtypeid, -1);
  	if (fieldnum <= 0 || fieldnum > tupdesc->natts)
  	{
  		ReleaseTupleDesc(tupdesc);
--- 2372,2378 ----
  	/* No issue for RECORD, since there is no way to ALTER such a type */
  	if (rowtypeid == RECORDOID)
  		return true;
! 	tupdesc = lookup_rowtype_tupdesc_domain(rowtypeid, -1, false);
  	if (fieldnum <= 0 || fieldnum > tupdesc->natts)
  	{
  		ReleaseTupleDesc(tupdesc);
*************** inline_set_returning_function(PlannerInf
*** 5005,5011 ****
  	 *
  	 * If the function returns a composite type, don't inline unless the check
  	 * shows it's returning a whole tuple result; otherwise what it's
! 	 * returning is a single composite column which is not what we need.
  	 */
  	if (!check_sql_fn_retval(func_oid, fexpr->funcresulttype,
  							 querytree_list,
--- 5009,5017 ----
  	 *
  	 * If the function returns a composite type, don't inline unless the check
  	 * shows it's returning a whole tuple result; otherwise what it's
! 	 * returning is a single composite column which is not what we need. (Like
! 	 * check_sql_fn_retval, we deliberately exclude domains over composite
! 	 * here.)
  	 */
  	if (!check_sql_fn_retval(func_oid, fexpr->funcresulttype,
  							 querytree_list,
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 53457dc..def41b3 100644
*** a/src/backend/parser/parse_coerce.c
--- b/src/backend/parser/parse_coerce.c
*************** coerce_type(ParseState *pstate, Node *no
*** 499,507 ****
--- 499,524 ----
  		 * Input class type is a subclass of target, so generate an
  		 * appropriate runtime conversion (removing unneeded columns and
  		 * possibly rearranging the ones that are wanted).
+ 		 *
+ 		 * We will also get here when the input is a domain over a subclass of
+ 		 * the target type.  To keep life simple for the executor, we define
+ 		 * ConvertRowtypeExpr as only working between regular composite types;
+ 		 * therefore, in such cases insert a RelabelType to smash the input
+ 		 * expression down to its base type.
  		 */
+ 		Oid			baseTypeId = getBaseType(inputTypeId);
  		ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr);
  
+ 		if (baseTypeId != inputTypeId)
+ 		{
+ 			RelabelType *rt = makeRelabelType((Expr *) node,
+ 											  baseTypeId, -1,
+ 											  InvalidOid,
+ 											  COERCE_IMPLICIT_CAST);
+ 
+ 			rt->location = location;
+ 			node = (Node *) rt;
+ 		}
  		r->arg = (Expr *) node;
  		r->resulttype = targetTypeId;
  		r->convertformat = cformat;
*************** coerce_record_to_complex(ParseState *pst
*** 966,971 ****
--- 983,990 ----
  						 int location)
  {
  	RowExpr    *rowexpr;
+ 	Oid			baseTypeId;
+ 	int32		baseTypeMod = -1;
  	TupleDesc	tupdesc;
  	List	   *args = NIL;
  	List	   *newargs;
*************** coerce_record_to_complex(ParseState *pst
*** 1001,1007 ****
  						format_type_be(targetTypeId)),
  				 parser_coercion_errposition(pstate, location, node)));
  
! 	tupdesc = lookup_rowtype_tupdesc(targetTypeId, -1);
  	newargs = NIL;
  	ucolno = 1;
  	arg = list_head(args);
--- 1020,1033 ----
  						format_type_be(targetTypeId)),
  				 parser_coercion_errposition(pstate, location, node)));
  
! 	/*
! 	 * Look up the composite type, accounting for possibility that what we are
! 	 * given is a domain over composite.
! 	 */
! 	baseTypeId = getBaseTypeAndTypmod(targetTypeId, &baseTypeMod);
! 	tupdesc = lookup_rowtype_tupdesc(baseTypeId, baseTypeMod);
! 
! 	/* Process the fields */
  	newargs = NIL;
  	ucolno = 1;
  	arg = list_head(args);
*************** coerce_record_to_complex(ParseState *pst
*** 1070,1079 ****
  
  	rowexpr = makeNode(RowExpr);
  	rowexpr->args = newargs;
! 	rowexpr->row_typeid = targetTypeId;
  	rowexpr->row_format = cformat;
  	rowexpr->colnames = NIL;	/* not needed for named target type */
  	rowexpr->location = location;
  	return (Node *) rowexpr;
  }
  
--- 1096,1117 ----
  
  	rowexpr = makeNode(RowExpr);
  	rowexpr->args = newargs;
! 	rowexpr->row_typeid = baseTypeId;
  	rowexpr->row_format = cformat;
  	rowexpr->colnames = NIL;	/* not needed for named target type */
  	rowexpr->location = location;
+ 
+ 	/* If target is a domain, apply constraints */
+ 	if (baseTypeId != targetTypeId)
+ 	{
+ 		rowexpr->row_format = COERCE_IMPLICIT_CAST;
+ 		return coerce_to_domain((Node *) rowexpr,
+ 								baseTypeId, baseTypeMod,
+ 								targetTypeId,
+ 								ccontext, cformat, location,
+ 								false);
+ 	}
+ 
  	return (Node *) rowexpr;
  }
  
*************** is_complex_array(Oid typid)
*** 2401,2413 ****
  
  /*
   * Check whether reltypeId is the row type of a typed table of type
!  * reloftypeId.  (This is conceptually similar to the subtype
!  * relationship checked by typeInheritsFrom().)
   */
  static bool
  typeIsOfTypedTable(Oid reltypeId, Oid reloftypeId)
  {
! 	Oid			relid = typeidTypeRelid(reltypeId);
  	bool		result = false;
  
  	if (relid)
--- 2439,2451 ----
  
  /*
   * Check whether reltypeId is the row type of a typed table of type
!  * reloftypeId, or is a domain over such a row type.  (This is conceptually
!  * similar to the subtype relationship checked by typeInheritsFrom().)
   */
  static bool
  typeIsOfTypedTable(Oid reltypeId, Oid reloftypeId)
  {
! 	Oid			relid = typeOrDomainTypeRelid(reltypeId);
  	bool		result = false;
  
  	if (relid)
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 2f2f2c7..fc0d6bc 100644
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
*************** ParseComplexProjection(ParseState *pstat
*** 1819,1836 ****
  	}
  
  	/*
! 	 * Else do it the hard way with get_expr_result_type().
  	 *
  	 * If it's a Var of type RECORD, we have to work even harder: we have to
! 	 * find what the Var refers to, and pass that to get_expr_result_type.
  	 * That task is handled by expandRecordVariable().
  	 */
  	if (IsA(first_arg, Var) &&
  		((Var *) first_arg)->vartype == RECORDOID)
  		tupdesc = expandRecordVariable(pstate, (Var *) first_arg, 0);
! 	else if (get_expr_result_type(first_arg, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
  		return NULL;			/* unresolvable RECORD type */
- 	Assert(tupdesc);
  
  	for (i = 0; i < tupdesc->natts; i++)
  	{
--- 1819,1837 ----
  	}
  
  	/*
! 	 * Else do it the hard way with get_expr_result_tupdesc().
  	 *
  	 * If it's a Var of type RECORD, we have to work even harder: we have to
! 	 * find what the Var refers to, and pass that to get_expr_result_tupdesc.
  	 * That task is handled by expandRecordVariable().
  	 */
  	if (IsA(first_arg, Var) &&
  		((Var *) first_arg)->vartype == RECORDOID)
  		tupdesc = expandRecordVariable(pstate, (Var *) first_arg, 0);
! 	else
! 		tupdesc = get_expr_result_tupdesc(first_arg, true);
! 	if (!tupdesc)
  		return NULL;			/* unresolvable RECORD type */
  
  	for (i = 0; i < tupdesc->natts; i++)
  	{
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index a9273af..ca32a37 100644
*** a/src/backend/parser/parse_relation.c
--- b/src/backend/parser/parse_relation.c
*************** addRangeTableEntryForFunction(ParseState
*** 1496,1502 ****
  						 parser_errposition(pstate, exprLocation(funcexpr))));
  		}
  
! 		if (functypclass == TYPEFUNC_COMPOSITE)
  		{
  			/* Composite data type, e.g. a table's row type */
  			Assert(tupdesc);
--- 1496,1503 ----
  						 parser_errposition(pstate, exprLocation(funcexpr))));
  		}
  
! 		if (functypclass == TYPEFUNC_COMPOSITE ||
! 			functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
  		{
  			/* Composite data type, e.g. a table's row type */
  			Assert(tupdesc);
*************** expandRTE(RangeTblEntry *rte, int rtinde
*** 2245,2251 ****
  					functypclass = get_expr_result_type(rtfunc->funcexpr,
  														&funcrettype,
  														&tupdesc);
! 					if (functypclass == TYPEFUNC_COMPOSITE)
  					{
  						/* Composite data type, e.g. a table's row type */
  						Assert(tupdesc);
--- 2246,2253 ----
  					functypclass = get_expr_result_type(rtfunc->funcexpr,
  														&funcrettype,
  														&tupdesc);
! 					if (functypclass == TYPEFUNC_COMPOSITE ||
! 						functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
  					{
  						/* Composite data type, e.g. a table's row type */
  						Assert(tupdesc);
*************** get_rte_attribute_type(RangeTblEntry *rt
*** 2765,2771 ****
  															&funcrettype,
  															&tupdesc);
  
! 						if (functypclass == TYPEFUNC_COMPOSITE)
  						{
  							/* Composite data type, e.g. a table's row type */
  							Form_pg_attribute att_tup;
--- 2767,2774 ----
  															&funcrettype,
  															&tupdesc);
  
! 						if (functypclass == TYPEFUNC_COMPOSITE ||
! 							functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
  						{
  							/* Composite data type, e.g. a table's row type */
  							Form_pg_attribute att_tup;
*************** get_rte_attribute_is_dropped(RangeTblEnt
*** 2966,2979 ****
  					if (attnum > atts_done &&
  						attnum <= atts_done + rtfunc->funccolcount)
  					{
- 						TypeFuncClass functypclass;
- 						Oid			funcrettype;
  						TupleDesc	tupdesc;
  
! 						functypclass = get_expr_result_type(rtfunc->funcexpr,
! 															&funcrettype,
! 															&tupdesc);
! 						if (functypclass == TYPEFUNC_COMPOSITE)
  						{
  							/* Composite data type, e.g. a table's row type */
  							Form_pg_attribute att_tup;
--- 2969,2979 ----
  					if (attnum > atts_done &&
  						attnum <= atts_done + rtfunc->funccolcount)
  					{
  						TupleDesc	tupdesc;
  
! 						tupdesc = get_expr_result_tupdesc(rtfunc->funcexpr,
! 														  true);
! 						if (tupdesc)
  						{
  							/* Composite data type, e.g. a table's row type */
  							Form_pg_attribute att_tup;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2547524..16d3dba 100644
*** a/src/backend/parser/parse_target.c
--- b/src/backend/parser/parse_target.c
*************** transformAssignmentIndirection(ParseStat
*** 725,730 ****
--- 725,732 ----
  		else
  		{
  			FieldStore *fstore;
+ 			Oid			baseTypeId;
+ 			int32		baseTypeMod;
  			Oid			typrelid;
  			AttrNumber	attnum;
  			Oid			fieldTypeId;
*************** transformAssignmentIndirection(ParseStat
*** 752,758 ****
  
  			/* No subscripts, so can process field selection here */
  
! 			typrelid = typeidTypeRelid(targetTypeId);
  			if (!typrelid)
  				ereport(ERROR,
  						(errcode(ERRCODE_DATATYPE_MISMATCH),
--- 754,767 ----
  
  			/* No subscripts, so can process field selection here */
  
! 			/*
! 			 * Look up the composite type, accounting for possibility that
! 			 * what we are given is a domain over composite.
! 			 */
! 			baseTypeMod = targetTypMod;
! 			baseTypeId = getBaseTypeAndTypmod(targetTypeId, &baseTypeMod);
! 
! 			typrelid = typeidTypeRelid(baseTypeId);
  			if (!typrelid)
  				ereport(ERROR,
  						(errcode(ERRCODE_DATATYPE_MISMATCH),
*************** transformAssignmentIndirection(ParseStat
*** 796,802 ****
  			fstore->arg = (Expr *) basenode;
  			fstore->newvals = list_make1(rhs);
  			fstore->fieldnums = list_make1_int(attnum);
! 			fstore->resulttype = targetTypeId;
  
  			return (Node *) fstore;
  		}
--- 805,821 ----
  			fstore->arg = (Expr *) basenode;
  			fstore->newvals = list_make1(rhs);
  			fstore->fieldnums = list_make1_int(attnum);
! 			fstore->resulttype = baseTypeId;
! 
! 			/* If target is a domain, apply constraints */
! 			if (baseTypeId != targetTypeId)
! 				return coerce_to_domain((Node *) fstore,
! 										baseTypeId, baseTypeMod,
! 										targetTypeId,
! 										COERCION_IMPLICIT,
! 										COERCE_IMPLICIT_CAST,
! 										location,
! 										false);
  
  			return (Node *) fstore;
  		}
*************** ExpandRowReference(ParseState *pstate, N
*** 1387,1408 ****
  	 * (This can be pretty inefficient if the expression involves nontrivial
  	 * computation :-(.)
  	 *
! 	 * Verify it's a composite type, and get the tupdesc.  We use
! 	 * get_expr_result_type() because that can handle references to functions
! 	 * returning anonymous record types.  If that fails, use
! 	 * lookup_rowtype_tupdesc(), which will almost certainly fail as well, but
! 	 * it will give an appropriate error message.
  	 *
  	 * If it's a Var of type RECORD, we have to work even harder: we have to
! 	 * find what the Var refers to, and pass that to get_expr_result_type.
  	 * That task is handled by expandRecordVariable().
  	 */
  	if (IsA(expr, Var) &&
  		((Var *) expr)->vartype == RECORDOID)
  		tupleDesc = expandRecordVariable(pstate, (Var *) expr, 0);
! 	else if (get_expr_result_type(expr, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
! 		tupleDesc = lookup_rowtype_tupdesc_copy(exprType(expr),
! 												exprTypmod(expr));
  	Assert(tupleDesc);
  
  	/* Generate a list of references to the individual fields */
--- 1406,1423 ----
  	 * (This can be pretty inefficient if the expression involves nontrivial
  	 * computation :-(.)
  	 *
! 	 * Verify it's a composite type, and get the tupdesc.
! 	 * get_expr_result_tupdesc() handles this conveniently.
  	 *
  	 * If it's a Var of type RECORD, we have to work even harder: we have to
! 	 * find what the Var refers to, and pass that to get_expr_result_tupdesc.
  	 * That task is handled by expandRecordVariable().
  	 */
  	if (IsA(expr, Var) &&
  		((Var *) expr)->vartype == RECORDOID)
  		tupleDesc = expandRecordVariable(pstate, (Var *) expr, 0);
! 	else
! 		tupleDesc = get_expr_result_tupdesc(expr, false);
  	Assert(tupleDesc);
  
  	/* Generate a list of references to the individual fields */
*************** expandRecordVariable(ParseState *pstate,
*** 1610,1624 ****
  
  	/*
  	 * We now have an expression we can't expand any more, so see if
! 	 * get_expr_result_type() can do anything with it.  If not, pass to
! 	 * lookup_rowtype_tupdesc() which will probably fail, but will give an
! 	 * appropriate error message while failing.
  	 */
! 	if (get_expr_result_type(expr, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
! 		tupleDesc = lookup_rowtype_tupdesc_copy(exprType(expr),
! 												exprTypmod(expr));
! 
! 	return tupleDesc;
  }
  
  
--- 1625,1633 ----
  
  	/*
  	 * We now have an expression we can't expand any more, so see if
! 	 * get_expr_result_tupdesc() can do anything with it.
  	 */
! 	return get_expr_result_tupdesc(expr, false);
  }
  
  
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index d0b3fbe..b032651 100644
*** a/src/backend/parser/parse_type.c
--- b/src/backend/parser/parse_type.c
*************** stringTypeDatum(Type tp, char *string, i
*** 641,647 ****
  	return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
  }
  
! /* given a typeid, return the type's typrelid (associated relation, if any) */
  Oid
  typeidTypeRelid(Oid type_id)
  {
--- 641,650 ----
  	return OidInputFunctionCall(typinput, string, typioparam, atttypmod);
  }
  
! /*
!  * Given a typeid, return the type's typrelid (associated relation), if any.
!  * Returns InvalidOid if type is not a composite type.
!  */
  Oid
  typeidTypeRelid(Oid type_id)
  {
*************** typeidTypeRelid(Oid type_id)
*** 652,658 ****
  	typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_id));
  	if (!HeapTupleIsValid(typeTuple))
  		elog(ERROR, "cache lookup failed for type %u", type_id);
- 
  	type = (Form_pg_type) GETSTRUCT(typeTuple);
  	result = type->typrelid;
  	ReleaseSysCache(typeTuple);
--- 655,660 ----
*************** typeidTypeRelid(Oid type_id)
*** 660,665 ****
--- 662,699 ----
  }
  
  /*
+  * Given a typeid, return the type's typrelid (associated relation), if any.
+  * Returns InvalidOid if type is not a composite type or a domain over one.
+  * This is the same as typeidTypeRelid(getBaseType(type_id)), but faster.
+  */
+ Oid
+ typeOrDomainTypeRelid(Oid type_id)
+ {
+ 	HeapTuple	typeTuple;
+ 	Form_pg_type type;
+ 	Oid			result;
+ 
+ 	for (;;)
+ 	{
+ 		typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_id));
+ 		if (!HeapTupleIsValid(typeTuple))
+ 			elog(ERROR, "cache lookup failed for type %u", type_id);
+ 		type = (Form_pg_type) GETSTRUCT(typeTuple);
+ 		if (type->typtype != TYPTYPE_DOMAIN)
+ 		{
+ 			/* Not a domain, so done looking through domains */
+ 			break;
+ 		}
+ 		/* It is a domain, so examine the base type instead */
+ 		type_id = type->typbasetype;
+ 		ReleaseSysCache(typeTuple);
+ 	}
+ 	result = type->typrelid;
+ 	ReleaseSysCache(typeTuple);
+ 	return result;
+ }
+ 
+ /*
   * error context callback for parse failure during parseTypeString()
   */
  static void
diff --git a/src/backend/utils/adt/domains.c b/src/backend/utils/adt/domains.c
index e61d91b..86f916f 100644
*** a/src/backend/utils/adt/domains.c
--- b/src/backend/utils/adt/domains.c
*************** domain_state_setup(Oid domainType, bool 
*** 82,90 ****
  	 * Verify that domainType represents a valid domain type.  We need to be
  	 * careful here because domain_in and domain_recv can be called from SQL,
  	 * possibly with incorrect arguments.  We use lookup_type_cache mainly
! 	 * because it will throw a clean user-facing error for a bad OID.
  	 */
! 	typentry = lookup_type_cache(domainType, 0);
  	if (typentry->typtype != TYPTYPE_DOMAIN)
  		ereport(ERROR,
  				(errcode(ERRCODE_DATATYPE_MISMATCH),
--- 82,91 ----
  	 * Verify that domainType represents a valid domain type.  We need to be
  	 * careful here because domain_in and domain_recv can be called from SQL,
  	 * possibly with incorrect arguments.  We use lookup_type_cache mainly
! 	 * because it will throw a clean user-facing error for a bad OID; but also
! 	 * it can cache the underlying base type info.
  	 */
! 	typentry = lookup_type_cache(domainType, TYPECACHE_DOMAIN_BASE_INFO);
  	if (typentry->typtype != TYPTYPE_DOMAIN)
  		ereport(ERROR,
  				(errcode(ERRCODE_DATATYPE_MISMATCH),
*************** domain_state_setup(Oid domainType, bool 
*** 92,99 ****
  						format_type_be(domainType))));
  
  	/* Find out the base type */
! 	my_extra->typtypmod = -1;
! 	baseType = getBaseTypeAndTypmod(domainType, &my_extra->typtypmod);
  
  	/* Look up underlying I/O function */
  	if (binary)
--- 93,100 ----
  						format_type_be(domainType))));
  
  	/* Find out the base type */
! 	baseType = typentry->domainBaseType;
! 	my_extra->typtypmod = typentry->domainBaseTypmod;
  
  	/* Look up underlying I/O function */
  	if (binary)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index d36fd9e..76ef01c 100644
*** a/src/backend/utils/adt/jsonfuncs.c
--- b/src/backend/utils/adt/jsonfuncs.c
*************** typedef struct CompositeIOData
*** 169,174 ****
--- 169,179 ----
  	 */
  	RecordIOData *record_io;	/* metadata cache for populate_record() */
  	TupleDesc	tupdesc;		/* cached tuple descriptor */
+ 	/* these fields differ from target type only if domain over composite: */
+ 	Oid			base_typid;		/* base type id */
+ 	int32		base_typmod;	/* base type modifier */
+ 	/* this field is used only if target type is domain over composite: */
+ 	void	   *domain_info;	/* opaque cache for domain checks */
  } CompositeIOData;
  
  /* structure to cache metadata needed for populate_domain() */
*************** typedef enum TypeCat
*** 186,191 ****
--- 191,197 ----
  	TYPECAT_SCALAR = 's',
  	TYPECAT_ARRAY = 'a',
  	TYPECAT_COMPOSITE = 'c',
+ 	TYPECAT_COMPOSITE_DOMAIN = 'C',
  	TYPECAT_DOMAIN = 'd'
  } TypeCat;
  
*************** struct RecordIOData
*** 217,223 ****
  	ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER];
  };
  
! /* state for populate_recordset */
  typedef struct PopulateRecordsetState
  {
  	JsonLexContext *lex;
--- 223,237 ----
  	ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER];
  };
  
! /* per-query cache for populate_recordset */
! typedef struct PopulateRecordsetCache
! {
! 	Oid			argtype;		/* declared type of the record argument */
! 	ColumnIOData c;				/* metadata cache for populate_composite() */
! 	MemoryContext fn_mcxt;		/* where this is stored */
! } PopulateRecordsetCache;
! 
! /* per-call state for populate_recordset */
  typedef struct PopulateRecordsetState
  {
  	JsonLexContext *lex;
*************** typedef struct PopulateRecordsetState
*** 227,243 ****
  	char	   *save_json_start;
  	JsonTokenType saved_token_type;
  	Tuplestorestate *tuple_store;
- 	TupleDesc	ret_tdesc;
  	HeapTupleHeader rec;
! 	RecordIOData **my_extra;
! 	MemoryContext fn_mcxt;		/* used to stash IO funcs */
  } PopulateRecordsetState;
  
  /* structure to cache metadata needed for populate_record_worker() */
  typedef struct PopulateRecordCache
  {
! 	Oid			argtype;		/* verified row type of the first argument */
! 	CompositeIOData io;			/* metadata cache for populate_composite() */
  } PopulateRecordCache;
  
  /* common data for populate_array_json() and populate_array_dim_jsonb() */
--- 241,255 ----
  	char	   *save_json_start;
  	JsonTokenType saved_token_type;
  	Tuplestorestate *tuple_store;
  	HeapTupleHeader rec;
! 	PopulateRecordsetCache *cache;
  } PopulateRecordsetState;
  
  /* structure to cache metadata needed for populate_record_worker() */
  typedef struct PopulateRecordCache
  {
! 	Oid			argtype;		/* declared type of the record argument */
! 	ColumnIOData c;				/* metadata cache for populate_composite() */
  } PopulateRecordCache;
  
  /* common data for populate_array_json() and populate_array_dim_jsonb() */
*************** static Datum populate_record_worker(Func
*** 415,430 ****
  static HeapTupleHeader populate_record(TupleDesc tupdesc, RecordIOData **record_p,
  				HeapTupleHeader defaultval, MemoryContext mcxt,
  				JsObject *obj);
- static Datum populate_record_field(ColumnIOData *col, Oid typid, int32 typmod,
- 					  const char *colname, MemoryContext mcxt,
- 					  Datum defaultval, JsValue *jsv, bool *isnull);
  static void JsValueToJsObject(JsValue *jsv, JsObject *jso);
! static Datum populate_composite(CompositeIOData *io, Oid typid, int32 typmod,
  				   const char *colname, MemoryContext mcxt,
! 				   HeapTupleHeader defaultval, JsValue *jsv);
  static Datum populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv);
  static void prepare_column_cache(ColumnIOData *column, Oid typid, int32 typmod,
! 					 MemoryContext mcxt, bool json);
  static Datum populate_record_field(ColumnIOData *col, Oid typid, int32 typmod,
  					  const char *colname, MemoryContext mcxt, Datum defaultval,
  					  JsValue *jsv, bool *isnull);
--- 427,439 ----
  static HeapTupleHeader populate_record(TupleDesc tupdesc, RecordIOData **record_p,
  				HeapTupleHeader defaultval, MemoryContext mcxt,
  				JsObject *obj);
  static void JsValueToJsObject(JsValue *jsv, JsObject *jso);
! static Datum populate_composite(CompositeIOData *io, Oid typid,
  				   const char *colname, MemoryContext mcxt,
! 				   HeapTupleHeader defaultval, JsValue *jsv, bool isnull);
  static Datum populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv);
  static void prepare_column_cache(ColumnIOData *column, Oid typid, int32 typmod,
! 					 MemoryContext mcxt, bool need_scalar);
  static Datum populate_record_field(ColumnIOData *col, Oid typid, int32 typmod,
  					  const char *colname, MemoryContext mcxt, Datum defaultval,
  					  JsValue *jsv, bool *isnull);
*************** JsValueToJsObject(JsValue *jsv, JsObject
*** 2704,2728 ****
  	}
  }
  
! /* recursively populate a composite (row type) value from json/jsonb */
! static Datum
! populate_composite(CompositeIOData *io,
! 				   Oid typid,
! 				   int32 typmod,
! 				   const char *colname,
! 				   MemoryContext mcxt,
! 				   HeapTupleHeader defaultval,
! 				   JsValue *jsv)
  {
- 	HeapTupleHeader tuple;
- 	JsObject	jso;
- 
- 	/* acquire cached tuple descriptor */
  	if (!io->tupdesc ||
! 		io->tupdesc->tdtypeid != typid ||
! 		io->tupdesc->tdtypmod != typmod)
  	{
! 		TupleDesc	tupdesc = lookup_rowtype_tupdesc(typid, typmod);
  		MemoryContext oldcxt;
  
  		if (io->tupdesc)
--- 2713,2728 ----
  	}
  }
  
! /* acquire or update cached tuple descriptor for a composite type */
! static void
! update_cached_tupdesc(CompositeIOData *io, MemoryContext mcxt)
  {
  	if (!io->tupdesc ||
! 		io->tupdesc->tdtypeid != io->base_typid ||
! 		io->tupdesc->tdtypmod != io->base_typmod)
  	{
! 		TupleDesc	tupdesc = lookup_rowtype_tupdesc(io->base_typid,
! 													 io->base_typmod);
  		MemoryContext oldcxt;
  
  		if (io->tupdesc)
*************** populate_composite(CompositeIOData *io,
*** 2735,2751 ****
  
  		ReleaseTupleDesc(tupdesc);
  	}
  
! 	/* prepare input value */
! 	JsValueToJsObject(jsv, &jso);
  
! 	/* populate resulting record tuple */
! 	tuple = populate_record(io->tupdesc, &io->record_io,
! 							defaultval, mcxt, &jso);
  
! 	JsObjectFree(&jso);
  
! 	return HeapTupleHeaderGetDatum(tuple);
  }
  
  /* populate non-null scalar value from json/jsonb value */
--- 2735,2780 ----
  
  		ReleaseTupleDesc(tupdesc);
  	}
+ }
  
! /* recursively populate a composite (row type) value from json/jsonb */
! static Datum
! populate_composite(CompositeIOData *io,
! 				   Oid typid,
! 				   const char *colname,
! 				   MemoryContext mcxt,
! 				   HeapTupleHeader defaultval,
! 				   JsValue *jsv,
! 				   bool isnull)
! {
! 	Datum		result;
  
! 	/* acquire/update cached tuple descriptor */
! 	update_cached_tupdesc(io, mcxt);
  
! 	if (isnull)
! 		result = (Datum) 0;
! 	else
! 	{
! 		HeapTupleHeader tuple;
! 		JsObject	jso;
  
! 		/* prepare input value */
! 		JsValueToJsObject(jsv, &jso);
! 
! 		/* populate resulting record tuple */
! 		tuple = populate_record(io->tupdesc, &io->record_io,
! 								defaultval, mcxt, &jso);
! 		result = HeapTupleHeaderGetDatum(tuple);
! 
! 		JsObjectFree(&jso);
! 	}
! 
! 	/* if it's domain over composite, check domain constraints */
! 	if (typid != io->base_typid)
! 		domain_check(result, isnull, typid, &io->domain_info, mcxt);
! 
! 	return result;
  }
  
  /* populate non-null scalar value from json/jsonb value */
*************** prepare_column_cache(ColumnIOData *colum
*** 2867,2873 ****
  					 Oid typid,
  					 int32 typmod,
  					 MemoryContext mcxt,
! 					 bool json)
  {
  	HeapTuple	tup;
  	Form_pg_type type;
--- 2896,2902 ----
  					 Oid typid,
  					 int32 typmod,
  					 MemoryContext mcxt,
! 					 bool need_scalar)
  {
  	HeapTuple	tup;
  	Form_pg_type type;
*************** prepare_column_cache(ColumnIOData *colum
*** 2883,2900 ****
  
  	if (type->typtype == TYPTYPE_DOMAIN)
  	{
! 		column->typcat = TYPECAT_DOMAIN;
! 		column->io.domain.base_typid = type->typbasetype;
! 		column->io.domain.base_typmod = type->typtypmod;
! 		column->io.domain.base_io = MemoryContextAllocZero(mcxt,
! 														   sizeof(ColumnIOData));
! 		column->io.domain.domain_info = NULL;
  	}
  	else if (type->typtype == TYPTYPE_COMPOSITE || typid == RECORDOID)
  	{
  		column->typcat = TYPECAT_COMPOSITE;
  		column->io.composite.record_io = NULL;
  		column->io.composite.tupdesc = NULL;
  	}
  	else if (type->typlen == -1 && OidIsValid(type->typelem))
  	{
--- 2912,2954 ----
  
  	if (type->typtype == TYPTYPE_DOMAIN)
  	{
! 		/*
! 		 * We can move directly to the bottom base type; domain_check() will
! 		 * take care of checking all constraints for a stack of domains.
! 		 */
! 		Oid			base_typid;
! 		int32		base_typmod = typmod;
! 
! 		base_typid = getBaseTypeAndTypmod(typid, &base_typmod);
! 		if (get_typtype(base_typid) == TYPTYPE_COMPOSITE)
! 		{
! 			/* domain over composite has its own code path */
! 			column->typcat = TYPECAT_COMPOSITE_DOMAIN;
! 			column->io.composite.record_io = NULL;
! 			column->io.composite.tupdesc = NULL;
! 			column->io.composite.base_typid = base_typid;
! 			column->io.composite.base_typmod = base_typmod;
! 			column->io.composite.domain_info = NULL;
! 		}
! 		else
! 		{
! 			/* domain over anything else */
! 			column->typcat = TYPECAT_DOMAIN;
! 			column->io.domain.base_typid = base_typid;
! 			column->io.domain.base_typmod = base_typmod;
! 			column->io.domain.base_io =
! 				MemoryContextAllocZero(mcxt, sizeof(ColumnIOData));
! 			column->io.domain.domain_info = NULL;
! 		}
  	}
  	else if (type->typtype == TYPTYPE_COMPOSITE || typid == RECORDOID)
  	{
  		column->typcat = TYPECAT_COMPOSITE;
  		column->io.composite.record_io = NULL;
  		column->io.composite.tupdesc = NULL;
+ 		column->io.composite.base_typid = typid;
+ 		column->io.composite.base_typmod = typmod;
+ 		column->io.composite.domain_info = NULL;
  	}
  	else if (type->typlen == -1 && OidIsValid(type->typelem))
  	{
*************** prepare_column_cache(ColumnIOData *colum
*** 2906,2915 ****
  		column->io.array.element_typmod = typmod;
  	}
  	else
  		column->typcat = TYPECAT_SCALAR;
  
! 	/* don't need input function when converting from jsonb to jsonb */
! 	if (json || typid != JSONBOID)
  	{
  		Oid			typioproc;
  
--- 2960,2972 ----
  		column->io.array.element_typmod = typmod;
  	}
  	else
+ 	{
  		column->typcat = TYPECAT_SCALAR;
+ 		need_scalar = true;
+ 	}
  
! 	/* caller can force us to look up scalar_io info even for non-scalars */
! 	if (need_scalar)
  	{
  		Oid			typioproc;
  
*************** populate_record_field(ColumnIOData *col,
*** 2935,2943 ****
  
  	check_stack_depth();
  
! 	/* prepare column metadata cache for the given type */
  	if (col->typid != typid || col->typmod != typmod)
! 		prepare_column_cache(col, typid, typmod, mcxt, jsv->is_json);
  
  	*isnull = JsValueIsNull(jsv);
  
--- 2992,3003 ----
  
  	check_stack_depth();
  
! 	/*
! 	 * Prepare column metadata cache for the given type.  Force lookup of the
! 	 * scalar_io data so that the json string hack below will work.
! 	 */
  	if (col->typid != typid || col->typmod != typmod)
! 		prepare_column_cache(col, typid, typmod, mcxt, true);
  
  	*isnull = JsValueIsNull(jsv);
  
*************** populate_record_field(ColumnIOData *col,
*** 2945,2955 ****
  
  	/* try to convert json string to a non-scalar type through input function */
  	if (JsValueIsString(jsv) &&
! 		(typcat == TYPECAT_ARRAY || typcat == TYPECAT_COMPOSITE))
  		typcat = TYPECAT_SCALAR;
  
! 	/* we must perform domain checks for NULLs */
! 	if (*isnull && typcat != TYPECAT_DOMAIN)
  		return (Datum) 0;
  
  	switch (typcat)
--- 3005,3019 ----
  
  	/* try to convert json string to a non-scalar type through input function */
  	if (JsValueIsString(jsv) &&
! 		(typcat == TYPECAT_ARRAY ||
! 		 typcat == TYPECAT_COMPOSITE ||
! 		 typcat == TYPECAT_COMPOSITE_DOMAIN))
  		typcat = TYPECAT_SCALAR;
  
! 	/* we must perform domain checks for NULLs, otherwise exit immediately */
! 	if (*isnull &&
! 		typcat != TYPECAT_DOMAIN &&
! 		typcat != TYPECAT_COMPOSITE_DOMAIN)
  		return (Datum) 0;
  
  	switch (typcat)
*************** populate_record_field(ColumnIOData *col,
*** 2961,2972 ****
  			return populate_array(&col->io.array, colname, mcxt, jsv);
  
  		case TYPECAT_COMPOSITE:
! 			return populate_composite(&col->io.composite, typid, typmod,
  									  colname, mcxt,
  									  DatumGetPointer(defaultval)
  									  ? DatumGetHeapTupleHeader(defaultval)
  									  : NULL,
! 									  jsv);
  
  		case TYPECAT_DOMAIN:
  			return populate_domain(&col->io.domain, typid, colname, mcxt,
--- 3025,3037 ----
  			return populate_array(&col->io.array, colname, mcxt, jsv);
  
  		case TYPECAT_COMPOSITE:
! 		case TYPECAT_COMPOSITE_DOMAIN:
! 			return populate_composite(&col->io.composite, typid,
  									  colname, mcxt,
  									  DatumGetPointer(defaultval)
  									  ? DatumGetHeapTupleHeader(defaultval)
  									  : NULL,
! 									  jsv, *isnull);
  
  		case TYPECAT_DOMAIN:
  			return populate_domain(&col->io.domain, typid, colname, mcxt,
*************** populate_record_worker(FunctionCallInfo 
*** 3137,3146 ****
  	int			json_arg_num = have_record_arg ? 1 : 0;
  	Oid			jtype = get_fn_expr_argtype(fcinfo->flinfo, json_arg_num);
  	JsValue		jsv = {0};
! 	HeapTupleHeader rec = NULL;
! 	Oid			tupType;
! 	int32		tupTypmod;
! 	TupleDesc	tupdesc = NULL;
  	Datum		rettuple;
  	JsonbValue	jbv;
  	MemoryContext fnmcxt = fcinfo->flinfo->fn_mcxt;
--- 3202,3208 ----
  	int			json_arg_num = have_record_arg ? 1 : 0;
  	Oid			jtype = get_fn_expr_argtype(fcinfo->flinfo, json_arg_num);
  	JsValue		jsv = {0};
! 	HeapTupleHeader rec;
  	Datum		rettuple;
  	JsonbValue	jbv;
  	MemoryContext fnmcxt = fcinfo->flinfo->fn_mcxt;
*************** populate_record_worker(FunctionCallInfo 
*** 3149,3225 ****
  	Assert(jtype == JSONOID || jtype == JSONBOID);
  
  	/*
! 	 * We arrange to look up the needed I/O info just once per series of
! 	 * calls, assuming the record type doesn't change underneath us.
  	 */
  	if (!cache)
  		fcinfo->flinfo->fn_extra = cache =
  			MemoryContextAllocZero(fnmcxt, sizeof(*cache));
  
! 	if (have_record_arg)
! 	{
! 		Oid			argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
! 
! 		if (cache->argtype != argtype)
  		{
! 			if (!type_is_rowtype(argtype))
  				ereport(ERROR,
  						(errcode(ERRCODE_DATATYPE_MISMATCH),
  						 errmsg("first argument of %s must be a row type",
  								funcname)));
- 
- 			cache->argtype = argtype;
  		}
! 
! 		if (PG_ARGISNULL(0))
  		{
- 			if (PG_ARGISNULL(1))
- 				PG_RETURN_NULL();
- 
  			/*
! 			 * We have no tuple to look at, so the only source of type info is
! 			 * the argtype. The lookup_rowtype_tupdesc call below will error
! 			 * out if we don't have a known composite type oid here.
  			 */
! 			tupType = argtype;
! 			tupTypmod = -1;
! 		}
! 		else
! 		{
! 			rec = PG_GETARG_HEAPTUPLEHEADER(0);
  
! 			if (PG_ARGISNULL(1))
! 				PG_RETURN_POINTER(rec);
  
! 			/* Extract type info from the tuple itself */
! 			tupType = HeapTupleHeaderGetTypeId(rec);
! 			tupTypmod = HeapTupleHeaderGetTypMod(rec);
  		}
  	}
- 	else
- 	{
- 		/* json{b}_to_record case */
- 		if (PG_ARGISNULL(0))
- 			PG_RETURN_NULL();
- 
- 		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- 			ereport(ERROR,
- 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- 					 errmsg("function returning record called in context "
- 							"that cannot accept type record"),
- 					 errhint("Try calling the function in the FROM clause "
- 							 "using a column definition list.")));
  
! 		Assert(tupdesc);
  
  		/*
! 		 * Add tupdesc to the cache and set the appropriate values of
! 		 * tupType/tupTypmod for proper cache usage in populate_composite().
  		 */
! 		cache->io.tupdesc = tupdesc;
  
! 		tupType = tupdesc->tdtypeid;
! 		tupTypmod = tupdesc->tdtypmod;
  	}
  
  	jsv.is_json = jtype == JSONOID;
--- 3211,3295 ----
  	Assert(jtype == JSONOID || jtype == JSONBOID);
  
  	/*
! 	 * If first time through, identify input/result record type.  Note that
! 	 * this stanza looks only at fcinfo context, which can't change during the
! 	 * query; so we may not be able to fully resolve a RECORD input type yet.
  	 */
  	if (!cache)
+ 	{
  		fcinfo->flinfo->fn_extra = cache =
  			MemoryContextAllocZero(fnmcxt, sizeof(*cache));
  
! 		if (have_record_arg)
  		{
! 			/*
! 			 * json{b}_populate_record case: result type will be same as first
! 			 * argument's.
! 			 */
! 			cache->argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
! 			prepare_column_cache(&cache->c,
! 								 cache->argtype, -1,
! 								 fnmcxt, false);
! 			if (cache->c.typcat != TYPECAT_COMPOSITE &&
! 				cache->c.typcat != TYPECAT_COMPOSITE_DOMAIN)
  				ereport(ERROR,
  						(errcode(ERRCODE_DATATYPE_MISMATCH),
  						 errmsg("first argument of %s must be a row type",
  								funcname)));
  		}
! 		else
  		{
  			/*
! 			 * json{b}_to_record case: result type is specified by calling
! 			 * query.  Here it is syntactically impossible to specify the
! 			 * target type as domain-over-composite.
  			 */
! 			TupleDesc	tupdesc;
! 			MemoryContext old_cxt;
  
! 			if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
! 				ereport(ERROR,
! 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 						 errmsg("function returning record called in context "
! 								"that cannot accept type record"),
! 						 errhint("Try calling the function in the FROM clause "
! 								 "using a column definition list.")));
  
! 			Assert(tupdesc);
! 			cache->argtype = tupdesc->tdtypeid;
! 
! 			/* Save identified tupdesc */
! 			old_cxt = MemoryContextSwitchTo(fnmcxt);
! 			cache->c.io.composite.tupdesc = CreateTupleDescCopy(tupdesc);
! 			cache->c.io.composite.base_typid = tupdesc->tdtypeid;
! 			cache->c.io.composite.base_typmod = tupdesc->tdtypmod;
! 			MemoryContextSwitchTo(old_cxt);
  		}
  	}
  
! 	/* Collect record arg if we have one */
! 	if (have_record_arg && !PG_ARGISNULL(0))
! 	{
! 		rec = PG_GETARG_HEAPTUPLEHEADER(0);
  
  		/*
! 		 * When declared arg type is RECORD, identify actual record type from
! 		 * the tuple itself.  Note the lookup_rowtype_tupdesc call in
! 		 * update_cached_tupdesc will fail if we're unable to do this.
  		 */
! 		if (cache->argtype == RECORDOID)
! 			cache->c.io.composite.base_typmod = HeapTupleHeaderGetTypMod(rec);
! 	}
! 	else
! 		rec = NULL;
  
! 	/* If no JSON argument, just return the record (if any) unchanged */
! 	if (PG_ARGISNULL(json_arg_num))
! 	{
! 		if (rec)
! 			PG_RETURN_POINTER(rec);
! 		else
! 			PG_RETURN_NULL();
  	}
  
  	jsv.is_json = jtype == JSONOID;
*************** populate_record_worker(FunctionCallInfo 
*** 3245,3258 ****
  		jbv.val.binary.len = VARSIZE(jb) - VARHDRSZ;
  	}
  
! 	rettuple = populate_composite(&cache->io, tupType, tupTypmod,
! 								  NULL, fnmcxt, rec, &jsv);
! 
! 	if (tupdesc)
! 	{
! 		cache->io.tupdesc = NULL;
! 		ReleaseTupleDesc(tupdesc);
! 	}
  
  	PG_RETURN_DATUM(rettuple);
  }
--- 3315,3322 ----
  		jbv.val.binary.len = VARSIZE(jb) - VARHDRSZ;
  	}
  
! 	rettuple = populate_composite(&cache->c.io.composite, cache->argtype,
! 								  NULL, fnmcxt, rec, &jsv, false);
  
  	PG_RETURN_DATUM(rettuple);
  }
*************** json_to_recordset(PG_FUNCTION_ARGS)
*** 3438,3450 ****
  static void
  populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
  {
  	HeapTupleData tuple;
- 	HeapTupleHeader tuphead = populate_record(state->ret_tdesc,
- 											  state->my_extra,
- 											  state->rec,
- 											  state->fn_mcxt,
- 											  obj);
  
  	tuple.t_len = HeapTupleHeaderGetDatumLength(tuphead);
  	ItemPointerSetInvalid(&(tuple.t_self));
  	tuple.t_tableOid = InvalidOid;
--- 3502,3529 ----
  static void
  populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
  {
+ 	PopulateRecordsetCache *cache = state->cache;
+ 	HeapTupleHeader tuphead;
  	HeapTupleData tuple;
  
+ 	/* acquire/update cached tuple descriptor */
+ 	update_cached_tupdesc(&cache->c.io.composite, cache->fn_mcxt);
+ 
+ 	/* replace record fields from json */
+ 	tuphead = populate_record(cache->c.io.composite.tupdesc,
+ 							  &cache->c.io.composite.record_io,
+ 							  state->rec,
+ 							  cache->fn_mcxt,
+ 							  obj);
+ 
+ 	/* if it's domain over composite, check domain constraints */
+ 	if (cache->argtype != cache->c.io.composite.base_typid)
+ 		domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ 					 cache->argtype,
+ 					 &cache->c.io.composite.domain_info,
+ 					 cache->fn_mcxt);
+ 
+ 	/* ok, save into tuplestore */
  	tuple.t_len = HeapTupleHeaderGetDatumLength(tuphead);
  	ItemPointerSetInvalid(&(tuple.t_self));
  	tuple.t_tableOid = InvalidOid;
*************** populate_recordset_worker(FunctionCallIn
*** 3465,3489 ****
  	ReturnSetInfo *rsi;
  	MemoryContext old_cxt;
  	HeapTupleHeader rec;
! 	TupleDesc	tupdesc;
  	PopulateRecordsetState *state;
  
- 	if (have_record_arg)
- 	{
- 		Oid			argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
- 
- 		if (!type_is_rowtype(argtype))
- 			ereport(ERROR,
- 					(errcode(ERRCODE_DATATYPE_MISMATCH),
- 					 errmsg("first argument of %s must be a row type",
- 							funcname)));
- 	}
- 
  	rsi = (ReturnSetInfo *) fcinfo->resultinfo;
  
  	if (!rsi || !IsA(rsi, ReturnSetInfo) ||
! 		(rsi->allowedModes & SFRM_Materialize) == 0 ||
! 		rsi->expectedDesc == NULL)
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  				 errmsg("set-valued function called in context that "
--- 3544,3556 ----
  	ReturnSetInfo *rsi;
  	MemoryContext old_cxt;
  	HeapTupleHeader rec;
! 	PopulateRecordsetCache *cache = fcinfo->flinfo->fn_extra;
  	PopulateRecordsetState *state;
  
  	rsi = (ReturnSetInfo *) fcinfo->resultinfo;
  
  	if (!rsi || !IsA(rsi, ReturnSetInfo) ||
! 		(rsi->allowedModes & SFRM_Materialize) == 0)
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  				 errmsg("set-valued function called in context that "
*************** populate_recordset_worker(FunctionCallIn
*** 3492,3531 ****
  	rsi->returnMode = SFRM_Materialize;
  
  	/*
! 	 * get the tupdesc from the result set info - it must be a record type
! 	 * because we already checked that arg1 is a record type, or we're in a
! 	 * to_record function which returns a setof record.
  	 */
! 	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				 errmsg("function returning record called in context "
! 						"that cannot accept type record")));
  
  	/* if the json is null send back an empty set */
  	if (PG_ARGISNULL(json_arg_num))
  		PG_RETURN_NULL();
  
- 	if (!have_record_arg || PG_ARGISNULL(0))
- 		rec = NULL;
- 	else
- 		rec = PG_GETARG_HEAPTUPLEHEADER(0);
- 
  	state = palloc0(sizeof(PopulateRecordsetState));
  
! 	/* make these in a sufficiently long-lived memory context */
  	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
- 	state->ret_tdesc = CreateTupleDescCopy(tupdesc);
- 	BlessTupleDesc(state->ret_tdesc);
  	state->tuple_store = tuplestore_begin_heap(rsi->allowedModes &
  											   SFRM_Materialize_Random,
  											   false, work_mem);
  	MemoryContextSwitchTo(old_cxt);
  
  	state->function_name = funcname;
! 	state->my_extra = (RecordIOData **) &fcinfo->flinfo->fn_extra;
  	state->rec = rec;
- 	state->fn_mcxt = fcinfo->flinfo->fn_mcxt;
  
  	if (jtype == JSONOID)
  	{
--- 3559,3652 ----
  	rsi->returnMode = SFRM_Materialize;
  
  	/*
! 	 * If first time through, identify input/result record type.  Note that
! 	 * this stanza looks only at fcinfo context, which can't change during the
! 	 * query; so we may not be able to fully resolve a RECORD input type yet.
  	 */
! 	if (!cache)
! 	{
! 		fcinfo->flinfo->fn_extra = cache =
! 			MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt, sizeof(*cache));
! 		cache->fn_mcxt = fcinfo->flinfo->fn_mcxt;
! 
! 		if (have_record_arg)
! 		{
! 			/*
! 			 * json{b}_populate_recordset case: result type will be same as
! 			 * first argument's.
! 			 */
! 			cache->argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
! 			prepare_column_cache(&cache->c,
! 								 cache->argtype, -1,
! 								 cache->fn_mcxt, false);
! 			if (cache->c.typcat != TYPECAT_COMPOSITE &&
! 				cache->c.typcat != TYPECAT_COMPOSITE_DOMAIN)
! 				ereport(ERROR,
! 						(errcode(ERRCODE_DATATYPE_MISMATCH),
! 						 errmsg("first argument of %s must be a row type",
! 								funcname)));
! 		}
! 		else
! 		{
! 			/*
! 			 * json{b}_to_recordset case: result type is specified by calling
! 			 * query.  Here it is syntactically impossible to specify the
! 			 * target type as domain-over-composite.
! 			 */
! 			TupleDesc	tupdesc;
! 
! 			if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
! 				ereport(ERROR,
! 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 						 errmsg("function returning record called in context "
! 								"that cannot accept type record"),
! 						 errhint("Try calling the function in the FROM clause "
! 								 "using a column definition list.")));
! 
! 			Assert(tupdesc);
! 			cache->argtype = tupdesc->tdtypeid;
! 
! 			/* Save identified tupdesc */
! 			old_cxt = MemoryContextSwitchTo(cache->fn_mcxt);
! 			cache->c.io.composite.tupdesc = CreateTupleDescCopy(tupdesc);
! 			cache->c.io.composite.base_typid = tupdesc->tdtypeid;
! 			cache->c.io.composite.base_typmod = tupdesc->tdtypmod;
! 			MemoryContextSwitchTo(old_cxt);
! 		}
! 	}
! 
! 	/* Collect record arg if we have one */
! 	if (have_record_arg && !PG_ARGISNULL(0))
! 	{
! 		rec = PG_GETARG_HEAPTUPLEHEADER(0);
! 
! 		/*
! 		 * When declared arg type is RECORD, identify actual record type from
! 		 * the tuple itself.  Note the lookup_rowtype_tupdesc call in
! 		 * update_cached_tupdesc will fail if we're unable to do this.
! 		 */
! 		if (cache->argtype == RECORDOID)
! 			cache->c.io.composite.base_typmod = HeapTupleHeaderGetTypMod(rec);
! 	}
! 	else
! 		rec = NULL;
  
  	/* if the json is null send back an empty set */
  	if (PG_ARGISNULL(json_arg_num))
  		PG_RETURN_NULL();
  
  	state = palloc0(sizeof(PopulateRecordsetState));
  
! 	/* make tuplestore in a sufficiently long-lived memory context */
  	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
  	state->tuple_store = tuplestore_begin_heap(rsi->allowedModes &
  											   SFRM_Materialize_Random,
  											   false, work_mem);
  	MemoryContextSwitchTo(old_cxt);
  
  	state->function_name = funcname;
! 	state->cache = cache;
  	state->rec = rec;
  
  	if (jtype == JSONOID)
  	{
*************** populate_recordset_worker(FunctionCallIn
*** 3592,3598 ****
  	}
  
  	rsi->setResult = state->tuple_store;
! 	rsi->setDesc = state->ret_tdesc;
  
  	PG_RETURN_NULL();
  }
--- 3713,3719 ----
  	}
  
  	rsi->setResult = state->tuple_store;
! 	rsi->setDesc = cache->c.io.composite.tupdesc;
  
  	PG_RETURN_NULL();
  }
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 84759b6..b1e70a0 100644
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** get_name_for_var_field(Var *var, int fie
*** 6731,6747 ****
  
  	/*
  	 * If it's a Var of type RECORD, we have to find what the Var refers to;
! 	 * if not, we can use get_expr_result_type. If that fails, we try
! 	 * lookup_rowtype_tupdesc, which will probably fail too, but will ereport
! 	 * an acceptable message.
  	 */
  	if (!IsA(var, Var) ||
  		var->vartype != RECORDOID)
  	{
! 		if (get_expr_result_type((Node *) var, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
! 			tupleDesc = lookup_rowtype_tupdesc_copy(exprType((Node *) var),
! 													exprTypmod((Node *) var));
! 		Assert(tupleDesc);
  		/* Got the tupdesc, so we can extract the field name */
  		Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
  		return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
--- 6731,6742 ----
  
  	/*
  	 * If it's a Var of type RECORD, we have to find what the Var refers to;
! 	 * if not, we can use get_expr_result_tupdesc().
  	 */
  	if (!IsA(var, Var) ||
  		var->vartype != RECORDOID)
  	{
! 		tupleDesc = get_expr_result_tupdesc((Node *) var, false);
  		/* Got the tupdesc, so we can extract the field name */
  		Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
  		return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
*************** get_name_for_var_field(Var *var, int fie
*** 7044,7057 ****
  
  	/*
  	 * We now have an expression we can't expand any more, so see if
! 	 * get_expr_result_type() can do anything with it.  If not, pass to
! 	 * lookup_rowtype_tupdesc() which will probably fail, but will give an
! 	 * appropriate error message while failing.
  	 */
! 	if (get_expr_result_type(expr, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
! 		tupleDesc = lookup_rowtype_tupdesc_copy(exprType(expr),
! 												exprTypmod(expr));
! 	Assert(tupleDesc);
  	/* Got the tupdesc, so we can extract the field name */
  	Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
  	return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
--- 7039,7047 ----
  
  	/*
  	 * We now have an expression we can't expand any more, so see if
! 	 * get_expr_result_tupdesc() can do anything with it.
  	 */
! 	tupleDesc = get_expr_result_tupdesc(expr, false);
  	/* Got the tupdesc, so we can extract the field name */
  	Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
  	return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index b7a14dc..48961e3 100644
*** a/src/backend/utils/cache/lsyscache.c
--- b/src/backend/utils/cache/lsyscache.c
*************** get_typtype(Oid typid)
*** 2398,2409 ****
   * type_is_rowtype
   *
   *		Convenience function to determine whether a type OID represents
!  *		a "rowtype" type --- either RECORD or a named composite type.
   */
  bool
  type_is_rowtype(Oid typid)
  {
! 	return (typid == RECORDOID || get_typtype(typid) == TYPTYPE_COMPOSITE);
  }
  
  /*
--- 2398,2423 ----
   * type_is_rowtype
   *
   *		Convenience function to determine whether a type OID represents
!  *		a "rowtype" type --- either RECORD or a named composite type
!  *		(including a domain over a named composite type).
   */
  bool
  type_is_rowtype(Oid typid)
  {
! 	if (typid == RECORDOID)
! 		return true;			/* easy case */
! 	switch (get_typtype(typid))
! 	{
! 		case TYPTYPE_COMPOSITE:
! 			return true;
! 		case TYPTYPE_DOMAIN:
! 			if (get_typtype(getBaseType(typid)) == TYPTYPE_COMPOSITE)
! 				return true;
! 			break;
! 		default:
! 			break;
! 	}
! 	return false;
  }
  
  /*
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 61ce7dc..7aadc5d 100644
*** a/src/backend/utils/cache/typcache.c
--- b/src/backend/utils/cache/typcache.c
*************** static TypeCacheEntry *firstDomainTypeEn
*** 96,101 ****
--- 96,102 ----
  #define TCFLAGS_HAVE_FIELD_EQUALITY			0x004000
  #define TCFLAGS_HAVE_FIELD_COMPARE			0x008000
  #define TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS	0x010000
+ #define TCFLAGS_DOMAIN_BASE_IS_COMPOSITE	0x020000
  
  /*
   * Data stored about a domain type's constraints.  Note that we do not create
*************** lookup_type_cache(Oid type_id, int flags
*** 747,753 ****
  	/*
  	 * If requested, get information about a domain type
  	 */
! 	if ((flags & TYPECACHE_DOMAIN_INFO) &&
  		(typentry->flags & TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS) == 0 &&
  		typentry->typtype == TYPTYPE_DOMAIN)
  	{
--- 748,762 ----
  	/*
  	 * If requested, get information about a domain type
  	 */
! 	if ((flags & TYPECACHE_DOMAIN_BASE_INFO) &&
! 		typentry->domainBaseType == InvalidOid &&
! 		typentry->typtype == TYPTYPE_DOMAIN)
! 	{
! 		typentry->domainBaseTypmod = -1;
! 		typentry->domainBaseType =
! 			getBaseTypeAndTypmod(type_id, &typentry->domainBaseTypmod);
! 	}
! 	if ((flags & TYPECACHE_DOMAIN_CONSTR_INFO) &&
  		(typentry->flags & TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS) == 0 &&
  		typentry->typtype == TYPTYPE_DOMAIN)
  	{
*************** InitDomainConstraintRef(Oid type_id, Dom
*** 1166,1172 ****
  						MemoryContext refctx, bool need_exprstate)
  {
  	/* Look up the typcache entry --- we assume it survives indefinitely */
! 	ref->tcache = lookup_type_cache(type_id, TYPECACHE_DOMAIN_INFO);
  	ref->need_exprstate = need_exprstate;
  	/* For safety, establish the callback before acquiring a refcount */
  	ref->refctx = refctx;
--- 1175,1181 ----
  						MemoryContext refctx, bool need_exprstate)
  {
  	/* Look up the typcache entry --- we assume it survives indefinitely */
! 	ref->tcache = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
  	ref->need_exprstate = need_exprstate;
  	/* For safety, establish the callback before acquiring a refcount */
  	ref->refctx = refctx;
*************** DomainHasConstraints(Oid type_id)
*** 1257,1263 ****
  	 * Note: a side effect is to cause the typcache's domain data to become
  	 * valid.  This is fine since we'll likely need it soon if there is any.
  	 */
! 	typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_INFO);
  
  	return (typentry->domainData != NULL);
  }
--- 1266,1272 ----
  	 * Note: a side effect is to cause the typcache's domain data to become
  	 * valid.  This is fine since we'll likely need it soon if there is any.
  	 */
! 	typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
  
  	return (typentry->domainData != NULL);
  }
*************** cache_record_field_properties(TypeCacheE
*** 1405,1410 ****
--- 1414,1442 ----
  
  		DecrTupleDescRefCount(tupdesc);
  	}
+ 	else if (typentry->typtype == TYPTYPE_DOMAIN)
+ 	{
+ 		/* If it's domain over composite, copy base type's properties */
+ 		TypeCacheEntry *baseentry;
+ 
+ 		/* load up basetype info if we didn't already */
+ 		if (typentry->domainBaseType == InvalidOid)
+ 		{
+ 			typentry->domainBaseTypmod = -1;
+ 			typentry->domainBaseType =
+ 				getBaseTypeAndTypmod(typentry->type_id,
+ 									 &typentry->domainBaseTypmod);
+ 		}
+ 		baseentry = lookup_type_cache(typentry->domainBaseType,
+ 									  TYPECACHE_EQ_OPR |
+ 									  TYPECACHE_CMP_PROC);
+ 		if (baseentry->typtype == TYPTYPE_COMPOSITE)
+ 		{
+ 			typentry->flags |= TCFLAGS_DOMAIN_BASE_IS_COMPOSITE;
+ 			typentry->flags |= baseentry->flags & (TCFLAGS_HAVE_FIELD_EQUALITY |
+ 												   TCFLAGS_HAVE_FIELD_COMPARE);
+ 		}
+ 	}
  	typentry->flags |= TCFLAGS_CHECKED_FIELD_PROPERTIES;
  }
  
*************** lookup_rowtype_tupdesc_copy(Oid type_id,
*** 1619,1624 ****
--- 1651,1703 ----
  }
  
  /*
+  * lookup_rowtype_tupdesc_domain
+  *
+  * Same as lookup_rowtype_tupdesc_noerror(), except that the type can also be
+  * a domain over a named composite type; so this is effectively equivalent to
+  * lookup_rowtype_tupdesc_noerror(getBaseType(type_id), typmod, noError)
+  * except for being a tad faster.
+  *
+  * Note: the reason we don't fold the look-through-domain behavior into plain
+  * lookup_rowtype_tupdesc() is that we want callers to know they might be
+  * dealing with a domain.  Otherwise they might construct a tuple that should
+  * be of the domain type, but not apply domain constraints.
+  */
+ TupleDesc
+ lookup_rowtype_tupdesc_domain(Oid type_id, int32 typmod, bool noError)
+ {
+ 	TupleDesc	tupDesc;
+ 
+ 	if (type_id != RECORDOID)
+ 	{
+ 		/*
+ 		 * Check for domain or named composite type.  We might as well load
+ 		 * whichever data is needed.
+ 		 */
+ 		TypeCacheEntry *typentry;
+ 
+ 		typentry = lookup_type_cache(type_id,
+ 									 TYPECACHE_TUPDESC |
+ 									 TYPECACHE_DOMAIN_BASE_INFO);
+ 		if (typentry->typtype == TYPTYPE_DOMAIN)
+ 			return lookup_rowtype_tupdesc_noerror(typentry->domainBaseType,
+ 												  typentry->domainBaseTypmod,
+ 												  noError);
+ 		if (typentry->tupDesc == NULL && !noError)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("type %s is not composite",
+ 							format_type_be(type_id))));
+ 		tupDesc = typentry->tupDesc;
+ 	}
+ 	else
+ 		tupDesc = lookup_rowtype_tupdesc_internal(type_id, typmod, noError);
+ 	if (tupDesc != NULL)
+ 		PinTupleDesc(tupDesc);
+ 	return tupDesc;
+ }
+ 
+ /*
   * Hash function for the hash table of RecordCacheEntry.
   */
  static uint32
*************** TypeCacheRelCallback(Datum arg, Oid reli
*** 1929,1957 ****
  	hash_seq_init(&status, TypeCacheHash);
  	while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
  	{
! 		if (typentry->typtype != TYPTYPE_COMPOSITE)
! 			continue;			/* skip non-composites */
  
! 		/* Skip if no match, unless we're zapping all composite types */
! 		if (relid != typentry->typrelid && relid != InvalidOid)
! 			continue;
  
! 		/* Delete tupdesc if we have it */
! 		if (typentry->tupDesc != NULL)
  		{
  			/*
! 			 * Release our refcount, and free the tupdesc if none remain.
! 			 * (Can't use DecrTupleDescRefCount because this reference is not
! 			 * logged in current resource owner.)
  			 */
! 			Assert(typentry->tupDesc->tdrefcount > 0);
! 			if (--typentry->tupDesc->tdrefcount == 0)
! 				FreeTupleDesc(typentry->tupDesc);
! 			typentry->tupDesc = NULL;
  		}
- 
- 		/* Reset equality/comparison/hashing validity information */
- 		typentry->flags = 0;
  	}
  }
  
--- 2008,2047 ----
  	hash_seq_init(&status, TypeCacheHash);
  	while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
  	{
! 		if (typentry->typtype == TYPTYPE_COMPOSITE)
! 		{
! 			/* Skip if no match, unless we're zapping all composite types */
! 			if (relid != typentry->typrelid && relid != InvalidOid)
! 				continue;
  
! 			/* Delete tupdesc if we have it */
! 			if (typentry->tupDesc != NULL)
! 			{
! 				/*
! 				 * Release our refcount, and free the tupdesc if none remain.
! 				 * (Can't use DecrTupleDescRefCount because this reference is
! 				 * not logged in current resource owner.)
! 				 */
! 				Assert(typentry->tupDesc->tdrefcount > 0);
! 				if (--typentry->tupDesc->tdrefcount == 0)
! 					FreeTupleDesc(typentry->tupDesc);
! 				typentry->tupDesc = NULL;
! 			}
  
! 			/* Reset equality/comparison/hashing validity information */
! 			typentry->flags = 0;
! 		}
! 		else if (typentry->typtype == TYPTYPE_DOMAIN)
  		{
  			/*
! 			 * If it's domain over composite, reset flags.  (We don't bother
! 			 * trying to determine whether the specific base type needs a
! 			 * reset.)  Note that if we haven't determined whether the base
! 			 * type is composite, we don't need to reset anything.
  			 */
! 			if (typentry->flags & TCFLAGS_DOMAIN_BASE_IS_COMPOSITE)
! 				typentry->flags = 0;
  		}
  	}
  }
  
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 9c3f451..f4a3cce 100644
*** a/src/backend/utils/fmgr/funcapi.c
--- b/src/backend/utils/fmgr/funcapi.c
*************** static TypeFuncClass internal_get_result
*** 39,45 ****
  static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
  							oidvector *declared_args,
  							Node *call_expr);
! static TypeFuncClass get_type_func_class(Oid typid);
  
  
  /*
--- 39,45 ----
  static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
  							oidvector *declared_args,
  							Node *call_expr);
! static TypeFuncClass get_type_func_class(Oid typid, Oid *base_typeid);
  
  
  /*
*************** get_expr_result_type(Node *expr,
*** 246,259 ****
  	{
  		/* handle as a generic expression; no chance to resolve RECORD */
  		Oid			typid = exprType(expr);
  
  		if (resultTypeId)
  			*resultTypeId = typid;
  		if (resultTupleDesc)
  			*resultTupleDesc = NULL;
! 		result = get_type_func_class(typid);
! 		if (result == TYPEFUNC_COMPOSITE && resultTupleDesc)
! 			*resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, -1);
  	}
  
  	return result;
--- 246,262 ----
  	{
  		/* handle as a generic expression; no chance to resolve RECORD */
  		Oid			typid = exprType(expr);
+ 		Oid			base_typid;
  
  		if (resultTypeId)
  			*resultTypeId = typid;
  		if (resultTupleDesc)
  			*resultTupleDesc = NULL;
! 		result = get_type_func_class(typid, &base_typid);
! 		if ((result == TYPEFUNC_COMPOSITE ||
! 			 result == TYPEFUNC_COMPOSITE_DOMAIN) &&
! 			resultTupleDesc)
! 			*resultTupleDesc = lookup_rowtype_tupdesc_copy(base_typid, -1);
  	}
  
  	return result;
*************** internal_get_result_type(Oid funcid,
*** 296,301 ****
--- 299,305 ----
  	HeapTuple	tp;
  	Form_pg_proc procform;
  	Oid			rettype;
+ 	Oid			base_rettype;
  	TupleDesc	tupdesc;
  
  	/* First fetch the function's pg_proc row to inspect its rettype */
*************** internal_get_result_type(Oid funcid,
*** 363,374 ****
  		*resultTupleDesc = NULL;	/* default result */
  
  	/* Classify the result type */
! 	result = get_type_func_class(rettype);
  	switch (result)
  	{
  		case TYPEFUNC_COMPOSITE:
  			if (resultTupleDesc)
! 				*resultTupleDesc = lookup_rowtype_tupdesc_copy(rettype, -1);
  			/* Named composite types can't have any polymorphic columns */
  			break;
  		case TYPEFUNC_SCALAR:
--- 367,379 ----
  		*resultTupleDesc = NULL;	/* default result */
  
  	/* Classify the result type */
! 	result = get_type_func_class(rettype, &base_rettype);
  	switch (result)
  	{
  		case TYPEFUNC_COMPOSITE:
+ 		case TYPEFUNC_COMPOSITE_DOMAIN:
  			if (resultTupleDesc)
! 				*resultTupleDesc = lookup_rowtype_tupdesc_copy(base_rettype, -1);
  			/* Named composite types can't have any polymorphic columns */
  			break;
  		case TYPEFUNC_SCALAR:
*************** internal_get_result_type(Oid funcid,
*** 394,399 ****
--- 399,444 ----
  }
  
  /*
+  * get_expr_result_tupdesc
+  *		Get a tupdesc describing the result of a composite-valued expression
+  *
+  * If expression is not composite or rowtype can't be determined, returns NULL
+  * if noError is true, else throws error.
+  *
+  * This is a simpler version of get_expr_result_type() for use when the caller
+  * is only interested in determinate rowtype results.
+  */
+ TupleDesc
+ get_expr_result_tupdesc(Node *expr, bool noError)
+ {
+ 	TupleDesc	tupleDesc;
+ 	TypeFuncClass functypclass;
+ 
+ 	functypclass = get_expr_result_type(expr, NULL, &tupleDesc);
+ 
+ 	if (functypclass == TYPEFUNC_COMPOSITE ||
+ 		functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
+ 		return tupleDesc;
+ 
+ 	if (!noError)
+ 	{
+ 		Oid			exprTypeId = exprType(expr);
+ 
+ 		if (exprTypeId != RECORDOID)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("type %s is not composite",
+ 							format_type_be(exprTypeId))));
+ 		else
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("record type has not been registered")));
+ 	}
+ 
+ 	return NULL;
+ }
+ 
+ /*
   * Given the result tuple descriptor for a function with OUT parameters,
   * replace any polymorphic columns (ANYELEMENT etc) with correct data types
   * deduced from the input arguments. Returns TRUE if able to deduce all types,
*************** resolve_polymorphic_argtypes(int numargs
*** 741,763 ****
  /*
   * get_type_func_class
   *		Given the type OID, obtain its TYPEFUNC classification.
   *
   * This is intended to centralize a bunch of formerly ad-hoc code for
   * classifying types.  The categories used here are useful for deciding
   * how to handle functions returning the datatype.
   */
  static TypeFuncClass
! get_type_func_class(Oid typid)
  {
  	switch (get_typtype(typid))
  	{
  		case TYPTYPE_COMPOSITE:
  			return TYPEFUNC_COMPOSITE;
  		case TYPTYPE_BASE:
- 		case TYPTYPE_DOMAIN:
  		case TYPTYPE_ENUM:
  		case TYPTYPE_RANGE:
  			return TYPEFUNC_SCALAR;
  		case TYPTYPE_PSEUDO:
  			if (typid == RECORDOID)
  				return TYPEFUNC_RECORD;
--- 786,816 ----
  /*
   * get_type_func_class
   *		Given the type OID, obtain its TYPEFUNC classification.
+  *		Also, if it's a domain, return the base type OID.
   *
   * This is intended to centralize a bunch of formerly ad-hoc code for
   * classifying types.  The categories used here are useful for deciding
   * how to handle functions returning the datatype.
   */
  static TypeFuncClass
! get_type_func_class(Oid typid, Oid *base_typeid)
  {
+ 	*base_typeid = typid;
+ 
  	switch (get_typtype(typid))
  	{
  		case TYPTYPE_COMPOSITE:
  			return TYPEFUNC_COMPOSITE;
  		case TYPTYPE_BASE:
  		case TYPTYPE_ENUM:
  		case TYPTYPE_RANGE:
  			return TYPEFUNC_SCALAR;
+ 		case TYPTYPE_DOMAIN:
+ 			*base_typeid = typid = getBaseType(typid);
+ 			if (get_typtype(typid) == TYPTYPE_COMPOSITE)
+ 				return TYPEFUNC_COMPOSITE_DOMAIN;
+ 			else				/* domain base type can't be a pseudotype */
+ 				return TYPEFUNC_SCALAR;
  		case TYPTYPE_PSEUDO:
  			if (typid == RECORDOID)
  				return TYPEFUNC_RECORD;
*************** RelationNameGetTupleDesc(const char *rel
*** 1320,1335 ****
  TupleDesc
  TypeGetTupleDesc(Oid typeoid, List *colaliases)
  {
! 	TypeFuncClass functypclass = get_type_func_class(typeoid);
  	TupleDesc	tupdesc = NULL;
  
  	/*
! 	 * Build a suitable tupledesc representing the output rows
  	 */
  	if (functypclass == TYPEFUNC_COMPOSITE)
  	{
  		/* Composite data type, e.g. a table's row type */
! 		tupdesc = lookup_rowtype_tupdesc_copy(typeoid, -1);
  
  		if (colaliases != NIL)
  		{
--- 1373,1392 ----
  TupleDesc
  TypeGetTupleDesc(Oid typeoid, List *colaliases)
  {
! 	Oid			base_typeoid;
! 	TypeFuncClass functypclass = get_type_func_class(typeoid, &base_typeoid);
  	TupleDesc	tupdesc = NULL;
  
  	/*
! 	 * Build a suitable tupledesc representing the output rows.  We
! 	 * intentionally do not support TYPEFUNC_COMPOSITE_DOMAIN here, as it's
! 	 * unlikely that legacy callers of this obsolete function would be
! 	 * prepared to apply domain constraints.
  	 */
  	if (functypclass == TYPEFUNC_COMPOSITE)
  	{
  		/* Composite data type, e.g. a table's row type */
! 		tupdesc = lookup_rowtype_tupdesc_copy(base_typeoid, -1);
  
  		if (colaliases != NIL)
  		{
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index fa04a63..b0d4c54 100644
*** a/src/include/access/htup_details.h
--- b/src/include/access/htup_details.h
*************** typedef struct DatumTupleFields
*** 134,139 ****
--- 134,144 ----
  	Oid			datum_typeid;	/* composite type OID, or RECORDOID */
  
  	/*
+ 	 * datum_typeid cannot be a domain over composite, only plain composite,
+ 	 * even if the datum is meant as a value of a domain-over-composite type.
+ 	 * This is in line with the general principle that CoerceToDomain does not
+ 	 * change the physical representation of the base type value.
+ 	 *
  	 * Note: field ordering is chosen with thought that Oid might someday
  	 * widen to 64 bits.
  	 */
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index c15610e..2be5af1 100644
*** a/src/include/access/tupdesc.h
--- b/src/include/access/tupdesc.h
*************** typedef struct tupleConstr
*** 60,65 ****
--- 60,71 ----
   * row type, or a value >= 0 to allow the rowtype to be looked up in the
   * typcache.c type cache.
   *
+  * Note that tdtypeid is never the OID of a domain over composite, even if
+  * we are dealing with values that are known (at some higher level) to be of
+  * a domain-over-composite type.  This is because tdtypeid/tdtypmod need to
+  * match up with the type labeling of composite Datums, and those are never
+  * explicitly marked as being of a domain type, either.
+  *
   * Tuple descriptors that live in caches (relcache or typcache, at present)
   * are reference-counted: they can be deleted when their reference count goes
   * to zero.  Tuple descriptors created by the executor need no reference
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index 951af2a..7c52de9 100644
*** a/src/include/funcapi.h
--- b/src/include/funcapi.h
*************** typedef struct FuncCallContext
*** 143,148 ****
--- 143,152 ----
   *		get_call_result_type.  Note: the cases in which rowtypes cannot be
   *		determined are different from the cases for get_call_result_type.
   *		Do *not* use this if you can use one of the others.
+  *
+  * See also get_expr_result_tupdesc(), which is a convenient wrapper around
+  * get_expr_result_type() for use when the caller only cares about
+  * determinable-rowtype cases.
   *----------
   */
  
*************** typedef enum TypeFuncClass
*** 151,156 ****
--- 155,161 ----
  {
  	TYPEFUNC_SCALAR,			/* scalar result type */
  	TYPEFUNC_COMPOSITE,			/* determinable rowtype result */
+ 	TYPEFUNC_COMPOSITE_DOMAIN,	/* domain over determinable rowtype result */
  	TYPEFUNC_RECORD,			/* indeterminate rowtype result */
  	TYPEFUNC_OTHER				/* bogus type, eg pseudotype */
  } TypeFuncClass;
*************** extern TypeFuncClass get_func_result_typ
*** 165,170 ****
--- 170,177 ----
  					 Oid *resultTypeId,
  					 TupleDesc *resultTupleDesc);
  
+ extern TupleDesc get_expr_result_tupdesc(Node *expr, bool noError);
+ 
  extern bool resolve_polymorphic_argtypes(int numargs, Oid *argtypes,
  							 char *argmodes,
  							 Node *call_expr);
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index ccb5123..c2929ac 100644
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
*************** typedef struct Var
*** 166,172 ****
  	Index		varno;			/* index of this var's relation in the range
  								 * table, or INNER_VAR/OUTER_VAR/INDEX_VAR */
  	AttrNumber	varattno;		/* attribute number of this var, or zero for
! 								 * all */
  	Oid			vartype;		/* pg_type OID for the type of this var */
  	int32		vartypmod;		/* pg_attribute typmod value */
  	Oid			varcollid;		/* OID of collation, or InvalidOid if none */
--- 166,172 ----
  	Index		varno;			/* index of this var's relation in the range
  								 * table, or INNER_VAR/OUTER_VAR/INDEX_VAR */
  	AttrNumber	varattno;		/* attribute number of this var, or zero for
! 								 * all attrs ("whole-row Var") */
  	Oid			vartype;		/* pg_type OID for the type of this var */
  	int32		vartypmod;		/* pg_attribute typmod value */
  	Oid			varcollid;		/* OID of collation, or InvalidOid if none */
*************** typedef struct FieldSelect
*** 755,760 ****
--- 755,763 ----
   * the assign case of ArrayRef, this is used to implement UPDATE of a
   * portion of a column.
   *
+  * resulttype is always a named composite type (not a domain).  To update
+  * a composite domain value, apply CoerceToDomain to the FieldStore.
+  *
   * A single FieldStore can actually represent updates of several different
   * fields.  The parser only generates FieldStores with single-element lists,
   * but the planner will collapse multiple updates of the same base column
*************** typedef struct ArrayCoerceExpr
*** 849,855 ****
   * needed for the destination type plus possibly others; the columns need not
   * be in the same positions, but are matched up by name.  This is primarily
   * used to convert a whole-row value of an inheritance child table into a
!  * valid whole-row value of its parent table's rowtype.
   * ----------------
   */
  
--- 852,859 ----
   * needed for the destination type plus possibly others; the columns need not
   * be in the same positions, but are matched up by name.  This is primarily
   * used to convert a whole-row value of an inheritance child table into a
!  * valid whole-row value of its parent table's rowtype.  Both resulttype
!  * and the exposed type of "arg" must be named composite types (not domains).
   * ----------------
   */
  
*************** typedef struct RowExpr
*** 987,992 ****
--- 991,999 ----
  	Oid			row_typeid;		/* RECORDOID or a composite type's ID */
  
  	/*
+ 	 * row_typeid cannot be a domain over composite, only plain composite.  To
+ 	 * create a composite domain value, apply CoerceToDomain to the RowExpr.
+ 	 *
  	 * Note: we deliberately do NOT store a typmod.  Although a typmod will be
  	 * associated with specific RECORD types at runtime, it will differ for
  	 * different backends, and so cannot safely be stored in stored
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 7b843d0..af1e314 100644
*** a/src/include/parser/parse_type.h
--- b/src/include/parser/parse_type.h
*************** extern Oid	typeTypeCollation(Type typ);
*** 46,55 ****
  extern Datum stringTypeDatum(Type tp, char *string, int32 atttypmod);
  
  extern Oid	typeidTypeRelid(Oid type_id);
  
  extern TypeName *typeStringToTypeName(const char *str);
  extern void parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p, bool missing_ok);
  
! #define ISCOMPLEX(typeid) (typeidTypeRelid(typeid) != InvalidOid)
  
  #endif							/* PARSE_TYPE_H */
--- 46,57 ----
  extern Datum stringTypeDatum(Type tp, char *string, int32 atttypmod);
  
  extern Oid	typeidTypeRelid(Oid type_id);
+ extern Oid	typeOrDomainTypeRelid(Oid type_id);
  
  extern TypeName *typeStringToTypeName(const char *str);
  extern void parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p, bool missing_ok);
  
! /* true if typeid is composite, or domain over composite, but not RECORD */
! #define ISCOMPLEX(typeid) (typeOrDomainTypeRelid(typeid) != InvalidOid)
  
  #endif							/* PARSE_TYPE_H */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 41b645a..ea799a8 100644
*** a/src/include/utils/typcache.h
--- b/src/include/utils/typcache.h
*************** typedef struct TypeCacheEntry
*** 92,97 ****
--- 92,104 ----
  	FmgrInfo	rng_subdiff_finfo;	/* difference function, if any */
  
  	/*
+ 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
+ 	 * domain, or if information hasn't been requested.
+ 	 */
+ 	Oid			domainBaseType;
+ 	int32		domainBaseTypmod;
+ 
+ 	/*
  	 * Domain constraint data if it's a domain type.  NULL if not domain, or
  	 * if domain has no constraints, or if information hasn't been requested.
  	 */
*************** typedef struct TypeCacheEntry
*** 123,131 ****
  #define TYPECACHE_BTREE_OPFAMILY	0x0200
  #define TYPECACHE_HASH_OPFAMILY		0x0400
  #define TYPECACHE_RANGE_INFO		0x0800
! #define TYPECACHE_DOMAIN_INFO		0x1000
! #define TYPECACHE_HASH_EXTENDED_PROC		0x2000
! #define TYPECACHE_HASH_EXTENDED_PROC_FINFO	0x4000
  
  /*
   * Callers wishing to maintain a long-lived reference to a domain's constraint
--- 130,139 ----
  #define TYPECACHE_BTREE_OPFAMILY	0x0200
  #define TYPECACHE_HASH_OPFAMILY		0x0400
  #define TYPECACHE_RANGE_INFO		0x0800
! #define TYPECACHE_DOMAIN_BASE_INFO			0x1000
! #define TYPECACHE_DOMAIN_CONSTR_INFO		0x2000
! #define TYPECACHE_HASH_EXTENDED_PROC		0x4000
! #define TYPECACHE_HASH_EXTENDED_PROC_FINFO	0x8000
  
  /*
   * Callers wishing to maintain a long-lived reference to a domain's constraint
*************** extern TupleDesc lookup_rowtype_tupdesc_
*** 163,168 ****
--- 171,179 ----
  
  extern TupleDesc lookup_rowtype_tupdesc_copy(Oid type_id, int32 typmod);
  
+ extern TupleDesc lookup_rowtype_tupdesc_domain(Oid type_id, int32 typmod,
+ 							  bool noError);
+ 
  extern void assign_record_type_typmod(TupleDesc tupDesc);
  
  extern int	compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 1e62c57..f7f3948 100644
*** a/src/test/regress/expected/domain.out
--- b/src/test/regress/expected/domain.out
*************** select pg_typeof('{1,2,3}'::dia || 42); 
*** 198,203 ****
--- 198,291 ----
  (1 row)
  
  drop domain dia;
+ -- Test domains over composites
+ create type comptype as (r float8, i float8);
+ create domain dcomptype as comptype;
+ create table dcomptable (d1 dcomptype unique);
+ insert into dcomptable values (row(1,2)::dcomptype);
+ insert into dcomptable values (row(3,4)::comptype);
+ insert into dcomptable values (row(1,2)::dcomptype);  -- fail on uniqueness
+ ERROR:  duplicate key value violates unique constraint "dcomptable_d1_key"
+ DETAIL:  Key (d1)=((1,2)) already exists.
+ insert into dcomptable (d1.r) values(11);
+ select * from dcomptable;
+   d1   
+ -------
+  (1,2)
+  (3,4)
+  (11,)
+ (3 rows)
+ 
+ select (d1).r, (d1).i, (d1).* from dcomptable;
+  r  | i | r  | i 
+ ----+---+----+---
+   1 | 2 |  1 | 2
+   3 | 4 |  3 | 4
+  11 |   | 11 |  
+ (3 rows)
+ 
+ update dcomptable set d1.r = (d1).r + 1 where (d1).i > 0;
+ select * from dcomptable;
+   d1   
+ -------
+  (11,)
+  (2,2)
+  (4,4)
+ (3 rows)
+ 
+ alter domain dcomptype add constraint c1 check ((value).r <= (value).i);
+ alter domain dcomptype add constraint c2 check ((value).r > (value).i);  -- fail
+ ERROR:  column "d1" of table "dcomptable" contains values that violate the new constraint
+ select row(2,1)::dcomptype;  -- fail
+ ERROR:  value for domain dcomptype violates check constraint "c1"
+ insert into dcomptable values (row(1,2)::comptype);
+ insert into dcomptable values (row(2,1)::comptype);  -- fail
+ ERROR:  value for domain dcomptype violates check constraint "c1"
+ insert into dcomptable (d1.r) values(99);
+ insert into dcomptable (d1.r, d1.i) values(99, 100);
+ insert into dcomptable (d1.r, d1.i) values(100, 99);  -- fail
+ ERROR:  value for domain dcomptype violates check constraint "c1"
+ update dcomptable set d1.r = (d1).r + 1 where (d1).i > 0;  -- fail
+ ERROR:  value for domain dcomptype violates check constraint "c1"
+ update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
+ select * from dcomptable;
+     d1    
+ ----------
+  (11,)
+  (99,)
+  (1,3)
+  (3,5)
+  (0,3)
+  (98,101)
+ (6 rows)
+ 
+ explain (verbose, costs off)
+   update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
+                                           QUERY PLAN                                           
+ -----------------------------------------------------------------------------------------------
+  Update on public.dcomptable
+    ->  Seq Scan on public.dcomptable
+          Output: ROW(((d1).r - '1'::double precision), ((d1).i + '1'::double precision)), ctid
+          Filter: ((dcomptable.d1).i > '0'::double precision)
+ (4 rows)
+ 
+ create rule silly as on delete to dcomptable do instead
+   update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
+ \d+ dcomptable
+                                   Table "public.dcomptable"
+  Column |   Type    | Collation | Nullable | Default | Storage  | Stats target | Description 
+ --------+-----------+-----------+----------+---------+----------+--------------+-------------
+  d1     | dcomptype |           |          |         | extended |              | 
+ Indexes:
+     "dcomptable_d1_key" UNIQUE CONSTRAINT, btree (d1)
+ Rules:
+     silly AS
+     ON DELETE TO dcomptable DO INSTEAD  UPDATE dcomptable SET d1.r = (dcomptable.d1).r - 1::double precision, d1.i = (dcomptable.d1).i + 1::double precision
+   WHERE (dcomptable.d1).i > 0::double precision
+ 
+ drop table dcomptable;
+ drop type comptype cascade;
+ NOTICE:  drop cascades to type dcomptype
  -- Test domains over arrays of composite
  create type comptype as (r float8, i float8);
  create domain dcomptypea as comptype[];
*************** insert into ddtest2 values('{(-1)}');
*** 762,767 ****
--- 850,863 ----
  alter domain posint add constraint c1 check(value >= 0);
  ERROR:  cannot alter type "posint" because column "ddtest2.f1" uses it
  drop table ddtest2;
+ -- Likewise for domains within domains over composite
+ create domain ddtest1d as ddtest1;
+ create table ddtest2(f1 ddtest1d);
+ insert into ddtest2 values('(-1)');
+ alter domain posint add constraint c1 check(value >= 0);
+ ERROR:  cannot alter type "posint" because column "ddtest2.f1" uses it
+ drop table ddtest2;
+ drop domain ddtest1d;
  -- Likewise for domains within domains over array of composite
  create domain ddtest1d as ddtest1[];
  create table ddtest2(f1 ddtest1d);
diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out
index d7abae9..f57d1ab 100644
*** a/src/test/regress/expected/json.out
--- b/src/test/regress/expected/json.out
*************** create type jpop as (a text, b int, c ti
*** 1316,1321 ****
--- 1316,1323 ----
  CREATE DOMAIN js_int_not_null  AS int     NOT NULL;
  CREATE DOMAIN js_int_array_1d  AS int[]   CHECK(array_length(VALUE, 1) = 3);
  CREATE DOMAIN js_int_array_2d  AS int[][] CHECK(array_length(VALUE, 2) = 3);
+ create type j_unordered_pair as (x int, y int);
+ create domain j_ordered_pair as j_unordered_pair check((value).x <= (value).y);
  CREATE TYPE jsrec AS (
  	i	int,
  	ia	_int4,
*************** SELECT rec FROM json_populate_record(
*** 1740,1745 ****
--- 1742,1771 ----
   (abc,3,"Thu Jan 02 00:00:00 2003")
  (1 row)
  
+ -- anonymous record type
+ SELECT json_populate_record(null::record, '{"x": 0, "y": 1}');
+ ERROR:  record type has not been registered
+ SELECT json_populate_record(row(1,2), '{"f1": 0, "f2": 1}');
+  json_populate_record 
+ ----------------------
+  (0,1)
+ (1 row)
+ 
+ -- composite domain
+ SELECT json_populate_record(null::j_ordered_pair, '{"x": 0, "y": 1}');
+  json_populate_record 
+ ----------------------
+  (0,1)
+ (1 row)
+ 
+ SELECT json_populate_record(row(1,2)::j_ordered_pair, '{"x": 0}');
+  json_populate_record 
+ ----------------------
+  (0,2)
+ (1 row)
+ 
+ SELECT json_populate_record(row(1,2)::j_ordered_pair, '{"x": 1, "y": 0}');
+ ERROR:  value for domain j_ordered_pair violates check constraint "j_ordered_pair_check"
  -- populate_recordset
  select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
     a    | b |            c             
*************** select * from json_populate_recordset(ro
*** 1806,1811 ****
--- 1832,1862 ----
   {"z":true}    |  3 | Fri Jan 20 10:42:53 2012
  (2 rows)
  
+ -- anonymous record type
+ SELECT json_populate_recordset(null::record, '[{"x": 0, "y": 1}]');
+ ERROR:  record type has not been registered
+ SELECT json_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]');
+  json_populate_recordset 
+ -------------------------
+  (0,1)
+ (1 row)
+ 
+ -- composite domain
+ SELECT json_populate_recordset(null::j_ordered_pair, '[{"x": 0, "y": 1}]');
+  json_populate_recordset 
+ -------------------------
+  (0,1)
+ (1 row)
+ 
+ SELECT json_populate_recordset(row(1,2)::j_ordered_pair, '[{"x": 0}, {"y": 3}]');
+  json_populate_recordset 
+ -------------------------
+  (0,2)
+  (1,3)
+ (2 rows)
+ 
+ SELECT json_populate_recordset(row(1,2)::j_ordered_pair, '[{"x": 1, "y": 0}]');
+ ERROR:  value for domain j_ordered_pair violates check constraint "j_ordered_pair_check"
  -- test type info caching in json_populate_record()
  CREATE TEMP TABLE jspoptest (js json);
  INSERT INTO jspoptest
*************** DROP TYPE jsrec_i_not_null;
*** 1828,1833 ****
--- 1879,1886 ----
  DROP DOMAIN js_int_not_null;
  DROP DOMAIN js_int_array_1d;
  DROP DOMAIN js_int_array_2d;
+ DROP DOMAIN j_ordered_pair;
+ DROP TYPE j_unordered_pair;
  --json_typeof() function
  select value, json_typeof(value)
    from (values (json '123.4'),
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index dcea6a4..7f40ced 100644
*** a/src/test/regress/expected/jsonb.out
--- b/src/test/regress/expected/jsonb.out
*************** CREATE TYPE jbpop AS (a text, b int, c t
*** 1900,1905 ****
--- 1900,1907 ----
  CREATE DOMAIN jsb_int_not_null  AS int     NOT NULL;
  CREATE DOMAIN jsb_int_array_1d  AS int[]   CHECK(array_length(VALUE, 1) = 3);
  CREATE DOMAIN jsb_int_array_2d  AS int[][] CHECK(array_length(VALUE, 2) = 3);
+ create type jb_unordered_pair as (x int, y int);
+ create domain jb_ordered_pair as jb_unordered_pair check((value).x <= (value).y);
  CREATE TYPE jsbrec AS (
  	i	int,
  	ia	_int4,
*************** SELECT rec FROM jsonb_populate_record(
*** 2324,2329 ****
--- 2326,2355 ----
   (abc,3,"Thu Jan 02 00:00:00 2003")
  (1 row)
  
+ -- anonymous record type
+ SELECT jsonb_populate_record(null::record, '{"x": 0, "y": 1}');
+ ERROR:  record type has not been registered
+ SELECT jsonb_populate_record(row(1,2), '{"f1": 0, "f2": 1}');
+  jsonb_populate_record 
+ -----------------------
+  (0,1)
+ (1 row)
+ 
+ -- composite domain
+ SELECT jsonb_populate_record(null::jb_ordered_pair, '{"x": 0, "y": 1}');
+  jsonb_populate_record 
+ -----------------------
+  (0,1)
+ (1 row)
+ 
+ SELECT jsonb_populate_record(row(1,2)::jb_ordered_pair, '{"x": 0}');
+  jsonb_populate_record 
+ -----------------------
+  (0,2)
+ (1 row)
+ 
+ SELECT jsonb_populate_record(row(1,2)::jb_ordered_pair, '{"x": 1, "y": 0}');
+ ERROR:  value for domain jb_ordered_pair violates check constraint "jb_ordered_pair_check"
  -- populate_recordset
  SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
     a    | b |            c             
*************** SELECT * FROM jsonb_populate_recordset(r
*** 2383,2388 ****
--- 2409,2439 ----
   {"z": true}     |  3 | Fri Jan 20 10:42:53 2012
  (2 rows)
  
+ -- anonymous record type
+ SELECT jsonb_populate_recordset(null::record, '[{"x": 0, "y": 1}]');
+ ERROR:  record type has not been registered
+ SELECT jsonb_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]');
+  jsonb_populate_recordset 
+ --------------------------
+  (0,1)
+ (1 row)
+ 
+ -- composite domain
+ SELECT jsonb_populate_recordset(null::jb_ordered_pair, '[{"x": 0, "y": 1}]');
+  jsonb_populate_recordset 
+ --------------------------
+  (0,1)
+ (1 row)
+ 
+ SELECT jsonb_populate_recordset(row(1,2)::jb_ordered_pair, '[{"x": 0}, {"y": 3}]');
+  jsonb_populate_recordset 
+ --------------------------
+  (0,2)
+  (1,3)
+ (2 rows)
+ 
+ SELECT jsonb_populate_recordset(row(1,2)::jb_ordered_pair, '[{"x": 1, "y": 0}]');
+ ERROR:  value for domain jb_ordered_pair violates check constraint "jb_ordered_pair_check"
  -- jsonb_to_record and jsonb_to_recordset
  select * from jsonb_to_record('{"a":1,"b":"foo","c":"bar"}')
      as x(a int, b text, d text);
*************** DROP TYPE jsbrec_i_not_null;
*** 2482,2487 ****
--- 2533,2540 ----
  DROP DOMAIN jsb_int_not_null;
  DROP DOMAIN jsb_int_array_1d;
  DROP DOMAIN jsb_int_array_2d;
+ DROP DOMAIN jb_ordered_pair;
+ DROP TYPE jb_unordered_pair;
  -- indexing
  SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
   count 
diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql
index 8fb3e20..5201f00 100644
*** a/src/test/regress/sql/domain.sql
--- b/src/test/regress/sql/domain.sql
*************** select pg_typeof('{1,2,3}'::dia || 42); 
*** 120,125 ****
--- 120,164 ----
  drop domain dia;
  
  
+ -- Test domains over composites
+ 
+ create type comptype as (r float8, i float8);
+ create domain dcomptype as comptype;
+ create table dcomptable (d1 dcomptype unique);
+ 
+ insert into dcomptable values (row(1,2)::dcomptype);
+ insert into dcomptable values (row(3,4)::comptype);
+ insert into dcomptable values (row(1,2)::dcomptype);  -- fail on uniqueness
+ insert into dcomptable (d1.r) values(11);
+ 
+ select * from dcomptable;
+ select (d1).r, (d1).i, (d1).* from dcomptable;
+ update dcomptable set d1.r = (d1).r + 1 where (d1).i > 0;
+ select * from dcomptable;
+ 
+ alter domain dcomptype add constraint c1 check ((value).r <= (value).i);
+ alter domain dcomptype add constraint c2 check ((value).r > (value).i);  -- fail
+ 
+ select row(2,1)::dcomptype;  -- fail
+ insert into dcomptable values (row(1,2)::comptype);
+ insert into dcomptable values (row(2,1)::comptype);  -- fail
+ insert into dcomptable (d1.r) values(99);
+ insert into dcomptable (d1.r, d1.i) values(99, 100);
+ insert into dcomptable (d1.r, d1.i) values(100, 99);  -- fail
+ update dcomptable set d1.r = (d1).r + 1 where (d1).i > 0;  -- fail
+ update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
+ select * from dcomptable;
+ 
+ explain (verbose, costs off)
+   update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
+ create rule silly as on delete to dcomptable do instead
+   update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
+ \d+ dcomptable
+ 
+ drop table dcomptable;
+ drop type comptype cascade;
+ 
+ 
  -- Test domains over arrays of composite
  
  create type comptype as (r float8, i float8);
*************** insert into ddtest2 values('{(-1)}');
*** 500,505 ****
--- 539,552 ----
  alter domain posint add constraint c1 check(value >= 0);
  drop table ddtest2;
  
+ -- Likewise for domains within domains over composite
+ create domain ddtest1d as ddtest1;
+ create table ddtest2(f1 ddtest1d);
+ insert into ddtest2 values('(-1)');
+ alter domain posint add constraint c1 check(value >= 0);
+ drop table ddtest2;
+ drop domain ddtest1d;
+ 
  -- Likewise for domains within domains over array of composite
  create domain ddtest1d as ddtest1[];
  create table ddtest2(f1 ddtest1d);
diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql
index 506e3a8..0859353 100644
*** a/src/test/regress/sql/json.sql
--- b/src/test/regress/sql/json.sql
*************** CREATE DOMAIN js_int_not_null  AS int   
*** 388,393 ****
--- 388,396 ----
  CREATE DOMAIN js_int_array_1d  AS int[]   CHECK(array_length(VALUE, 1) = 3);
  CREATE DOMAIN js_int_array_2d  AS int[][] CHECK(array_length(VALUE, 2) = 3);
  
+ create type j_unordered_pair as (x int, y int);
+ create domain j_ordered_pair as j_unordered_pair check((value).x <= (value).y);
+ 
  CREATE TYPE jsrec AS (
  	i	int,
  	ia	_int4,
*************** SELECT rec FROM json_populate_record(
*** 516,521 ****
--- 519,533 ----
  	'{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}'
  ) q;
  
+ -- anonymous record type
+ SELECT json_populate_record(null::record, '{"x": 0, "y": 1}');
+ SELECT json_populate_record(row(1,2), '{"f1": 0, "f2": 1}');
+ 
+ -- composite domain
+ SELECT json_populate_record(null::j_ordered_pair, '{"x": 0, "y": 1}');
+ SELECT json_populate_record(row(1,2)::j_ordered_pair, '{"x": 0}');
+ SELECT json_populate_record(row(1,2)::j_ordered_pair, '{"x": 1, "y": 0}');
+ 
  -- populate_recordset
  
  select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
*************** select * from json_populate_recordset(nu
*** 532,537 ****
--- 544,558 ----
  select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
  select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
  
+ -- anonymous record type
+ SELECT json_populate_recordset(null::record, '[{"x": 0, "y": 1}]');
+ SELECT json_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]');
+ 
+ -- composite domain
+ SELECT json_populate_recordset(null::j_ordered_pair, '[{"x": 0, "y": 1}]');
+ SELECT json_populate_recordset(row(1,2)::j_ordered_pair, '[{"x": 0}, {"y": 3}]');
+ SELECT json_populate_recordset(row(1,2)::j_ordered_pair, '[{"x": 1, "y": 0}]');
+ 
  -- test type info caching in json_populate_record()
  CREATE TEMP TABLE jspoptest (js json);
  
*************** DROP TYPE jsrec_i_not_null;
*** 550,555 ****
--- 571,578 ----
  DROP DOMAIN js_int_not_null;
  DROP DOMAIN js_int_array_1d;
  DROP DOMAIN js_int_array_2d;
+ DROP DOMAIN j_ordered_pair;
+ DROP TYPE j_unordered_pair;
  
  --json_typeof() function
  select value, json_typeof(value)
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 57fff3b..9cad707 100644
*** a/src/test/regress/sql/jsonb.sql
--- b/src/test/regress/sql/jsonb.sql
*************** CREATE DOMAIN jsb_int_not_null  AS int  
*** 488,493 ****
--- 488,496 ----
  CREATE DOMAIN jsb_int_array_1d  AS int[]   CHECK(array_length(VALUE, 1) = 3);
  CREATE DOMAIN jsb_int_array_2d  AS int[][] CHECK(array_length(VALUE, 2) = 3);
  
+ create type jb_unordered_pair as (x int, y int);
+ create domain jb_ordered_pair as jb_unordered_pair check((value).x <= (value).y);
+ 
  CREATE TYPE jsbrec AS (
  	i	int,
  	ia	_int4,
*************** SELECT rec FROM jsonb_populate_record(
*** 616,621 ****
--- 619,633 ----
  	'{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}'
  ) q;
  
+ -- anonymous record type
+ SELECT jsonb_populate_record(null::record, '{"x": 0, "y": 1}');
+ SELECT jsonb_populate_record(row(1,2), '{"f1": 0, "f2": 1}');
+ 
+ -- composite domain
+ SELECT jsonb_populate_record(null::jb_ordered_pair, '{"x": 0, "y": 1}');
+ SELECT jsonb_populate_record(row(1,2)::jb_ordered_pair, '{"x": 0}');
+ SELECT jsonb_populate_record(row(1,2)::jb_ordered_pair, '{"x": 1, "y": 0}');
+ 
  -- populate_recordset
  SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
  SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
*************** SELECT * FROM jsonb_populate_recordset(N
*** 628,633 ****
--- 640,654 ----
  SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
  SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
  
+ -- anonymous record type
+ SELECT jsonb_populate_recordset(null::record, '[{"x": 0, "y": 1}]');
+ SELECT jsonb_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]');
+ 
+ -- composite domain
+ SELECT jsonb_populate_recordset(null::jb_ordered_pair, '[{"x": 0, "y": 1}]');
+ SELECT jsonb_populate_recordset(row(1,2)::jb_ordered_pair, '[{"x": 0}, {"y": 3}]');
+ SELECT jsonb_populate_recordset(row(1,2)::jb_ordered_pair, '[{"x": 1, "y": 0}]');
+ 
  -- jsonb_to_record and jsonb_to_recordset
  
  select * from jsonb_to_record('{"a":1,"b":"foo","c":"bar"}')
*************** DROP TYPE jsbrec_i_not_null;
*** 673,678 ****
--- 694,701 ----
  DROP DOMAIN jsb_int_not_null;
  DROP DOMAIN jsb_int_array_1d;
  DROP DOMAIN jsb_int_array_2d;
+ DROP DOMAIN jb_ordered_pair;
+ DROP TYPE jb_unordered_pair;
  
  -- indexing
  SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
