Re: [HACKERS] Arrays of domains

Поиск
Список
Период
Сортировка
От Tom Lane
Тема Re: [HACKERS] Arrays of domains
Дата
Msg-id 3881.1502471872@sss.pgh.pa.us
обсуждение исходный текст
Ответ на [HACKERS] Arrays of domains  (Tom Lane <tgl@sss.pgh.pa.us>)
Ответы Re: [HACKERS] Arrays of domains  (Tom Lane <tgl@sss.pgh.pa.us>)
Re: [HACKERS] Arrays of domains  (Andrew Dunstan <andrew.dunstan@2ndquadrant.com>)
Список pgsql-hackers
I wrote:
> Probably a better answer is to start supporting arrays over domain
> types.  That was left unimplemented in the original domains patch,
> but AFAICS not for any better reason than lack of round tuits.

Attached is a patch series that allows us to create arrays of domain
types.  I haven't tested this in great detail, so there might be some
additional corners of the system that need work, but it passes basic
sanity checks.  I believe it's independent of the other patch I have
in the commitfest for domains over composites, but I haven't tested
for interactions there either.

01-rationalize-coercion-APIs.patch cleans up the APIs of
coerce_to_domain() and some internal functions in parse_coerce.c so that
we consistently pass around a CoercionContext along with CoercionForm.
Previously, we sometimes passed an "isExplicit" boolean flag instead,
which is strictly less information; and coerce_to_domain() didn't even get
that, but instead had to reverse-engineer isExplicit from CoercionForm.
That's contrary to the documentation in primnodes.h that says that
CoercionForm only affects display and not semantics.  I don't think this
change fixes any live bugs, but it makes things more consistent.  The
main reason for doing it though is that now build_coercion_expression()
receives ccontext, which it needs in order to be able to recursively
invoke coerce_to_target_type(), as required by the next patch.

02-reimplement-ArrayCoerceExpr.patch is the core of the patch.  It changes
ArrayCoerceExpr so that the node does not directly know any details of
what has to be done to the individual array elements while performing the
array coercion.  Instead, the per-element processing is represented by
a sub-expression whose input is a source array element and whose output
is a target array element.  This simplifies life in parse_coerce.c,
because it can build that sub-expression by a recursive invocation of
coerce_to_target_type(), and it allows the executor to handle the
per-element processing as a compiled expression instead of hard-wired
code.  This is probably about a wash or a small loss performance-wise
for the simplest case where we just need to invoke one coercion function
for each element.  However, there are many cases where the existing code
ends up generating two nested ArrayCoerceExprs, one to do the type
conversion and one to apply a typmod (length) coercion function.  In the
new code there will be just one ArrayCoerceExpr, saving one deconstruction
and reconstruction of the array.  If I hadn't done it like this, adding
domains into the mix could have produced as many as three
ArrayCoerceExprs, where the top one would have only checked domain
constraints; that did not sound nice performance-wise, and it would have
required a lot of code duplication as well.

Finally, 03-support-arrays-of-domains.patch simply turns on the spigot
by creating an array type in DefineDomain(), and adds some test cases.
Given the new method of handling ArrayCoerceExpr, everything Just Works.

I'll add this to the next commitfest.

            regards, tom lane

diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index afc733f..277ca8b 100644
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
*************** expand_targetlist(List *tlist, int comma
*** 306,314 ****
                          new_expr = coerce_to_domain(new_expr,
                                                      InvalidOid, -1,
                                                      atttype,
                                                      COERCE_IMPLICIT_CAST,
                                                      -1,
-                                                     false,
                                                      false);
                      }
                      else
