Re: [HACKERS] Domains and arrays and composites, oh my

Поиск
Список
Период
Сортировка
От Tom Lane
Тема Re: [HACKERS] Domains and arrays and composites, oh my
Дата
Msg-id 3734.1508876357@sss.pgh.pa.us
обсуждение исходный текст
Ответ на Re: [HACKERS] Domains and arrays and composites, oh my  (Tom Lane <tgl@sss.pgh.pa.us>)
Список pgsql-hackers
I wrote:
> Anyway, PFA an updated patch that also fixes some conflicts with the
> already-committed arrays-of-domains patch.

I realized that the pending patch for jsonb_build_object doesn't
actually have any conflict with what I needed to touch here, so
I went ahead and fixed the JSON functions that needed fixing,
along with hstore's populate_record.  I ended up rewriting the
argument-metadata-collection portions of populate_record_worker
and populate_recordset_worker rather heavily, because I didn't
like them at all: aside from not working for domains over composite,
they were pretty inefficient (redoing a lot of work on each call
for no good reason) and they were randomly different from each
other, resulting in json{b}_populate_recordset rejecting some cases
that worked in json{b}_populate_record.

I've also updated the documentation.

I think that this patch version is done so far as the core code
and contrib are concerned.  The PLs need varying amounts of work,
but as I said earlier, I think it would be better to tackle those
in separate patches instead of continuing to enlarge the footprint
of the core patch.  So, barring objection, I'd like to go ahead
and commit this.

            regards, tom lane

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 — 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 — 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
       — 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}';

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

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

Предыдущее
От: Justin Pryzby
Дата:
Сообщение: Re: [HACKERS] unique index violation after pg_upgrade to PG10
Следующее
От: Masahiko Sawada
Дата:
Сообщение: Re: [HACKERS] Transactions involving multiple postgres foreign servers