--- 306,314 ----
                          new_expr = coerce_to_domain(new_expr,
                                                      InvalidOid, -1,
                                                      atttype,
+                                                     COERCION_IMPLICIT,
                                                      COERCE_IMPLICIT_CAST,
                                                      -1,
                                                      false);
                      }
                      else
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 0bc7dba..c406cea 100644
*** a/src/backend/parser/parse_coerce.c
--- b/src/backend/parser/parse_coerce.c
***************
*** 34,48 ****

  static Node *coerce_type_typmod(Node *node,
                     Oid targetTypeId, int32 targetTypMod,
!                    CoercionForm cformat, int location,
!                    bool isExplicit, bool hideInputCoercion);
  static void hide_coercion_node(Node *node);
  static Node *build_coercion_expression(Node *node,
                            CoercionPathType pathtype,
                            Oid funcId,
                            Oid targetTypeId, int32 targetTypMod,
!                           CoercionForm cformat, int location,
!                           bool isExplicit);
  static Node *coerce_record_to_complex(ParseState *pstate, Node *node,
                           Oid targetTypeId,
                           CoercionContext ccontext,
--- 34,49 ----

  static Node *coerce_type_typmod(Node *node,
                     Oid targetTypeId, int32 targetTypMod,
!                    CoercionContext ccontext, CoercionForm cformat,
!                    int location,
!                    bool hideInputCoercion);
  static void hide_coercion_node(Node *node);
  static Node *build_coercion_expression(Node *node,
                            CoercionPathType pathtype,
                            Oid funcId,
                            Oid targetTypeId, int32 targetTypMod,
!                           CoercionContext ccontext, CoercionForm cformat,
!                           int location);
  static Node *coerce_record_to_complex(ParseState *pstate, Node *node,
                           Oid targetTypeId,
                           CoercionContext ccontext,
*************** coerce_to_target_type(ParseState *pstate
*** 110,117 ****
       */
      result = coerce_type_typmod(result,
                                  targettype, targettypmod,
!                                 cformat, location,
!                                 (cformat != COERCE_IMPLICIT_CAST),
                                  (result != expr && !IsA(result, Const)));

      if (expr != origexpr)
--- 111,117 ----
       */
      result = coerce_type_typmod(result,
                                  targettype, targettypmod,
!                                 ccontext, cformat, location,
                                  (result != expr && !IsA(result, Const)));

      if (expr != origexpr)
*************** coerce_type(ParseState *pstate, Node *no
*** 355,361 ****
              result = coerce_to_domain(result,
                                        baseTypeId, baseTypeMod,
                                        targetTypeId,
!                                       cformat, location, false, false);

          ReleaseSysCache(baseType);

--- 355,362 ----
              result = coerce_to_domain(result,
                                        baseTypeId, baseTypeMod,
                                        targetTypeId,
!                                       ccontext, cformat, location,
!                                       false);

          ReleaseSysCache(baseType);

*************** coerce_type(ParseState *pstate, Node *no
*** 417,436 ****

              result = build_coercion_expression(node, pathtype, funcId,
                                                 baseTypeId, baseTypeMod,
!                                                cformat, location,
!                                                (cformat != COERCE_IMPLICIT_CAST));

              /*
               * If domain, coerce to the domain type and relabel with domain
!              * type ID.  We can skip the internal length-coercion step if the
!              * selected coercion function was a type-and-length coercion.
               */
              if (targetTypeId != baseTypeId)
                  result = coerce_to_domain(result, baseTypeId, baseTypeMod,
                                            targetTypeId,
!                                           cformat, location, true,
!                                           exprIsLengthCoercion(result,
!                                                                NULL));
          }
          else
          {
--- 418,434 ----

              result = build_coercion_expression(node, pathtype, funcId,
                                                 baseTypeId, baseTypeMod,
!                                                ccontext, cformat, location);

              /*
               * If domain, coerce to the domain type and relabel with domain
!              * type ID, hiding the previous coercion node.
               */
              if (targetTypeId != baseTypeId)
                  result = coerce_to_domain(result, baseTypeId, baseTypeMod,
                                            targetTypeId,
!                                           ccontext, cformat, location,
!                                           true);
          }
          else
          {
*************** coerce_type(ParseState *pstate, Node *no
*** 444,450 ****
               * then we won't need a RelabelType node.
               */
              result = coerce_to_domain(node, InvalidOid, -1, targetTypeId,
!                                       cformat, location, false, false);
              if (result == node)
              {
                  /*
--- 442,449 ----
               * then we won't need a RelabelType node.
               */
              result = coerce_to_domain(node, InvalidOid, -1, targetTypeId,
!                                       ccontext, cformat, location,
!                                       false);
              if (result == node)
              {
                  /*
*************** can_coerce_type(int nargs, Oid *input_ty
*** 636,654 ****
   * 'baseTypeMod': base type typmod of domain, if known (pass -1 if caller
   *        has not bothered to look this up)
   * 'typeId': target type to coerce to
!  * 'cformat': coercion format
   * 'location': coercion request location
   * 'hideInputCoercion': if true, hide the input coercion under this one.
-  * 'lengthCoercionDone': if true, caller already accounted for length,
-  *        ie the input is already of baseTypMod as well as baseTypeId.
   *
   * If the target type isn't a domain, the given 'arg' is returned as-is.
   */
  Node *
  coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId,
!                  CoercionForm cformat, int location,
!                  bool hideInputCoercion,
!                  bool lengthCoercionDone)
  {
      CoerceToDomain *result;

--- 635,651 ----
   * 'baseTypeMod': base type typmod of domain, if known (pass -1 if caller
   *        has not bothered to look this up)
   * 'typeId': target type to coerce to
!  * 'ccontext': context indicator to control coercions
!  * 'cformat': coercion display format
   * 'location': coercion request location
   * 'hideInputCoercion': if true, hide the input coercion under this one.
   *
   * If the target type isn't a domain, the given 'arg' is returned as-is.
   */
  Node *
  coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId,
!                  CoercionContext ccontext, CoercionForm cformat, int location,
!                  bool hideInputCoercion)
  {
      CoerceToDomain *result;

*************** coerce_to_domain(Node *arg, Oid baseType
*** 677,690 ****
       * would be safe to do anyway, without lots of knowledge about what the
       * base type thinks the typmod means.
       */
!     if (!lengthCoercionDone)
!     {
!         if (baseTypeMod >= 0)
!             arg = coerce_type_typmod(arg, baseTypeId, baseTypeMod,
!                                      COERCE_IMPLICIT_CAST, location,
!                                      (cformat != COERCE_IMPLICIT_CAST),
!                                      false);
!     }

      /*
       * Now build the domain coercion node.  This represents run-time checking
--- 674,682 ----
       * would be safe to do anyway, without lots of knowledge about what the
       * base type thinks the typmod means.
       */
!     arg = coerce_type_typmod(arg, baseTypeId, baseTypeMod,
!                              ccontext, COERCE_IMPLICIT_CAST, location,
!                              false);

      /*
       * Now build the domain coercion node.  This represents run-time checking
*************** coerce_to_domain(Node *arg, Oid baseType
*** 714,724 ****
   * The caller must have already ensured that the value is of the correct
   * type, typically by applying coerce_type.
   *
!  * cformat determines the display properties of the generated node (if any),
!  * while isExplicit may affect semantics.  If hideInputCoercion is true
!  * *and* we generate a node, the input node is forced to IMPLICIT display
!  * form, so that only the typmod coercion node will be visible when
!  * displaying the expression.
   *
   * NOTE: this does not need to work on domain types, because any typmod
   * coercion for a domain is considered to be part of the type coercion
--- 706,719 ----
   * The caller must have already ensured that the value is of the correct
   * type, typically by applying coerce_type.
   *
!  * ccontext may affect semantics, depending on whether the length coercion
!  * function pays attention to the isExplicit flag it's passed.
!  *
!  * cformat determines the display properties of the generated node (if any).
!  *
!  * If hideInputCoercion is true *and* we generate a node, the input node is
!  * forced to IMPLICIT display form, so that only the typmod coercion node will
!  * be visible when displaying the expression.
   *
   * NOTE: this does not need to work on domain types, because any typmod
   * coercion for a domain is considered to be part of the type coercion
*************** coerce_to_domain(Node *arg, Oid baseType
*** 726,733 ****
   */
  static Node *
  coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod,
!                    CoercionForm cformat, int location,
!                    bool isExplicit, bool hideInputCoercion)
  {
      CoercionPathType pathtype;
      Oid            funcId;
--- 721,729 ----
   */
  static Node *
  coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod,
!                    CoercionContext ccontext, CoercionForm cformat,
!                    int location,
!                    bool hideInputCoercion)
  {
      CoercionPathType pathtype;
      Oid            funcId;
*************** coerce_type_typmod(Node *node, Oid targe
*** 749,756 ****

          node = build_coercion_expression(node, pathtype, funcId,
                                           targetTypeId, targetTypMod,
!                                          cformat, location,
!                                          isExplicit);
      }

      return node;
--- 745,751 ----

          node = build_coercion_expression(node, pathtype, funcId,
                                           targetTypeId, targetTypMod,
!                                          ccontext, cformat, location);
      }

      return node;
*************** build_coercion_expression(Node *node,
*** 799,806 ****
                            CoercionPathType pathtype,
                            Oid funcId,
                            Oid targetTypeId, int32 targetTypMod,
!                           CoercionForm cformat, int location,
!                           bool isExplicit)
  {
      int            nargs = 0;

--- 794,801 ----
                            CoercionPathType pathtype,
                            Oid funcId,
                            Oid targetTypeId, int32 targetTypMod,
!                           CoercionContext ccontext, CoercionForm cformat,
!                           int location)
  {
      int            nargs = 0;

*************** build_coercion_expression(Node *node,
*** 865,871 ****
                               -1,
                               InvalidOid,
                               sizeof(bool),
!                              BoolGetDatum(isExplicit),
                               false,
                               true);

--- 860,866 ----
                               -1,
                               InvalidOid,
                               sizeof(bool),
!                              BoolGetDatum(ccontext == COERCION_EXPLICIT),
                               false,
                               true);

*************** build_coercion_expression(Node *node,
*** 893,899 ****
           */
          acoerce->resulttypmod = (nargs >= 2) ? targetTypMod : -1;
          /* resultcollid will be set by parse_collate.c */
!         acoerce->isExplicit = isExplicit;
          acoerce->coerceformat = cformat;
          acoerce->location = location;

--- 888,894 ----
           */
          acoerce->resulttypmod = (nargs >= 2) ? targetTypMod : -1;
          /* resultcollid will be set by parse_collate.c */
!         acoerce->isExplicit = (ccontext == COERCION_EXPLICIT);
          acoerce->coerceformat = cformat;
          acoerce->location = location;

diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 6b79c69..a2841d2 100644
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
*************** rewriteTargetListIU(List *targetList,
*** 875,883 ****
                      new_expr = coerce_to_domain(new_expr,
                                                  InvalidOid, -1,
                                                  att_tup->atttypid,
                                                  COERCE_IMPLICIT_CAST,
                                                  -1,
-                                                 false,
                                                  false);
                  }
              }
--- 875,883 ----
                      new_expr = coerce_to_domain(new_expr,
                                                  InvalidOid, -1,
                                                  att_tup->atttypid,
+                                                 COERCION_IMPLICIT,
                                                  COERCE_IMPLICIT_CAST,
                                                  -1,
                                                  false);
                  }
              }
*************** rewriteValuesRTE(RangeTblEntry *rte, Rel
*** 1271,1279 ****
                      new_expr = coerce_to_domain(new_expr,
                                                  InvalidOid, -1,
                                                  att_tup->atttypid,
                                                  COERCE_IMPLICIT_CAST,
                                                  -1,
-                                                 false,
                                                  false);
                  }
                  newList = lappend(newList, new_expr);
--- 1271,1279 ----
                      new_expr = coerce_to_domain(new_expr,
                                                  InvalidOid, -1,
                                                  att_tup->atttypid,
+                                                 COERCION_IMPLICIT,
                                                  COERCE_IMPLICIT_CAST,
                                                  -1,
                                                  false);
                  }
                  newList = lappend(newList, new_expr);
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index ba706b2..d962ae3 100644
*** a/src/backend/rewrite/rewriteManip.c
--- b/src/backend/rewrite/rewriteManip.c
*************** ReplaceVarsFromTargetList_callback(Var *
*** 1429,1437 ****
                                                                 var->varcollid),
                                          InvalidOid, -1,
                                          var->vartype,
                                          COERCE_IMPLICIT_CAST,
                                          -1,
-                                         false,
                                          false);
          }
          elog(ERROR, "could not find replacement targetlist entry for attno %d",
--- 1429,1437 ----
                                                                 var->varcollid),
                                          InvalidOid, -1,
                                          var->vartype,
+                                         COERCION_IMPLICIT,
                                          COERCE_IMPLICIT_CAST,
                                          -1,
                                          false);
          }
          elog(ERROR, "could not find replacement targetlist entry for attno %d",
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index 06f6529..e560f0c 100644
*** a/src/include/parser/parse_coerce.h
--- b/src/include/parser/parse_coerce.h
*************** extern Node *coerce_type(ParseState *pst
*** 48,56 ****
              CoercionContext ccontext, CoercionForm cformat, int location);
  extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod,
                   Oid typeId,
!                  CoercionForm cformat, int location,
!                  bool hideInputCoercion,
!                  bool lengthCoercionDone);

  extern Node *coerce_to_boolean(ParseState *pstate, Node *node,
                    const char *constructName);
--- 48,55 ----
              CoercionContext ccontext, CoercionForm cformat, int location);
  extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod,
                   Oid typeId,
!                  CoercionContext ccontext, CoercionForm cformat, int location,
!                  bool hideInputCoercion);

  extern Node *coerce_to_boolean(ParseState *pstate, Node *node,
                    const char *constructName);
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index fa409d7..1776730 100644
*** a/contrib/pg_stat_statements/pg_stat_statements.c
--- b/contrib/pg_stat_statements/pg_stat_statements.c
*************** JumbleExpr(pgssJumbleState *jstate, Node
*** 2631,2636 ****
--- 2631,2637 ----

                  APP_JUMB(acexpr->resulttype);
                  JumbleExpr(jstate, (Node *) acexpr->arg);
+                 JumbleExpr(jstate, (Node *) acexpr->elemexpr);
              }
              break;
          case T_ConvertRowtypeExpr:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 6fffc29..2668650 100644
*** a/src/backend/catalog/dependency.c
--- b/src/backend/catalog/dependency.c
*************** find_expr_references_walker(Node *node,
*** 1738,1748 ****
      {
          ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;

!         if (OidIsValid(acoerce->elemfuncid))
!             add_object_address(OCLASS_PROC, acoerce->elemfuncid, 0,
!                                context->addrs);
          add_object_address(OCLASS_TYPE, acoerce->resulttype, 0,
                             context->addrs);
          /* fall through to examine arguments */
      }
      else if (IsA(node, ConvertRowtypeExpr))
--- 1738,1751 ----
      {
          ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;

!         /* as above, depend on type */
          add_object_address(OCLASS_TYPE, acoerce->resulttype, 0,
                             context->addrs);
+         /* the collation might not be referenced anywhere else, either */
+         if (OidIsValid(acoerce->resultcollid) &&
+             acoerce->resultcollid != DEFAULT_COLLATION_OID)
+             add_object_address(OCLASS_COLLATION, acoerce->resultcollid, 0,
+                                context->addrs);
          /* fall through to examine arguments */
      }
      else if (IsA(node, ConvertRowtypeExpr))
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 7496189..6337bfa 100644
*** a/src/backend/executor/execExpr.c
--- b/src/backend/executor/execExpr.c
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1225,1230 ****
--- 1225,1231 ----
              {
                  ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
                  Oid            resultelemtype;
+                 ExprState  *elemstate;

                  /* evaluate argument into step's result area */
                  ExecInitExprRec(acoerce->arg, parent, state, resv, resnull);
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 1234,1275 ****
                      ereport(ERROR,
                              (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                               errmsg("target type is not an array")));
-                 /* Arrays over domains aren't supported yet */
-                 Assert(getBaseType(resultelemtype) == resultelemtype);

!                 scratch.opcode = EEOP_ARRAYCOERCE;
!                 scratch.d.arraycoerce.coerceexpr = acoerce;
!                 scratch.d.arraycoerce.resultelemtype = resultelemtype;

!                 if (OidIsValid(acoerce->elemfuncid))
!                 {
!                     AclResult    aclresult;

!                     /* Check permission to call function */
!                     aclresult = pg_proc_aclcheck(acoerce->elemfuncid,
!                                                  GetUserId(),
!                                                  ACL_EXECUTE);
!                     if (aclresult != ACLCHECK_OK)
!                         aclcheck_error(aclresult, ACL_KIND_PROC,
!                                        get_func_name(acoerce->elemfuncid));
!                     InvokeFunctionExecuteHook(acoerce->elemfuncid);

!                     /* Set up the primary fmgr lookup information */
!                     scratch.d.arraycoerce.elemfunc =
!                         (FmgrInfo *) palloc0(sizeof(FmgrInfo));
!                     fmgr_info(acoerce->elemfuncid,
!                               scratch.d.arraycoerce.elemfunc);
!                     fmgr_info_set_expr((Node *) acoerce,
!                                        scratch.d.arraycoerce.elemfunc);

                      /* Set up workspace for array_map */
                      scratch.d.arraycoerce.amstate =
                          (ArrayMapState *) palloc0(sizeof(ArrayMapState));
                  }
                  else
                  {
!                     /* Don't need workspace if there's no conversion func */
!                     scratch.d.arraycoerce.elemfunc = NULL;
                      scratch.d.arraycoerce.amstate = NULL;
                  }

--- 1235,1283 ----
                      ereport(ERROR,
                              (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                               errmsg("target type is not an array")));

!                 /*
!                  * Construct a sub-expression for the per-element expression;
!                  * but don't ready it until after we check it for triviality.
!                  * We assume it hasn't any Var references, but does have a
!                  * CaseTestExpr representing the source array element values.
!                  */
!                 elemstate = makeNode(ExprState);
!                 elemstate->expr = acoerce->elemexpr;
!                 elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
!                 elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));

!                 ExecInitExprRec(acoerce->elemexpr, parent, elemstate,
!                                 &elemstate->resvalue, &elemstate->resnull);

!                 if (elemstate->steps_len == 1 &&
!                     elemstate->steps[0].opcode == EEOP_CASE_TESTVAL)
!                 {
!                     /* Trivial, so we need no per-element work at runtime */
!                     elemstate = NULL;
!                 }
!                 else
!                 {
!                     /* Not trivial, so append a DONE step */
!                     scratch.opcode = EEOP_DONE;
!                     ExprEvalPushStep(elemstate, &scratch);
!                     /* and ready the subexpression */
!                     ExecReadyExpr(elemstate);
!                 }

!                 scratch.opcode = EEOP_ARRAYCOERCE;
!                 scratch.d.arraycoerce.elemexprstate = elemstate;
!                 scratch.d.arraycoerce.resultelemtype = resultelemtype;

+                 if (elemstate)
+                 {
                      /* Set up workspace for array_map */
                      scratch.d.arraycoerce.amstate =
                          (ArrayMapState *) palloc0(sizeof(ArrayMapState));
                  }
                  else
                  {
!                     /* Don't need workspace if there's no subexpression */
                      scratch.d.arraycoerce.amstate = NULL;
                  }

diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index f2a52f6..98fa93e 100644
*** a/src/backend/executor/execExprInterp.c
--- b/src/backend/executor/execExprInterp.c
*************** ExecInterpExpr(ExprState *state, ExprCon
*** 1252,1258 ****
          EEO_CASE(EEOP_ARRAYCOERCE)
          {
              /* too complex for an inline implementation */
!             ExecEvalArrayCoerce(state, op);

              EEO_NEXT();
          }
--- 1252,1258 ----
          EEO_CASE(EEOP_ARRAYCOERCE)
          {
              /* too complex for an inline implementation */
!             ExecEvalArrayCoerce(state, op, econtext);

              EEO_NEXT();
          }
*************** ExecEvalArrayExpr(ExprState *state, Expr
*** 2328,2338 ****
   * Source array is in step's result variable.
   */
  void
! ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op)
  {
-     ArrayCoerceExpr *acoerce = op->d.arraycoerce.coerceexpr;
      Datum        arraydatum;
-     FunctionCallInfoData locfcinfo;

      /* NULL array -> NULL result */
      if (*op->resnull)
--- 2328,2336 ----
   * Source array is in step's result variable.
   */
  void
! ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  {
      Datum        arraydatum;

      /* NULL array -> NULL result */
      if (*op->resnull)
*************** ExecEvalArrayCoerce(ExprState *state, Ex
*** 2344,2350 ****
       * If it's binary-compatible, modify the element type in the array header,
       * but otherwise leave the array as we received it.
       */
!     if (!OidIsValid(acoerce->elemfuncid))
      {
          /* Detoast input array if necessary, and copy in any case */
          ArrayType  *array = DatumGetArrayTypePCopy(arraydatum);
--- 2342,2348 ----
       * If it's binary-compatible, modify the element type in the array header,
       * but otherwise leave the array as we received it.
       */
!     if (op->d.arraycoerce.elemexprstate == NULL)
      {
          /* Detoast input array if necessary, and copy in any case */
          ArrayType  *array = DatumGetArrayTypePCopy(arraydatum);
*************** ExecEvalArrayCoerce(ExprState *state, Ex
*** 2355,2377 ****
      }

      /*
!      * Use array_map to apply the function to each array element.
!      *
!      * We pass on the desttypmod and isExplicit flags whether or not the
!      * function wants them.
!      *
!      * Note: coercion functions are assumed to not use collation.
       */
!     InitFunctionCallInfoData(locfcinfo, op->d.arraycoerce.elemfunc, 3,
!                              InvalidOid, NULL, NULL);
!     locfcinfo.arg[0] = arraydatum;
!     locfcinfo.arg[1] = Int32GetDatum(acoerce->resulttypmod);
!     locfcinfo.arg[2] = BoolGetDatum(acoerce->isExplicit);
!     locfcinfo.argnull[0] = false;
!     locfcinfo.argnull[1] = false;
!     locfcinfo.argnull[2] = false;
!
!     *op->resvalue = array_map(&locfcinfo, op->d.arraycoerce.resultelemtype,
                                op->d.arraycoerce.amstate);
  }

--- 2353,2364 ----
      }

      /*
!      * Use array_map to apply the sub-expression to each array element.
       */
!     *op->resvalue = array_map(arraydatum,
!                               op->d.arraycoerce.elemexprstate,
!                               econtext,
!                               op->d.arraycoerce.resultelemtype,
                                op->d.arraycoerce.amstate);
  }

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 45a04b0..9655f4a 100644
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyArrayCoerceExpr(const ArrayCoerceEx
*** 1696,1706 ****
      ArrayCoerceExpr *newnode = makeNode(ArrayCoerceExpr);

      COPY_NODE_FIELD(arg);
!     COPY_SCALAR_FIELD(elemfuncid);
      COPY_SCALAR_FIELD(resulttype);
      COPY_SCALAR_FIELD(resulttypmod);
      COPY_SCALAR_FIELD(resultcollid);
-     COPY_SCALAR_FIELD(isExplicit);
      COPY_SCALAR_FIELD(coerceformat);
      COPY_LOCATION_FIELD(location);

--- 1696,1705 ----
      ArrayCoerceExpr *newnode = makeNode(ArrayCoerceExpr);

      COPY_NODE_FIELD(arg);
!     COPY_NODE_FIELD(elemexpr);
      COPY_SCALAR_FIELD(resulttype);
      COPY_SCALAR_FIELD(resulttypmod);
      COPY_SCALAR_FIELD(resultcollid);
      COPY_SCALAR_FIELD(coerceformat);
      COPY_LOCATION_FIELD(location);

diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03..9fdc887 100644
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** static bool
*** 513,523 ****
  _equalArrayCoerceExpr(const ArrayCoerceExpr *a, const ArrayCoerceExpr *b)
  {
      COMPARE_NODE_FIELD(arg);
!     COMPARE_SCALAR_FIELD(elemfuncid);
      COMPARE_SCALAR_FIELD(resulttype);
      COMPARE_SCALAR_FIELD(resulttypmod);
      COMPARE_SCALAR_FIELD(resultcollid);
-     COMPARE_SCALAR_FIELD(isExplicit);
      COMPARE_COERCIONFORM_FIELD(coerceformat);
      COMPARE_LOCATION_FIELD(location);

--- 513,522 ----
  _equalArrayCoerceExpr(const ArrayCoerceExpr *a, const ArrayCoerceExpr *b)
  {
      COMPARE_NODE_FIELD(arg);
!     COMPARE_NODE_FIELD(elemexpr);
      COMPARE_SCALAR_FIELD(resulttype);
      COMPARE_SCALAR_FIELD(resulttypmod);
      COMPARE_SCALAR_FIELD(resultcollid);
      COMPARE_COERCIONFORM_FIELD(coerceformat);
      COMPARE_LOCATION_FIELD(location);

diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index e3eb0c5..8e6f27e 100644
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
*************** check_functions_in_node(Node *node, chec
*** 1717,1731 ****
                      return true;
              }
              break;
-         case T_ArrayCoerceExpr:
-             {
-                 ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node;
-
-                 if (OidIsValid(expr->elemfuncid) &&
-                     checker(expr->elemfuncid, context))
-                     return true;
-             }
-             break;
          case T_RowCompareExpr:
              {
                  RowCompareExpr *rcexpr = (RowCompareExpr *) node;
--- 1717,1722 ----
*************** expression_tree_walker(Node *node,
*** 2023,2029 ****
          case T_CoerceViaIO:
              return walker(((CoerceViaIO *) node)->arg, context);
          case T_ArrayCoerceExpr:
!             return walker(((ArrayCoerceExpr *) node)->arg, context);
          case T_ConvertRowtypeExpr:
              return walker(((ConvertRowtypeExpr *) node)->arg, context);
          case T_CollateExpr:
--- 2014,2028 ----
          case T_CoerceViaIO:
              return walker(((CoerceViaIO *) node)->arg, context);
          case T_ArrayCoerceExpr:
!             {
!                 ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
!
!                 if (walker(acoerce->arg, context))
!                     return true;
!                 if (walker(acoerce->elemexpr, context))
!                     return true;
!             }
!             break;
          case T_ConvertRowtypeExpr:
              return walker(((ConvertRowtypeExpr *) node)->arg, context);
          case T_CollateExpr:
*************** expression_tree_mutator(Node *node,
*** 2705,2710 ****
--- 2704,2710 ----

                  FLATCOPY(newnode, acoerce, ArrayCoerceExpr);
                  MUTATE(newnode->arg, acoerce->arg, Expr *);
+                 MUTATE(newnode->elemexpr, acoerce->elemexpr, Expr *);
                  return (Node *) newnode;
              }
              break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 379d92a..21cdf90 100644
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outArrayCoerceExpr(StringInfo str, cons
*** 1392,1402 ****
      WRITE_NODE_TYPE("ARRAYCOERCEEXPR");

      WRITE_NODE_FIELD(arg);
!     WRITE_OID_FIELD(elemfuncid);
      WRITE_OID_FIELD(resulttype);
      WRITE_INT_FIELD(resulttypmod);
      WRITE_OID_FIELD(resultcollid);
-     WRITE_BOOL_FIELD(isExplicit);
      WRITE_ENUM_FIELD(coerceformat, CoercionForm);
      WRITE_LOCATION_FIELD(location);
  }
--- 1392,1401 ----
      WRITE_NODE_TYPE("ARRAYCOERCEEXPR");

      WRITE_NODE_FIELD(arg);
!     WRITE_NODE_FIELD(elemexpr);
      WRITE_OID_FIELD(resulttype);
      WRITE_INT_FIELD(resulttypmod);
      WRITE_OID_FIELD(resultcollid);
      WRITE_ENUM_FIELD(coerceformat, CoercionForm);
      WRITE_LOCATION_FIELD(location);
  }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 86c811d..76ba570 100644
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readArrayCoerceExpr(void)
*** 892,902 ****
      READ_LOCALS(ArrayCoerceExpr);

      READ_NODE_FIELD(arg);
!     READ_OID_FIELD(elemfuncid);
      READ_OID_FIELD(resulttype);
      READ_INT_FIELD(resulttypmod);
      READ_OID_FIELD(resultcollid);
-     READ_BOOL_FIELD(isExplicit);
      READ_ENUM_FIELD(coerceformat, CoercionForm);
      READ_LOCATION_FIELD(location);

--- 892,901 ----
      READ_LOCALS(ArrayCoerceExpr);

      READ_NODE_FIELD(arg);
!     READ_NODE_FIELD(elemexpr);
      READ_OID_FIELD(resulttype);
      READ_INT_FIELD(resulttypmod);
      READ_OID_FIELD(resultcollid);
      READ_ENUM_FIELD(coerceformat, CoercionForm);
      READ_LOCATION_FIELD(location);

diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index b35acb7..3721a49 100644
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
*************** cost_qual_eval_walker(Node *node, cost_q
*** 3607,3617 ****
      else if (IsA(node, ArrayCoerceExpr))
      {
          ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
!         Node       *arraynode = (Node *) acoerce->arg;

!         if (OidIsValid(acoerce->elemfuncid))
!             context->total.per_tuple += get_func_cost(acoerce->elemfuncid) *
!                 cpu_operator_cost * estimate_array_length(arraynode);
      }
      else if (IsA(node, RowCompareExpr))
      {
--- 3607,3620 ----
      else if (IsA(node, ArrayCoerceExpr))
      {
          ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
!         QualCost    perelemcost;

!         cost_qual_eval_node(&perelemcost, (Node *) acoerce->elemexpr,
!                             context->root);
!         context->total.startup += perelemcost.startup;
!         if (perelemcost.per_tuple > 0)
!             context->total.per_tuple += perelemcost.per_tuple *
!                 estimate_array_length((Node *) acoerce->arg);
      }
      else if (IsA(node, RowCompareExpr))
      {
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index b0c9e94..dee4414 100644
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
*************** fix_expr_common(PlannerInfo *root, Node
*** 1395,1406 ****
          record_plan_function_dependency(root,
                                          ((ScalarArrayOpExpr *) node)->opfuncid);
      }
-     else if (IsA(node, ArrayCoerceExpr))
-     {
-         if (OidIsValid(((ArrayCoerceExpr *) node)->elemfuncid))
-             record_plan_function_dependency(root,
-                                             ((ArrayCoerceExpr *) node)->elemfuncid);
-     }
      else if (IsA(node, Const))
      {
          Const       *con = (Const *) node;
--- 1395,1400 ----
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 8b4425d..e15f043 100644
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
*************** contain_nonstrict_functions_walker(Node
*** 1361,1366 ****
--- 1361,1377 ----
          return true;
      if (IsA(node, FieldStore))
          return true;
+     if (IsA(node, ArrayCoerceExpr))
+     {
+         /*
+          * ArrayCoerceExpr is strict at the array level, regardless of what
+          * the per-element expression is; so we should ignore elemexpr and
+          * recurse only into the arg.
+          */
+         return expression_tree_walker((Node *) ((ArrayCoerceExpr *) node)->arg,
+                                       contain_nonstrict_functions_walker,
+                                       context);
+     }
      if (IsA(node, CaseExpr))
          return true;
      if (IsA(node, ArrayExpr))
*************** contain_nonstrict_functions_walker(Node
*** 1380,1393 ****
      if (IsA(node, BooleanTest))
          return true;

!     /*
!      * Check other function-containing nodes; but ArrayCoerceExpr is strict at
!      * the array level, regardless of elemfunc.
!      */
!     if (!IsA(node, ArrayCoerceExpr) &&
!         check_functions_in_node(node, contain_nonstrict_functions_checker,
                                  context))
          return true;
      return expression_tree_walker(node, contain_nonstrict_functions_walker,
                                    context);
  }
--- 1391,1401 ----
      if (IsA(node, BooleanTest))
          return true;

!     /* Check other function-containing nodes */
!     if (check_functions_in_node(node, contain_nonstrict_functions_checker,
                                  context))
          return true;
+
      return expression_tree_walker(node, contain_nonstrict_functions_walker,
                                    context);
  }
*************** find_nonnullable_rels_walker(Node *node,
*** 1757,1763 ****
      }
      else if (IsA(node, ArrayCoerceExpr))
      {
!         /* ArrayCoerceExpr is strict at the array level */
          ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node;

          result = find_nonnullable_rels_walker((Node *) expr->arg, top_level);
--- 1765,1771 ----
      }
      else if (IsA(node, ArrayCoerceExpr))
      {
!         /* ArrayCoerceExpr is strict at the array level; ignore elemexpr */
          ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node;

          result = find_nonnullable_rels_walker((Node *) expr->arg, top_level);
*************** find_nonnullable_vars_walker(Node *node,
*** 1965,1971 ****
      }
      else if (IsA(node, ArrayCoerceExpr))
      {
!         /* ArrayCoerceExpr is strict at the array level */
          ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node;

          result = find_nonnullable_vars_walker((Node *) expr->arg, top_level);
--- 1973,1979 ----
      }
      else if (IsA(node, ArrayCoerceExpr))
      {
!         /* ArrayCoerceExpr is strict at the array level; ignore elemexpr */
          ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node;

          result = find_nonnullable_vars_walker((Node *) expr->arg, top_level);
*************** eval_const_expressions_mutator(Node *nod
*** 3005,3036 ****
              {
                  ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node;
                  Expr       *arg;
                  ArrayCoerceExpr *newexpr;

                  /*
!                  * Reduce constants in the ArrayCoerceExpr's argument, then
!                  * build a new ArrayCoerceExpr.
                   */
                  arg = (Expr *) eval_const_expressions_mutator((Node *) expr->arg,
                                                                context);

                  newexpr = makeNode(ArrayCoerceExpr);
                  newexpr->arg = arg;
!                 newexpr->elemfuncid = expr->elemfuncid;
                  newexpr->resulttype = expr->resulttype;
                  newexpr->resulttypmod = expr->resulttypmod;
                  newexpr->resultcollid = expr->resultcollid;
-                 newexpr->isExplicit = expr->isExplicit;
                  newexpr->coerceformat = expr->coerceformat;
                  newexpr->location = expr->location;

                  /*
!                  * If constant argument and it's a binary-coercible or
!                  * immutable conversion, we can simplify it to a constant.
                   */
                  if (arg && IsA(arg, Const) &&
!                     (!OidIsValid(newexpr->elemfuncid) ||
!                      func_volatile(newexpr->elemfuncid) == PROVOLATILE_IMMUTABLE))
                      return (Node *) evaluate_expr((Expr *) newexpr,
                                                    newexpr->resulttype,
                                                    newexpr->resulttypmod,
--- 3013,3050 ----
              {
                  ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node;
                  Expr       *arg;
+                 Expr       *elemexpr;
                  ArrayCoerceExpr *newexpr;

                  /*
!                  * Reduce constants in the ArrayCoerceExpr's argument and
!                  * per-element expressions, then build a new ArrayCoerceExpr.
                   */
                  arg = (Expr *) eval_const_expressions_mutator((Node *) expr->arg,
                                                                context);
+                 elemexpr = (Expr *) eval_const_expressions_mutator((Node *) expr->elemexpr,
+                                                                    context);

                  newexpr = makeNode(ArrayCoerceExpr);
                  newexpr->arg = arg;
!                 newexpr->elemexpr = elemexpr;
                  newexpr->resulttype = expr->resulttype;
                  newexpr->resulttypmod = expr->resulttypmod;
                  newexpr->resultcollid = expr->resultcollid;
                  newexpr->coerceformat = expr->coerceformat;
                  newexpr->location = expr->location;

                  /*
!                  * If constant argument and per-element expression is
!                  * immutable, we can simplify the whole thing to a constant.
!                  * Exception: although contain_mutable_functions considers
!                  * CoerceToDomain immutable for historical reasons, let's not
!                  * do so here; this ensures coercion to an array-over-domain
!                  * does not apply the domain's constraints until runtime.
                   */
                  if (arg && IsA(arg, Const) &&
!                     elemexpr && !IsA(elemexpr, CoerceToDomain) &&
!                     !contain_mutable_functions((Node *) elemexpr))
                      return (Node *) evaluate_expr((Expr *) newexpr,
                                                    newexpr->resulttype,
                                                    newexpr->resulttypmod,
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index c406cea..8fe5444 100644
*** a/src/backend/parser/parse_coerce.c
--- b/src/backend/parser/parse_coerce.c
*************** build_coercion_expression(Node *node,
*** 876,894 ****
      {
          /* We need to build an ArrayCoerceExpr */
          ArrayCoerceExpr *acoerce = makeNode(ArrayCoerceExpr);

          acoerce->arg = (Expr *) node;
!         acoerce->elemfuncid = funcId;
          acoerce->resulttype = targetTypeId;

          /*
!          * Label the output as having a particular typmod only if we are
!          * really invoking a length-coercion function, ie one with more than
!          * one argument.
           */
!         acoerce->resulttypmod = (nargs >= 2) ? targetTypMod : -1;
          /* resultcollid will be set by parse_collate.c */
-         acoerce->isExplicit = (ccontext == COERCION_EXPLICIT);
          acoerce->coerceformat = cformat;
          acoerce->location = location;

--- 876,927 ----
      {
          /* We need to build an ArrayCoerceExpr */
          ArrayCoerceExpr *acoerce = makeNode(ArrayCoerceExpr);
+         CaseTestExpr *ctest = makeNode(CaseTestExpr);
+         Oid            sourceBaseTypeId;
+         int32        sourceBaseTypeMod;
+         Oid            targetElementType;
+         Node       *elemexpr;
+
+         /*
+          * Look through any domain over the source array type.  Note we don't
+          * expect that the target type is a domain; it must be a plain array.
+          * (To get to a domain target type, we'll do coerce_to_domain later.)
+          */
+         sourceBaseTypeMod = exprTypmod(node);
+         sourceBaseTypeId = getBaseTypeAndTypmod(exprType(node),
+                                                 &sourceBaseTypeMod);
+
+         /* Set up CaseTestExpr representing one element of source array */
+         ctest->typeId = get_element_type(sourceBaseTypeId);
+         Assert(OidIsValid(ctest->typeId));
+         ctest->typeMod = sourceBaseTypeMod;
+         ctest->collation = InvalidOid;    /* Assume coercions don't care */
+
+         /* And coerce it to the target element type */
+         targetElementType = get_element_type(targetTypeId);
+         Assert(OidIsValid(targetElementType));
+
+         elemexpr = coerce_to_target_type(NULL,
+                                          (Node *) ctest,
+                                          ctest->typeId,
+                                          targetElementType,
+                                          targetTypMod,
+                                          ccontext,
+                                          cformat,
+                                          location);
+         if (elemexpr == NULL)    /* shouldn't happen */
+             elog(ERROR, "failed to coerce array element type as expected");

          acoerce->arg = (Expr *) node;
!         acoerce->elemexpr = (Expr *) elemexpr;
          acoerce->resulttype = targetTypeId;

          /*
!          * Label the output as having a particular element typmod only if we
!          * ended up with a per-element expression that is labeled that way.
           */
!         acoerce->resulttypmod = exprTypmod(elemexpr);
          /* resultcollid will be set by parse_collate.c */
          acoerce->coerceformat = cformat;
          acoerce->location = location;

*************** IsBinaryCoercible(Oid srctype, Oid targe
*** 2142,2149 ****
   *    COERCION_PATH_RELABELTYPE: binary-compatible cast, no function needed
   *                *funcid is set to InvalidOid
   *    COERCION_PATH_ARRAYCOERCE: need an ArrayCoerceExpr node
!  *                *funcid is set to the element cast function, or InvalidOid
!  *                if the array elements are binary-compatible
   *    COERCION_PATH_COERCEVIAIO: need a CoerceViaIO node
   *                *funcid is set to InvalidOid
   *
--- 2175,2181 ----
   *    COERCION_PATH_RELABELTYPE: binary-compatible cast, no function needed
   *                *funcid is set to InvalidOid
   *    COERCION_PATH_ARRAYCOERCE: need an ArrayCoerceExpr node
!  *                *funcid is set to InvalidOid
   *    COERCION_PATH_COERCEVIAIO: need a CoerceViaIO node
   *                *funcid is set to InvalidOid
   *
*************** find_coercion_pathway(Oid targetTypeId,
*** 2229,2239 ****
      {
          /*
           * If there's no pg_cast entry, perhaps we are dealing with a pair of
!          * array types.  If so, and if the element types have a suitable cast,
!          * report that we can coerce with an ArrayCoerceExpr.
!          *
!          * Note that the source type can be a domain over array, but not the
!          * target, because ArrayCoerceExpr won't check domain constraints.
           *
           * Hack: disallow coercions to oidvector and int2vector, which
           * otherwise tend to capture coercions that should go to "real" array
--- 2261,2268 ----
      {
          /*
           * If there's no pg_cast entry, perhaps we are dealing with a pair of
!          * array types.  If so, and if their element types have a conversion
!          * pathway, report that we can coerce with an ArrayCoerceExpr.
           *
           * Hack: disallow coercions to oidvector and int2vector, which
           * otherwise tend to capture coercions that should go to "real" array
*************** find_coercion_pathway(Oid targetTypeId,
*** 2248,2254 ****
              Oid            sourceElem;

              if ((targetElem = get_element_type(targetTypeId)) != InvalidOid &&
!                 (sourceElem = get_base_element_type(sourceTypeId)) != InvalidOid)
              {
                  CoercionPathType elempathtype;
                  Oid            elemfuncid;
--- 2277,2283 ----
              Oid            sourceElem;

              if ((targetElem = get_element_type(targetTypeId)) != InvalidOid &&
!                 (sourceElem = get_element_type(sourceTypeId)) != InvalidOid)
              {
                  CoercionPathType elempathtype;
                  Oid            elemfuncid;
*************** find_coercion_pathway(Oid targetTypeId,
*** 2257,2270 ****
                                                       sourceElem,
                                                       ccontext,
                                                       &elemfuncid);
!                 if (elempathtype != COERCION_PATH_NONE &&
!                     elempathtype != COERCION_PATH_ARRAYCOERCE)
                  {
!                     *funcid = elemfuncid;
!                     if (elempathtype == COERCION_PATH_COERCEVIAIO)
!                         result = COERCION_PATH_COERCEVIAIO;
!                     else
!                         result = COERCION_PATH_ARRAYCOERCE;
                  }
              }
          }
--- 2286,2294 ----
                                                       sourceElem,
                                                       ccontext,
                                                       &elemfuncid);
!                 if (elempathtype != COERCION_PATH_NONE)
                  {
!                     result = COERCION_PATH_ARRAYCOERCE;
                  }
              }
          }
*************** find_coercion_pathway(Oid targetTypeId,
*** 2305,2311 ****
   * If the given type is a varlena array type, we do not look for a coercion
   * function associated directly with the array type, but instead look for
   * one associated with the element type.  An ArrayCoerceExpr node must be
!  * used to apply such a function.
   *
   * We use the same result enum as find_coercion_pathway, but the only possible
   * result codes are:
--- 2329,2337 ----
   * If the given type is a varlena array type, we do not look for a coercion
   * function associated directly with the array type, but instead look for
   * one associated with the element type.  An ArrayCoerceExpr node must be
!  * used to apply such a function.  (Note: currently, it's pointless to
!  * return the funcid in this case, because it'll just get looked up again
!  * in the recursive construction of the ArrayCoerceExpr's elemexpr.)
   *
   * We use the same result enum as find_coercion_pathway, but the only possible
   * result codes are:
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 34dadd6..8287b39 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** array_set(ArrayType *array, int nSubscri
*** 3091,3111 ****
  /*
   * array_map()
   *
!  * Map an array through an arbitrary function.  Return a new array with
!  * same dimensions and each source element transformed by fn().  Each
!  * source element is passed as the first argument to fn(); additional
!  * arguments to be passed to fn() can be specified by the caller.
!  * The output array can have a different element type than the input.
   *
   * Parameters are:
!  * * fcinfo: a function-call data structure pre-constructed by the caller
!  *     to be ready to call the desired function, with everything except the
!  *     first argument position filled in.  In particular, flinfo identifies
!  *     the function fn(), and if nargs > 1 then argument positions after the
!  *     first must be preset to the additional values to be passed.  The
!  *     first argument position initially holds the input array value.
   * * retType: OID of element type of output array.  This must be the same as,
!  *     or binary-compatible with, the result type of fn().
   * * amstate: workspace for array_map.  Must be zeroed by caller before
   *     first call, and not touched after that.
   *
--- 3091,3108 ----
  /*
   * array_map()
   *
!  * Map an array through an arbitrary expression.  Return a new array with
!  * the same dimensions and each source element transformed by the given,
!  * already-compiled expression.  Each source element is placed in the
!  * innermost_caseval/innermost_casenull fields of the ExprState.
   *
   * Parameters are:
!  * * arrayd: Datum representing array argument.
!  * * exprstate: ExprState representing the per-element transformation.
!  * * econtext: context for expression evaluation.
   * * retType: OID of element type of output array.  This must be the same as,
!  *     or binary-compatible with, the result type of the expression.  It might
!  *     be different from the input array's element type.
   * * amstate: workspace for array_map.  Must be zeroed by caller before
   *     first call, and not touched after that.
   *
*************** array_set(ArrayType *array, int nSubscri
*** 3115,3125 ****
   *
   * NB: caller must assure that input array is not NULL.  NULL elements in
   * the array are OK however.
   */
  Datum
! array_map(FunctionCallInfo fcinfo, Oid retType, ArrayMapState *amstate)
  {
!     AnyArrayType *v;
      ArrayType  *result;
      Datum       *values;
      bool       *nulls;
--- 3112,3125 ----
   *
   * NB: caller must assure that input array is not NULL.  NULL elements in
   * the array are OK however.
+  * NB: caller should be running in econtext's per-tuple memory context.
   */
  Datum
! array_map(Datum arrayd,
!           ExprState *exprstate, ExprContext *econtext,
!           Oid retType, ArrayMapState *amstate)
  {
!     AnyArrayType *v = DatumGetAnyArray(arrayd);
      ArrayType  *result;
      Datum       *values;
      bool       *nulls;
*************** array_map(FunctionCallInfo fcinfo, Oid r
*** 3140,3152 ****
      array_iter    iter;
      ArrayMetaState *inp_extra;
      ArrayMetaState *ret_extra;
!
!     /* Get input array */
!     if (fcinfo->nargs < 1)
!         elog(ERROR, "invalid nargs: %d", fcinfo->nargs);
!     if (PG_ARGISNULL(0))
!         elog(ERROR, "null input array");
!     v = PG_GETARG_ANY_ARRAY(0);

      inpType = AARR_ELEMTYPE(v);
      ndim = AARR_NDIM(v);
--- 3140,3147 ----
      array_iter    iter;
      ArrayMetaState *inp_extra;
      ArrayMetaState *ret_extra;
!     Datum       *transform_source = exprstate->innermost_caseval;
!     bool       *transform_source_isnull = exprstate->innermost_casenull;

      inpType = AARR_ELEMTYPE(v);
      ndim = AARR_NDIM(v);
*************** array_map(FunctionCallInfo fcinfo, Oid r
*** 3157,3163 ****
      if (nitems <= 0)
      {
          /* Return empty array */
!         PG_RETURN_ARRAYTYPE_P(construct_empty_array(retType));
      }

      /*
--- 3152,3158 ----
      if (nitems <= 0)
      {
          /* Return empty array */
!         return PointerGetDatum(construct_empty_array(retType));
      }

      /*
*************** array_map(FunctionCallInfo fcinfo, Oid r
*** 3202,3240 ****

      for (i = 0; i < nitems; i++)
      {
-         bool        callit = true;
-
          /* Get source element, checking for NULL */
!         fcinfo->arg[0] = array_iter_next(&iter, &fcinfo->argnull[0], i,
!                                          inp_typlen, inp_typbyval, inp_typalign);
!
!         /*
!          * Apply the given function to source elt and extra args.
!          */
!         if (fcinfo->flinfo->fn_strict)
!         {
!             int            j;
!
!             for (j = 0; j < fcinfo->nargs; j++)
!             {
!                 if (fcinfo->argnull[j])
!                 {
!                     callit = false;
!                     break;
!                 }
!             }
!         }

!         if (callit)
!         {
!             fcinfo->isnull = false;
!             values[i] = FunctionCallInvoke(fcinfo);
!         }
!         else
!             fcinfo->isnull = true;

!         nulls[i] = fcinfo->isnull;
!         if (fcinfo->isnull)
              hasnulls = true;
          else
          {
--- 3197,3211 ----

      for (i = 0; i < nitems; i++)
      {
          /* Get source element, checking for NULL */
!         *transform_source =
!             array_iter_next(&iter, transform_source_isnull, i,
!                             inp_typlen, inp_typbyval, inp_typalign);

!         /* Apply the given expression to source element */
!         values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]);

!         if (nulls[i])
              hasnulls = true;
          else
          {
*************** array_map(FunctionCallInfo fcinfo, Oid r
*** 3253,3259 ****
          }
      }

!     /* Allocate and initialize the result array */
      if (hasnulls)
      {
          dataoffset = ARR_OVERHEAD_WITHNULLS(ndim, nitems);
--- 3224,3230 ----
          }
      }

!     /* Allocate and fill the result array */
      if (hasnulls)
      {
          dataoffset = ARR_OVERHEAD_WITHNULLS(ndim, nitems);
*************** array_map(FunctionCallInfo fcinfo, Oid r
*** 3272,3289 ****
      memcpy(ARR_DIMS(result), AARR_DIMS(v), ndim * sizeof(int));
      memcpy(ARR_LBOUND(result), AARR_LBOUND(v), ndim * sizeof(int));

-     /*
-      * Note: do not risk trying to pfree the results of the called function
-      */
      CopyArrayEls(result,
                   values, nulls, nitems,
                   typlen, typbyval, typalign,
                   false);

      pfree(values);
      pfree(nulls);

!     PG_RETURN_ARRAYTYPE_P(result);
  }

  /*
--- 3243,3260 ----
      memcpy(ARR_DIMS(result), AARR_DIMS(v), ndim * sizeof(int));
      memcpy(ARR_LBOUND(result), AARR_LBOUND(v), ndim * sizeof(int));

      CopyArrayEls(result,
                   values, nulls, nitems,
                   typlen, typbyval, typalign,
                   false);

+     /*
+      * Note: do not risk trying to pfree the results of the called expression
+      */
      pfree(values);
      pfree(nulls);

!     return PointerGetDatum(result);
  }

  /*
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index e103f5e..1bb0da1 100644
*** a/src/backend/utils/adt/selfuncs.c
--- b/src/backend/utils/adt/selfuncs.c
*************** strip_array_coercion(Node *node)
*** 1752,1761 ****
  {
      for (;;)
      {
!         if (node && IsA(node, ArrayCoerceExpr) &&
!             ((ArrayCoerceExpr *) node)->elemfuncid == InvalidOid)
          {
!             node = (Node *) ((ArrayCoerceExpr *) node)->arg;
          }
          else if (node && IsA(node, RelabelType))
          {
--- 1752,1770 ----
  {
      for (;;)
      {
!         if (node && IsA(node, ArrayCoerceExpr))
          {
!             ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
!
!             /*
!              * If the per-element expression is just a RelabelType on top of
!              * CaseTestExpr, then we know it's a binary-compatible relabeling.
!              */
!             if (IsA(acoerce->elemexpr, RelabelType) &&
!                 IsA(((RelabelType *) acoerce->elemexpr)->arg, CaseTestExpr))
!                 node = (Node *) acoerce->arg;
!             else
!                 break;
          }
          else if (node && IsA(node, RelabelType))
          {
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index a7b0782..9197335 100644
*** a/src/backend/utils/fmgr/fmgr.c
--- b/src/backend/utils/fmgr/fmgr.c
*************** get_call_expr_argtype(Node *expr, int ar
*** 1941,1948 ****
          args = ((DistinctExpr *) expr)->args;
      else if (IsA(expr, ScalarArrayOpExpr))
          args = ((ScalarArrayOpExpr *) expr)->args;
-     else if (IsA(expr, ArrayCoerceExpr))
-         args = list_make1(((ArrayCoerceExpr *) expr)->arg);
      else if (IsA(expr, NullIfExpr))
          args = ((NullIfExpr *) expr)->args;
      else if (IsA(expr, WindowFunc))
--- 1941,1946 ----
*************** get_call_expr_argtype(Node *expr, int ar
*** 1956,1971 ****
      argtype = exprType((Node *) list_nth(args, argnum));

      /*
!      * special hack for ScalarArrayOpExpr and ArrayCoerceExpr: what the
!      * underlying function will actually get passed is the element type of the
!      * array.
       */
      if (IsA(expr, ScalarArrayOpExpr) &&
          argnum == 1)
          argtype = get_base_element_type(argtype);
-     else if (IsA(expr, ArrayCoerceExpr) &&
-              argnum == 0)
-         argtype = get_base_element_type(argtype);

      return argtype;
  }
--- 1954,1965 ----
      argtype = exprType((Node *) list_nth(args, argnum));

      /*
!      * special hack for ScalarArrayOpExpr: what the underlying function will
!      * actually get passed is the element type of the array.
       */
      if (IsA(expr, ScalarArrayOpExpr) &&
          argnum == 1)
          argtype = get_base_element_type(argtype);

      return argtype;
  }
*************** get_call_expr_arg_stable(Node *expr, int
*** 2012,2019 ****
          args = ((DistinctExpr *) expr)->args;
      else if (IsA(expr, ScalarArrayOpExpr))
          args = ((ScalarArrayOpExpr *) expr)->args;
-     else if (IsA(expr, ArrayCoerceExpr))
-         args = list_make1(((ArrayCoerceExpr *) expr)->arg);
      else if (IsA(expr, NullIfExpr))
          args = ((NullIfExpr *) expr)->args;
      else if (IsA(expr, WindowFunc))
--- 2006,2011 ----
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 8ee0496..78d2247 100644
*** a/src/include/executor/execExpr.h
--- b/src/include/executor/execExpr.h
*************** typedef struct ExprEvalStep
*** 385,394 ****
          /* for EEOP_ARRAYCOERCE */
          struct
          {
!             ArrayCoerceExpr *coerceexpr;
              Oid            resultelemtype; /* element type of result array */
-             FmgrInfo   *elemfunc;    /* lookup info for element coercion
-                                      * function */
              struct ArrayMapState *amstate;    /* workspace for array_map */
          }            arraycoerce;

--- 385,392 ----
          /* for EEOP_ARRAYCOERCE */
          struct
          {
!             ExprState  *elemexprstate;    /* null if no per-element work */
              Oid            resultelemtype; /* element type of result array */
              struct ArrayMapState *amstate;    /* workspace for array_map */
          }            arraycoerce;

*************** extern void ExecEvalRowNull(ExprState *s
*** 621,627 ****
  extern void ExecEvalRowNotNull(ExprState *state, ExprEvalStep *op,
                     ExprContext *econtext);
  extern void ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op);
! extern void ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op);
  extern void ExecEvalRow(ExprState *state, ExprEvalStep *op);
  extern void ExecEvalMinMax(ExprState *state, ExprEvalStep *op);
  extern void ExecEvalFieldSelect(ExprState *state, ExprEvalStep *op,
--- 619,626 ----
  extern void ExecEvalRowNotNull(ExprState *state, ExprEvalStep *op,
                     ExprContext *econtext);
  extern void ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op);
! extern void ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op,
!                     ExprContext *econtext);
  extern void ExecEvalRow(ExprState *state, ExprEvalStep *op);
  extern void ExecEvalMinMax(ExprState *state, ExprEvalStep *op);
  extern void ExecEvalFieldSelect(ExprState *state, ExprEvalStep *op,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 8c536a8..ccb5123 100644
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
*************** typedef struct CoerceViaIO
*** 820,830 ****
   * ArrayCoerceExpr
   *
   * ArrayCoerceExpr represents a type coercion from one array type to another,
!  * which is implemented by applying the indicated element-type coercion
!  * function to each element of the source array.  If elemfuncid is InvalidOid
!  * then the element types are binary-compatible, but the coercion still
!  * requires some effort (we have to fix the element type ID stored in the
!  * array header).
   * ----------------
   */

--- 820,831 ----
   * ArrayCoerceExpr
   *
   * ArrayCoerceExpr represents a type coercion from one array type to another,
!  * which is implemented by applying the per-element coercion expression
!  * "elemexpr" to each element of the source array.  Within elemexpr, the
!  * source element is represented by a CaseTestExpr node.  Note that even if
!  * elemexpr is a no-op (that is, just CaseTestExpr + RelabelType), the
!  * coercion still requires some effort: we have to fix the element type OID
!  * stored in the array header.
   * ----------------
   */

*************** typedef struct ArrayCoerceExpr
*** 832,842 ****
  {
      Expr        xpr;
      Expr       *arg;            /* input expression (yields an array) */
!     Oid            elemfuncid;        /* OID of element coercion function, or 0 */
      Oid            resulttype;        /* output type of coercion (an array type) */
      int32        resulttypmod;    /* output typmod (also element typmod) */
      Oid            resultcollid;    /* OID of collation, or InvalidOid if none */
-     bool        isExplicit;        /* conversion semantics flag to pass to func */
      CoercionForm coerceformat;    /* how to display this node */
      int            location;        /* token location, or -1 if unknown */
  } ArrayCoerceExpr;
--- 833,842 ----
  {
      Expr        xpr;
      Expr       *arg;            /* input expression (yields an array) */
!     Expr       *elemexpr;        /* expression representing per-element work */
      Oid            resulttype;        /* output type of coercion (an array type) */
      int32        resulttypmod;    /* output typmod (also element typmod) */
      Oid            resultcollid;    /* OID of collation, or InvalidOid if none */
      CoercionForm coerceformat;    /* how to display this node */
      int            location;        /* token location, or -1 if unknown */
  } ArrayCoerceExpr;
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 61a67a2..32833f0 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
***************
*** 64,69 ****
--- 64,73 ----
  #include "fmgr.h"
  #include "utils/expandeddatum.h"

+ /* avoid including execnodes.h here */
+ struct ExprState;
+ struct ExprContext;
+

  /*
   * Arrays are varlena objects, so must meet the varlena convention that
*************** extern ArrayType *array_set(ArrayType *a
*** 360,367 ****
            Datum dataValue, bool isNull,
            int arraytyplen, int elmlen, bool elmbyval, char elmalign);

! extern Datum array_map(FunctionCallInfo fcinfo, Oid retType,
!           ArrayMapState *amstate);

  extern void array_bitmap_copy(bits8 *destbitmap, int destoffset,
                    const bits8 *srcbitmap, int srcoffset,
--- 364,372 ----
            Datum dataValue, bool isNull,
            int arraytyplen, int elmlen, bool elmbyval, char elmalign);

! extern Datum array_map(Datum arrayd,
!           struct ExprState *exprstate, struct ExprContext *econtext,
!           Oid retType, ArrayMapState *amstate);

  extern void array_bitmap_copy(bits8 *destbitmap, int destoffset,
                    const bits8 *srcbitmap, int srcoffset,
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index c98492b..eb584ac 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** exec_simple_check_node(Node *node)
*** 6577,6583 ****
              return exec_simple_check_node((Node *) ((CoerceViaIO *) node)->arg);

          case T_ArrayCoerceExpr:
!             return exec_simple_check_node((Node *) ((ArrayCoerceExpr *) node)->arg);

          case T_ConvertRowtypeExpr:
              return exec_simple_check_node((Node *) ((ConvertRowtypeExpr *) node)->arg);
--- 6577,6592 ----
              return exec_simple_check_node((Node *) ((CoerceViaIO *) node)->arg);

          case T_ArrayCoerceExpr:
!             {
!                 ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node;
!
!                 if (!exec_simple_check_node((Node *) expr->arg))
!                     return FALSE;
!                 if (!exec_simple_check_node((Node *) expr->elemexpr))
!                     return FALSE;
!
!                 return TRUE;
!             }

          case T_ConvertRowtypeExpr:
              return exec_simple_check_node((Node *) ((ConvertRowtypeExpr *) node)->arg);
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 29ac5d5..c7bbe22 100644
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
*************** ObjectAddress
*** 729,734 ****
--- 729,735 ----
  DefineDomain(CreateDomainStmt *stmt)
  {
      char       *domainName;
+     char       *domainArrayName;
      Oid            domainNamespace;
      AclResult    aclresult;
      int16        internalLength;
*************** DefineDomain(CreateDomainStmt *stmt)
*** 757,762 ****
--- 758,764 ----
      Oid            basetypeoid;
      Oid            old_type_oid;
      Oid            domaincoll;
+     Oid            domainArrayOid;
      Form_pg_type baseType;
      int32        basetypeMod;
      Oid            baseColl;
*************** DefineDomain(CreateDomainStmt *stmt)
*** 1027,1032 ****
--- 1029,1037 ----
          }
      }

+     /* Allocate OID for array type */
+     domainArrayOid = AssignTypeArrayOid();
+
      /*
       * Have TypeCreate do all the real work.
       */
*************** DefineDomain(CreateDomainStmt *stmt)
*** 1051,1057 ****
                     analyzeProcedure,    /* analyze procedure */
                     InvalidOid,    /* no array element type */
                     false,        /* this isn't an array */
!                    InvalidOid,    /* no arrays for domains (yet) */
                     basetypeoid, /* base type ID */
                     defaultValue,    /* default type value (text) */
                     defaultValueBin, /* default type value (binary) */
--- 1056,1062 ----
                     analyzeProcedure,    /* analyze procedure */
                     InvalidOid,    /* no array element type */
                     false,        /* this isn't an array */
!                    domainArrayOid,    /* array type we are about to create */
                     basetypeoid, /* base type ID */
                     defaultValue,    /* default type value (text) */
                     defaultValueBin, /* default type value (binary) */
*************** DefineDomain(CreateDomainStmt *stmt)
*** 1064,1069 ****
--- 1069,1116 ----
                     domaincoll); /* type's collation */

      /*
+      * Create the array type that goes with it.
+      */
+     domainArrayName = makeArrayTypeName(domainName, domainNamespace);
+
+     /* alignment must be 'i' or 'd' for arrays */
+     alignment = (alignment == 'd') ? 'd' : 'i';
+
+     TypeCreate(domainArrayOid,    /* force assignment of this type OID */
+                domainArrayName, /* type name */
+                domainNamespace, /* namespace */
+                InvalidOid,        /* relation oid (n/a here) */
+                0,                /* relation kind (ditto) */
+                GetUserId(),        /* owner's ID */
+                -1,                /* internal size (always varlena) */
+                TYPTYPE_BASE,    /* type-type (base type) */
+                TYPCATEGORY_ARRAY,    /* type-category (array) */
+                false,            /* array types are never preferred */
+                delimiter,        /* array element delimiter */
+                F_ARRAY_IN,        /* input procedure */
+                F_ARRAY_OUT,        /* output procedure */
+                F_ARRAY_RECV,    /* receive procedure */
+                F_ARRAY_SEND,    /* send procedure */
+                InvalidOid,        /* typmodin procedure - none */
+                InvalidOid,        /* typmodout procedure - none */
+                F_ARRAY_TYPANALYZE,    /* analyze procedure */
+                address.objectId,    /* element type ID */
+                true,            /* yes this is an array type */
+                InvalidOid,        /* no further array type */
+                InvalidOid,        /* base type ID */
+                NULL,            /* never a default type value */
+                NULL,            /* binary default isn't sent either */
+                false,            /* never passed by value */
+                alignment,        /* see above */
+                'x',                /* ARRAY is always toastable */
+                -1,                /* typMod (Domains only) */
+                0,                /* Array dimensions of typbasetype */
+                false,            /* Type NOT NULL */
+                domaincoll);        /* type's collation */
+
+     pfree(domainArrayName);
+
+     /*
       * Process constraints which refer to the domain ID returned by TypeCreate
       */
      foreach(listptr, schema)
*************** DefineEnum(CreateEnumStmt *stmt)
*** 1139,1144 ****
--- 1186,1192 ----
                       errmsg("type \"%s\" already exists", enumName)));
      }

+     /* Allocate OID for array type */
      enumArrayOid = AssignTypeArrayOid();

      /* Create the pg_type entry */
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 3acc696..1e62c57 100644
*** a/src/test/regress/expected/domain.out
--- b/src/test/regress/expected/domain.out
*************** Rules:
*** 310,315 ****
--- 310,410 ----
  drop table dcomptable;
  drop type comptype cascade;
  NOTICE:  drop cascades to type dcomptypea
+ -- Test arrays over domains
+ create domain posint as int check (value > 0);
+ create table pitable (f1 posint[]);
+ insert into pitable values(array[42]);
+ insert into pitable values(array[-1]);  -- fail
+ ERROR:  value for domain posint violates check constraint "posint_check"
+ insert into pitable values('{0}');  -- fail
+ ERROR:  value for domain posint violates check constraint "posint_check"
+ LINE 1: insert into pitable values('{0}');
+                                    ^
+ update pitable set f1[1] = f1[1] + 1;
+ update pitable set f1[1] = 0;  -- fail
+ ERROR:  value for domain posint violates check constraint "posint_check"
+ select * from pitable;
+   f1
+ ------
+  {43}
+ (1 row)
+
+ drop table pitable;
+ create domain vc4 as varchar(4);
+ create table vc4table (f1 vc4[]);
+ insert into vc4table values(array['too long']);  -- fail
+ ERROR:  value too long for type character varying(4)
+ insert into vc4table values(array['too long']::vc4[]);  -- cast truncates
+ select * from vc4table;
+     f1
+ ----------
+  {"too "}
+ (1 row)
+
+ drop table vc4table;
+ drop type vc4;
+ -- You can sort of fake arrays-of-arrays by putting a domain in between
+ create domain dposinta as posint[];
+ create table dposintatable (f1 dposinta[]);
+ insert into dposintatable values(array[array[42]]);  -- fail
+ ERROR:  column "f1" is of type dposinta[] but expression is of type integer[]
+ LINE 1: insert into dposintatable values(array[array[42]]);
+                                          ^
+ HINT:  You will need to rewrite or cast the expression.
+ insert into dposintatable values(array[array[42]::posint[]]); -- still fail
+ ERROR:  column "f1" is of type dposinta[] but expression is of type posint[]
+ LINE 1: insert into dposintatable values(array[array[42]::posint[]])...
+                                          ^
+ HINT:  You will need to rewrite or cast the expression.
+ insert into dposintatable values(array[array[42]::dposinta]); -- but this works
+ select f1, f1[1], (f1[1])[1] from dposintatable;
+     f1    |  f1  | f1
+ ----------+------+----
+  {"{42}"} | {42} | 42
+ (1 row)
+
+ select pg_typeof(f1) from dposintatable;
+  pg_typeof
+ ------------
+  dposinta[]
+ (1 row)
+
+ select pg_typeof(f1[1]) from dposintatable;
+  pg_typeof
+ -----------
+  dposinta
+ (1 row)
+
+ select pg_typeof(f1[1][1]) from dposintatable;
+  pg_typeof
+ -----------
+  dposinta
+ (1 row)
+
+ select pg_typeof((f1[1])[1]) from dposintatable;
+  pg_typeof
+ -----------
+  posint
+ (1 row)
+
+ update dposintatable set f1[2] = array[99];
+ select f1, f1[1], (f1[2])[1] from dposintatable;
+        f1        |  f1  | f1
+ -----------------+------+----
+  {"{42}","{99}"} | {42} | 99
+ (1 row)
+
+ -- it'd be nice if you could do something like this, but for now you can't:
+ update dposintatable set f1[2][1] = array[97];
+ ERROR:  wrong number of array subscripts
+ -- maybe someday we can make this syntax work:
+ update dposintatable set (f1[2])[1] = array[98];
+ ERROR:  syntax error at or near "["
+ LINE 1: update dposintatable set (f1[2])[1] = array[98];
+                                         ^
+ drop table dposintatable;
+ drop domain posint cascade;
+ NOTICE:  drop cascades to type dposinta
  -- Test not-null restrictions
  create domain dnotnull varchar(15) NOT NULL;
  create domain dnull    varchar(15);
diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql
index 0fd383e..8fb3e20 100644
*** a/src/test/regress/sql/domain.sql
--- b/src/test/regress/sql/domain.sql
*************** drop table dcomptable;
*** 166,171 ****
--- 166,214 ----
  drop type comptype cascade;


+ -- Test arrays over domains
+
+ create domain posint as int check (value > 0);
+
+ create table pitable (f1 posint[]);
+ insert into pitable values(array[42]);
+ insert into pitable values(array[-1]);  -- fail
+ insert into pitable values('{0}');  -- fail
+ update pitable set f1[1] = f1[1] + 1;
+ update pitable set f1[1] = 0;  -- fail
+ select * from pitable;
+ drop table pitable;
+
+ create domain vc4 as varchar(4);
+ create table vc4table (f1 vc4[]);
+ insert into vc4table values(array['too long']);  -- fail
+ insert into vc4table values(array['too long']::vc4[]);  -- cast truncates
+ select * from vc4table;
+ drop table vc4table;
+ drop type vc4;
+
+ -- You can sort of fake arrays-of-arrays by putting a domain in between
+ create domain dposinta as posint[];
+ create table dposintatable (f1 dposinta[]);
+ insert into dposintatable values(array[array[42]]);  -- fail
+ insert into dposintatable values(array[array[42]::posint[]]); -- still fail
+ insert into dposintatable values(array[array[42]::dposinta]); -- but this works
+ select f1, f1[1], (f1[1])[1] from dposintatable;
+ select pg_typeof(f1) from dposintatable;
+ select pg_typeof(f1[1]) from dposintatable;
+ select pg_typeof(f1[1][1]) from dposintatable;
+ select pg_typeof((f1[1])[1]) from dposintatable;
+ update dposintatable set f1[2] = array[99];
+ select f1, f1[1], (f1[2])[1] from dposintatable;
+ -- it'd be nice if you could do something like this, but for now you can't:
+ update dposintatable set f1[2][1] = array[97];
+ -- maybe someday we can make this syntax work:
+ update dposintatable set (f1[2])[1] = array[98];
+
+ drop table dposintatable;
+ drop domain posint cascade;
+
+
  -- Test not-null restrictions

  create domain dnotnull varchar(15) NOT 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 по дате отправления:

Предыдущее
От: Sokolov Yura
Дата:
Сообщение: Re: [HACKERS] Lazy hash table for XidInMVCCSnapshot (helps Zipfiana bit)
Следующее
От: Robert Haas
Дата:
Сообщение: Re: [HACKERS] reload-through-the-top-parent switch the partition table