]> granicus.if.org Git - postgresql/commitdiff
Support arrays over domains.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 30 Sep 2017 17:40:56 +0000 (13:40 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 30 Sep 2017 17:40:56 +0000 (13:40 -0400)
Allowing arrays with a domain type as their element type was left un-done
in the original domain patch, but not for any very good reason.  This
omission leads to such surprising results as array_agg() not working on
a domain column, because the parser can't identify a suitable output type
for the polymorphic aggregate.

In order to fix this, first clean 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().

Next, reimplement 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().  The executor now handles the
per-element processing as a compiled expression instead of hard-wired code.
The main advantage of this is that we can use a single ArrayCoerceExpr to
handle as many as three successive steps per element: base type conversion,
typmod coercion, and domain constraint checking.  The old code used two
stacked ArrayCoerceExprs to handle type + typmod coercion, which was pretty
inefficient, and adding yet another array deconstruction to do domain
constraint checking seemed very unappetizing.

In the case where we just need a single, very simple coercion function,
doing this straightforwardly leads to a noticeable increase in the
per-array-element runtime cost.  Hence, add an additional shortcut evalfunc
in execExprInterp.c that skips unnecessary overhead for that specific form
of expression.  The runtime speed of simple cases is within 1% or so of
where it was before, while cases that previously required two levels of
array processing are significantly faster.

Finally, create an implicit array type for every domain type, as we do for
base types, enums, etc.  Everything except the array-coercion case seems
to just work without further effort.

Tom Lane, reviewed by Andrew Dunstan

Discussion: https://postgr.es/m/9852.1499791473@sss.pgh.pa.us

28 files changed:
contrib/pg_stat_statements/pg_stat_statements.c
doc/src/sgml/array.sgml
src/backend/catalog/dependency.c
src/backend/commands/typecmds.c
src/backend/executor/execExpr.c
src/backend/executor/execExprInterp.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/nodeFuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/path/costsize.c
src/backend/optimizer/plan/setrefs.c
src/backend/optimizer/prep/preptlist.c
src/backend/optimizer/util/clauses.c
src/backend/parser/parse_coerce.c
src/backend/rewrite/rewriteHandler.c
src/backend/rewrite/rewriteManip.c
src/backend/utils/adt/arrayfuncs.c
src/backend/utils/adt/selfuncs.c
src/backend/utils/fmgr/fmgr.c
src/include/catalog/catversion.h
src/include/executor/execExpr.h
src/include/nodes/primnodes.h
src/include/parser/parse_coerce.h
src/include/utils/array.h
src/test/regress/expected/domain.out
src/test/regress/sql/domain.sql

index 3ab1fd2db43ac369f5199482685d8819a8a4635a..a0e7a46871dcfa6f98d4e026f4cd1d77d4b0ed0c 100644 (file)
@@ -2630,6 +2630,7 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
 
                                APP_JUMB(acexpr->resulttype);
                                JumbleExpr(jstate, (Node *) acexpr->arg);
+                               JumbleExpr(jstate, (Node *) acexpr->elemexpr);
                        }
                        break;
                case T_ConvertRowtypeExpr:
index dd0d20e541f8947d97932abfc020718649340148..88eb4be04d001472f8c4d4aa5df29a82fbbf418b 100644 (file)
@@ -10,9 +10,8 @@
  <para>
   <productname>PostgreSQL</productname> allows columns of a table to be
   defined as variable-length multidimensional arrays. Arrays of any
-  built-in or user-defined base type, enum type, or composite type
-  can be created.
-  Arrays of domains are not yet supported.
+  built-in or user-defined base type, enum type, composite type, range type,
+  or domain can be created.
  </para>
 
  <sect2 id="arrays-declaration">
index 6fffc290fad8a859ec348364642f8a4bdab608f6..2668650f272382f2f392ad5862af5775241d217b 100644 (file)
@@ -1738,11 +1738,14 @@ find_expr_references_walker(Node *node,
        {
                ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
 
-               if (OidIsValid(acoerce->elemfuncid))
-                       add_object_address(OCLASS_PROC, acoerce->elemfuncid, 0,
-                                                          context->addrs);
+               /* 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))
index 4c490ed5c1bba46b1f22d0aef9eabc3169f38d9b..c1b87e09e7473807cd5ceb59ce47e86e562f72b8 100644 (file)
@@ -729,6 +729,7 @@ ObjectAddress
 DefineDomain(CreateDomainStmt *stmt)
 {
        char       *domainName;
+       char       *domainArrayName;
        Oid                     domainNamespace;
        AclResult       aclresult;
        int16           internalLength;
@@ -757,6 +758,7 @@ DefineDomain(CreateDomainStmt *stmt)
        Oid                     basetypeoid;
        Oid                     old_type_oid;
        Oid                     domaincoll;
+       Oid                     domainArrayOid;
        Form_pg_type baseType;
        int32           basetypeMod;
        Oid                     baseColl;
@@ -1027,6 +1029,9 @@ DefineDomain(CreateDomainStmt *stmt)
                }
        }
 
+       /* Allocate OID for array type */
+       domainArrayOid = AssignTypeArrayOid();
+
        /*
         * Have TypeCreate do all the real work.
         */
@@ -1051,7 +1056,7 @@ DefineDomain(CreateDomainStmt *stmt)
                                   analyzeProcedure,    /* analyze procedure */
                                   InvalidOid,  /* no array element type */
                                   false,               /* this isn't an array */
-                                  InvalidOid,  /* no arrays for domains (yet) */
+                                  domainArrayOid,      /* array type we are about to create */
                                   basetypeoid, /* base type ID */
                                   defaultValue,        /* default type value (text) */
                                   defaultValueBin, /* default type value (binary) */
@@ -1063,6 +1068,48 @@ DefineDomain(CreateDomainStmt *stmt)
                                   typNotNull,  /* Type NOT NULL */
                                   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
         */
@@ -1139,6 +1186,7 @@ DefineEnum(CreateEnumStmt *stmt)
                                         errmsg("type \"%s\" already exists", enumName)));
        }
 
+       /* Allocate OID for array type */
        enumArrayOid = AssignTypeArrayOid();
 
        /* Create the pg_type entry */
index be9d23bc323ba208f16effb7429c4fcddca931af..e0839616e196638e657f2fed70c8338ecf164720 100644 (file)
@@ -1225,6 +1225,7 @@ ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
                        {
                                ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
                                Oid                     resultelemtype;
+                               ExprState  *elemstate;
 
                                /* evaluate argument into step's result area */
                                ExecInitExprRec(acoerce->arg, parent, state, resv, resnull);
@@ -1234,42 +1235,49 @@ ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
                                        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;
+                               /*
+                                * 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));
 
-                               if (OidIsValid(acoerce->elemfuncid))
-                               {
-                                       AclResult       aclresult;
+                               ExecInitExprRec(acoerce->elemexpr, parent, elemstate,
+                                                               &elemstate->resvalue, &elemstate->resnull);
 
-                                       /* 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);
+                               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);
+                               }
 
-                                       /* 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);
+                               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 conversion func */
-                                       scratch.d.arraycoerce.elemfunc = NULL;
+                                       /* Don't need workspace if there's no subexpression */
                                        scratch.d.arraycoerce.amstate = NULL;
                                }
 
index 39d50f98a9db79568b9b45238a52250ebc339c7b..c5e97ef9e2d9303c147c3011508def6e859255e7 100644 (file)
  *
  * For very simple instructions the overhead of the full interpreter
  * "startup", as minimal as it is, is noticeable.  Therefore
- * ExecReadyInterpretedExpr will choose to implement simple scalar Var
- * and Const expressions using special fast-path routines (ExecJust*).
- * Benchmarking shows anything more complex than those may as well use the
- * "full interpreter".
+ * ExecReadyInterpretedExpr will choose to implement certain simple
+ * opcode patterns using special fast-path routines (ExecJust*).
  *
  * Complex or uncommon instructions are not implemented in-line in
  * ExecInterpExpr(), rather we call out to a helper function appearing later
@@ -149,6 +147,7 @@ static Datum ExecJustConst(ExprState *state, ExprContext *econtext, bool *isnull
 static Datum ExecJustAssignInnerVar(ExprState *state, ExprContext *econtext, bool *isnull);
 static Datum ExecJustAssignOuterVar(ExprState *state, ExprContext *econtext, bool *isnull);
 static Datum ExecJustAssignScanVar(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustApplyFuncToCase(ExprState *state, ExprContext *econtext, bool *isnull);
 
 
 /*
@@ -184,10 +183,8 @@ ExecReadyInterpretedExpr(ExprState *state)
 
        /*
         * Select fast-path evalfuncs for very simple expressions.  "Starting up"
-        * the full interpreter is a measurable overhead for these.  Plain Vars
-        * and Const seem to be the only ones where the intrinsic cost is small
-        * enough that the overhead of ExecInterpExpr matters.  For more complex
-        * expressions it's cheaper to use ExecInterpExpr always.
+        * the full interpreter is a measurable overhead for these, and these
+        * patterns occur often enough to be worth optimizing.
         */
        if (state->steps_len == 3)
        {
@@ -230,6 +227,13 @@ ExecReadyInterpretedExpr(ExprState *state)
                        state->evalfunc = ExecJustAssignScanVar;
                        return;
                }
+               else if (step0 == EEOP_CASE_TESTVAL &&
+                                step1 == EEOP_FUNCEXPR_STRICT &&
+                                state->steps[0].d.casetest.value)
+               {
+                       state->evalfunc = ExecJustApplyFuncToCase;
+                       return;
+               }
        }
        else if (state->steps_len == 2 &&
                         state->steps[0].opcode == EEOP_CONST)
@@ -1275,7 +1279,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
                EEO_CASE(EEOP_ARRAYCOERCE)
                {
                        /* too complex for an inline implementation */
-                       ExecEvalArrayCoerce(state, op);
+                       ExecEvalArrayCoerce(state, op, econtext);
 
                        EEO_NEXT();
                }
@@ -1811,6 +1815,43 @@ ExecJustAssignScanVar(ExprState *state, ExprContext *econtext, bool *isnull)
        return 0;
 }
 
+/* Evaluate CASE_TESTVAL and apply a strict function to it */
+static Datum
+ExecJustApplyFuncToCase(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+       ExprEvalStep *op = &state->steps[0];
+       FunctionCallInfo fcinfo;
+       bool       *argnull;
+       int                     argno;
+       Datum           d;
+
+       /*
+        * XXX with some redesign of the CaseTestExpr mechanism, maybe we could
+        * get rid of this data shuffling?
+        */
+       *op->resvalue = *op->d.casetest.value;
+       *op->resnull = *op->d.casetest.isnull;
+
+       op++;
+
+       fcinfo = op->d.func.fcinfo_data;
+       argnull = fcinfo->argnull;
+
+       /* strict function, so check for NULL args */
+       for (argno = 0; argno < op->d.func.nargs; argno++)
+       {
+               if (argnull[argno])
+               {
+                       *isnull = true;
+                       return (Datum) 0;
+               }
+       }
+       fcinfo->isnull = false;
+       d = op->d.func.fn_addr(fcinfo);
+       *isnull = fcinfo->isnull;
+       return d;
+}
+
 
 /*
  * Do one-time initialization of interpretation machinery.
@@ -2345,11 +2386,9 @@ ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op)
  * Source array is in step's result variable.
  */
 void
-ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op)
+ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 {
-       ArrayCoerceExpr *acoerce = op->d.arraycoerce.coerceexpr;
        Datum           arraydatum;
-       FunctionCallInfoData locfcinfo;
 
        /* NULL array -> NULL result */
        if (*op->resnull)
@@ -2361,7 +2400,7 @@ ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op)
         * 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))
+       if (op->d.arraycoerce.elemexprstate == NULL)
        {
                /* Detoast input array if necessary, and copy in any case */
                ArrayType  *array = DatumGetArrayTypePCopy(arraydatum);
@@ -2372,23 +2411,12 @@ ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op)
        }
 
        /*
-        * 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.
+        * Use array_map to apply the sub-expression to each array element.
         */
-       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->resvalue = array_map(arraydatum,
+                                                         op->d.arraycoerce.elemexprstate,
+                                                         econtext,
+                                                         op->d.arraycoerce.resultelemtype,
                                                          op->d.arraycoerce.amstate);
 }
 
index f1bed14e2bbc47f9ea7313ed9fa3e95ae8aef358..b274af26a4278e02cb7a2fb2ec88732efc6c6391 100644 (file)
@@ -1698,11 +1698,10 @@ _copyArrayCoerceExpr(const ArrayCoerceExpr *from)
        ArrayCoerceExpr *newnode = makeNode(ArrayCoerceExpr);
 
        COPY_NODE_FIELD(arg);
-       COPY_SCALAR_FIELD(elemfuncid);
+       COPY_NODE_FIELD(elemexpr);
        COPY_SCALAR_FIELD(resulttype);
        COPY_SCALAR_FIELD(resulttypmod);
        COPY_SCALAR_FIELD(resultcollid);
-       COPY_SCALAR_FIELD(isExplicit);
        COPY_SCALAR_FIELD(coerceformat);
        COPY_LOCATION_FIELD(location);
 
index 8b56b9146a1e7692a230d43a909141a6314c9ad2..5c839f4c31a6ace93292c0565656d86d5df44553 100644 (file)
@@ -513,11 +513,10 @@ static bool
 _equalArrayCoerceExpr(const ArrayCoerceExpr *a, const ArrayCoerceExpr *b)
 {
        COMPARE_NODE_FIELD(arg);
-       COMPARE_SCALAR_FIELD(elemfuncid);
+       COMPARE_NODE_FIELD(elemexpr);
        COMPARE_SCALAR_FIELD(resulttype);
        COMPARE_SCALAR_FIELD(resulttypmod);
        COMPARE_SCALAR_FIELD(resultcollid);
-       COMPARE_SCALAR_FIELD(isExplicit);
        COMPARE_COERCIONFORM_FIELD(coerceformat);
        COMPARE_LOCATION_FIELD(location);
 
index e3eb0c578877a514216021bfd39c0c1bb5a4f22d..8e6f27e1536864812a9ede92859019f29ff402ce 100644 (file)
@@ -1717,15 +1717,6 @@ check_functions_in_node(Node *node, check_function_callback checker,
                                        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;
@@ -2023,7 +2014,15 @@ expression_tree_walker(Node *node,
                case T_CoerceViaIO:
                        return walker(((CoerceViaIO *) node)->arg, context);
                case T_ArrayCoerceExpr:
-                       return walker(((ArrayCoerceExpr *) node)->arg, context);
+                       {
+                               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:
@@ -2705,6 +2704,7 @@ expression_tree_mutator(Node *node,
 
                                FLATCOPY(newnode, acoerce, ArrayCoerceExpr);
                                MUTATE(newnode->arg, acoerce->arg, Expr *);
+                               MUTATE(newnode->elemexpr, acoerce->elemexpr, Expr *);
                                return (Node *) newnode;
                        }
                        break;
index b83d919e408cac5680ddbc4005f135df2732f2c1..2532edc94a2b260949be08e333f5317e5251717f 100644 (file)
@@ -1394,11 +1394,10 @@ _outArrayCoerceExpr(StringInfo str, const ArrayCoerceExpr *node)
        WRITE_NODE_TYPE("ARRAYCOERCEEXPR");
 
        WRITE_NODE_FIELD(arg);
-       WRITE_OID_FIELD(elemfuncid);
+       WRITE_NODE_FIELD(elemexpr);
        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);
 }
index fbf8330735835fcbb01c09b6707ebb6202a8118c..07ba69178c84d30e49c41d9b9901acbe91c7d6ee 100644 (file)
@@ -892,11 +892,10 @@ _readArrayCoerceExpr(void)
        READ_LOCALS(ArrayCoerceExpr);
 
        READ_NODE_FIELD(arg);
-       READ_OID_FIELD(elemfuncid);
+       READ_NODE_FIELD(elemexpr);
        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);
 
index 0baf9785c98e1a31cd6213a9f28499aba6450d75..f76da490447049b99a8bd79409b4bc1db29a25be 100644 (file)
@@ -3632,11 +3632,14 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
        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);
+               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))
        {
index b0c9e9445978099d6e82fdc05b1ac98ea9c461ec..dee4414cec228c38420350385e8e85d402cd157f 100644 (file)
@@ -1395,12 +1395,6 @@ fix_expr_common(PlannerInfo *root, Node *node)
                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;
index 9d75e8612ae73bce94c066fa54bea549ca85f05f..d7db32ebf5e46df715a596db43c19430e42d667a 100644 (file)
@@ -306,9 +306,9 @@ expand_targetlist(List *tlist, int command_type,
                                                new_expr = coerce_to_domain(new_expr,
                                                                                                        InvalidOid, -1,
                                                                                                        atttype,
+                                                                                                       COERCION_IMPLICIT,
                                                                                                        COERCE_IMPLICIT_CAST,
                                                                                                        -1,
-                                                                                                       false,
                                                                                                        false);
                                        }
                                        else
index 93add27dbe2f0cb72bcf74a30df8f2e175bcafef..79613622805c20ca7c5439a92f8ea3445a6d6efe 100644 (file)
@@ -1361,6 +1361,17 @@ contain_nonstrict_functions_walker(Node *node, void *context)
                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))
@@ -1380,14 +1391,11 @@ contain_nonstrict_functions_walker(Node *node, void *context)
        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,
+       /* 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);
 }
@@ -1757,7 +1765,7 @@ find_nonnullable_rels_walker(Node *node, bool top_level)
        }
        else if (IsA(node, ArrayCoerceExpr))
        {
-               /* ArrayCoerceExpr is strict at the array level */
+               /* ArrayCoerceExpr is strict at the array level; ignore elemexpr */
                ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node;
 
                result = find_nonnullable_rels_walker((Node *) expr->arg, top_level);
@@ -1965,7 +1973,7 @@ find_nonnullable_vars_walker(Node *node, bool top_level)
        }
        else if (IsA(node, ArrayCoerceExpr))
        {
-               /* ArrayCoerceExpr is strict at the array level */
+               /* ArrayCoerceExpr is strict at the array level; ignore elemexpr */
                ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node;
 
                result = find_nonnullable_vars_walker((Node *) expr->arg, top_level);
@@ -3005,32 +3013,38 @@ eval_const_expressions_mutator(Node *node,
                        {
                                ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node;
                                Expr       *arg;
+                               Expr       *elemexpr;
                                ArrayCoerceExpr *newexpr;
 
                                /*
-                                * Reduce constants in the ArrayCoerceExpr's argument, then
-                                * build a new ArrayCoerceExpr.
+                                * 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->elemfuncid = expr->elemfuncid;
+                               newexpr->elemexpr = elemexpr;
                                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 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) &&
-                                       (!OidIsValid(newexpr->elemfuncid) ||
-                                        func_volatile(newexpr->elemfuncid) == PROVOLATILE_IMMUTABLE))
+                                       elemexpr && !IsA(elemexpr, CoerceToDomain) &&
+                                       !contain_mutable_functions((Node *) elemexpr))
                                        return (Node *) evaluate_expr((Expr *) newexpr,
                                                                                                  newexpr->resulttype,
                                                                                                  newexpr->resulttypmod,
index e79ad26e71658f319193d0f1ba2f97803c5752c3..53457dc2c8df30a694256941e3940a8a81f38545 100644 (file)
 
 static Node *coerce_type_typmod(Node *node,
                                   Oid targetTypeId, int32 targetTypMod,
-                                  CoercionForm cformat, int location,
-                                  bool isExplicit, bool hideInputCoercion);
+                                  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,
-                                                 CoercionForm cformat, int location,
-                                                 bool isExplicit);
+                                                 CoercionContext ccontext, CoercionForm cformat,
+                                                 int location);
 static Node *coerce_record_to_complex(ParseState *pstate, Node *node,
                                                 Oid targetTypeId,
                                                 CoercionContext ccontext,
@@ -110,8 +111,7 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype,
         */
        result = coerce_type_typmod(result,
                                                                targettype, targettypmod,
-                                                               cformat, location,
-                                                               (cformat != COERCE_IMPLICIT_CAST),
+                                                               ccontext, cformat, location,
                                                                (result != expr && !IsA(result, Const)));
 
        if (expr != origexpr)
@@ -355,7 +355,8 @@ coerce_type(ParseState *pstate, Node *node,
                        result = coerce_to_domain(result,
                                                                          baseTypeId, baseTypeMod,
                                                                          targetTypeId,
-                                                                         cformat, location, false, false);
+                                                                         ccontext, cformat, location,
+                                                                         false);
 
                ReleaseSysCache(baseType);
 
@@ -370,10 +371,10 @@ coerce_type(ParseState *pstate, Node *node,
                 * NULL to indicate we should proceed with normal coercion.
                 */
                result = pstate->p_coerce_param_hook(pstate,
-                                                                                                (Param *) node,
-                                                                                                targetTypeId,
-                                                                                                targetTypeMod,
-                                                                                                location);
+                                                                                        (Param *) node,
+                                                                                        targetTypeId,
+                                                                                        targetTypeMod,
+                                                                                        location);
                if (result)
                        return result;
        }
@@ -417,20 +418,17 @@ coerce_type(ParseState *pstate, Node *node,
 
                        result = build_coercion_expression(node, pathtype, funcId,
                                                                                           baseTypeId, baseTypeMod,
-                                                                                          cformat, location,
-                                                                                          (cformat != COERCE_IMPLICIT_CAST));
+                                                                                          ccontext, cformat, location);
 
                        /*
                         * 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.
+                        * type ID, hiding the previous coercion node.
                         */
                        if (targetTypeId != baseTypeId)
                                result = coerce_to_domain(result, baseTypeId, baseTypeMod,
                                                                                  targetTypeId,
-                                                                                 cformat, location, true,
-                                                                                 exprIsLengthCoercion(result,
-                                                                                                                          NULL));
+                                                                                 ccontext, cformat, location,
+                                                                                 true);
                }
                else
                {
@@ -444,7 +442,8 @@ coerce_type(ParseState *pstate, Node *node,
                         * then we won't need a RelabelType node.
                         */
                        result = coerce_to_domain(node, InvalidOid, -1, targetTypeId,
-                                                                         cformat, location, false, false);
+                                                                         ccontext, cformat, location,
+                                                                         false);
                        if (result == node)
                        {
                                /*
@@ -636,19 +635,17 @@ can_coerce_type(int nargs, Oid *input_typeids, Oid *target_typeids,
  * '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
+ * 'ccontext': context indicator to control coercions
+ * 'cformat': coercion display 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)
+                                CoercionContext ccontext, CoercionForm cformat, int location,
+                                bool hideInputCoercion)
 {
        CoerceToDomain *result;
 
@@ -677,14 +674,9 @@ coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId,
         * 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);
-       }
+       arg = coerce_type_typmod(arg, baseTypeId, baseTypeMod,
+                                                        ccontext, COERCE_IMPLICIT_CAST, location,
+                                                        false);
 
        /*
         * Now build the domain coercion node.  This represents run-time checking
@@ -714,11 +706,14 @@ coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId,
  * 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.
+ * 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
@@ -726,8 +721,9 @@ coerce_to_domain(Node *arg, Oid baseTypeId, int32 baseTypeMod, Oid typeId,
  */
 static Node *
 coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod,
-                                  CoercionForm cformat, int location,
-                                  bool isExplicit, bool hideInputCoercion)
+                                  CoercionContext ccontext, CoercionForm cformat,
+                                  int location,
+                                  bool hideInputCoercion)
 {
        CoercionPathType pathtype;
        Oid                     funcId;
@@ -749,8 +745,7 @@ coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod,
 
                node = build_coercion_expression(node, pathtype, funcId,
                                                                                 targetTypeId, targetTypMod,
-                                                                                cformat, location,
-                                                                                isExplicit);
+                                                                                ccontext, cformat, location);
        }
 
        return node;
@@ -799,8 +794,8 @@ build_coercion_expression(Node *node,
                                                  CoercionPathType pathtype,
                                                  Oid funcId,
                                                  Oid targetTypeId, int32 targetTypMod,
-                                                 CoercionForm cformat, int location,
-                                                 bool isExplicit)
+                                                 CoercionContext ccontext, CoercionForm cformat,
+                                                 int location)
 {
        int                     nargs = 0;
 
@@ -865,7 +860,7 @@ build_coercion_expression(Node *node,
                                                         -1,
                                                         InvalidOid,
                                                         sizeof(bool),
-                                                        BoolGetDatum(isExplicit),
+                                                        BoolGetDatum(ccontext == COERCION_EXPLICIT),
                                                         false,
                                                         true);
 
@@ -881,19 +876,52 @@ build_coercion_expression(Node *node,
        {
                /* 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->elemfuncid = funcId;
+               acoerce->elemexpr = (Expr *) elemexpr;
                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.
+                * 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 = (nargs >= 2) ? targetTypMod : -1;
+               acoerce->resulttypmod = exprTypmod(elemexpr);
                /* resultcollid will be set by parse_collate.c */
-               acoerce->isExplicit = isExplicit;
                acoerce->coerceformat = cformat;
                acoerce->location = location;
 
@@ -2148,8 +2176,7 @@ IsBinaryCoercible(Oid srctype, Oid targettype)
  *     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
+ *                             *funcid is set to InvalidOid
  *     COERCION_PATH_COERCEVIAIO: need a CoerceViaIO node
  *                             *funcid is set to InvalidOid
  *
@@ -2235,11 +2262,8 @@ find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId,
        {
                /*
                 * 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.
+                * 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
@@ -2254,7 +2278,7 @@ find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId,
                        Oid                     sourceElem;
 
                        if ((targetElem = get_element_type(targetTypeId)) != InvalidOid &&
-                               (sourceElem = get_base_element_type(sourceTypeId)) != InvalidOid)
+                               (sourceElem = get_element_type(sourceTypeId)) != InvalidOid)
                        {
                                CoercionPathType elempathtype;
                                Oid                     elemfuncid;
@@ -2263,14 +2287,9 @@ find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId,
                                                                                                         sourceElem,
                                                                                                         ccontext,
                                                                                                         &elemfuncid);
-                               if (elempathtype != COERCION_PATH_NONE &&
-                                       elempathtype != COERCION_PATH_ARRAYCOERCE)
+                               if (elempathtype != COERCION_PATH_NONE)
                                {
-                                       *funcid = elemfuncid;
-                                       if (elempathtype == COERCION_PATH_COERCEVIAIO)
-                                               result = COERCION_PATH_COERCEVIAIO;
-                                       else
-                                               result = COERCION_PATH_ARRAYCOERCE;
+                                       result = COERCION_PATH_ARRAYCOERCE;
                                }
                        }
                }
@@ -2311,7 +2330,9 @@ find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId,
  * 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.
+ * 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:
index ef52dd5b955eaa2d683d5082ea67d687c0c41cc5..7054d4f77d994fc48fda704d97cb062b048ff88d 100644 (file)
@@ -875,9 +875,9 @@ rewriteTargetListIU(List *targetList,
                                        new_expr = coerce_to_domain(new_expr,
                                                                                                InvalidOid, -1,
                                                                                                att_tup->atttypid,
+                                                                                               COERCION_IMPLICIT,
                                                                                                COERCE_IMPLICIT_CAST,
                                                                                                -1,
-                                                                                               false,
                                                                                                false);
                                }
                        }
@@ -1271,9 +1271,9 @@ rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation, List *attrnos)
                                        new_expr = coerce_to_domain(new_expr,
                                                                                                InvalidOid, -1,
                                                                                                att_tup->atttypid,
+                                                                                               COERCION_IMPLICIT,
                                                                                                COERCE_IMPLICIT_CAST,
                                                                                                -1,
-                                                                                               false,
                                                                                                false);
                                }
                                newList = lappend(newList, new_expr);
index 5c17213720731844b1df24c4fea9f4c428a98e92..c5773efd19296d2562beb17dd8c18a1ff14e9739 100644 (file)
@@ -1429,9 +1429,9 @@ ReplaceVarsFromTargetList_callback(Var *var,
                                                                                                                           var->varcollid),
                                                                                InvalidOid, -1,
                                                                                var->vartype,
+                                                                               COERCION_IMPLICIT,
                                                                                COERCE_IMPLICIT_CAST,
                                                                                -1,
-                                                                               false,
                                                                                false);
                }
                elog(ERROR, "could not find replacement targetlist entry for attno %d",
index d1f2fe7d95854429df2f7eb09a4558559f8b23af..ca04b13e825abb8e6fc9bcc6a1b3fed3250e04d7 100644 (file)
@@ -3092,21 +3092,18 @@ array_set(ArrayType *array, int nSubscripts, int *indx,
 /*
  * 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.
+ * 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:
- * * 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.
+ * * 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 fn().
+ *      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.
  *
@@ -3116,11 +3113,14 @@ array_set(ArrayType *array, int nSubscripts, int *indx,
  *
  * 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(FunctionCallInfo fcinfo, Oid retType, ArrayMapState *amstate)
+array_map(Datum arrayd,
+                 ExprState *exprstate, ExprContext *econtext,
+                 Oid retType, ArrayMapState *amstate)
 {
-       AnyArrayType *v;
+       AnyArrayType *v = DatumGetAnyArrayP(arrayd);
        ArrayType  *result;
        Datum      *values;
        bool       *nulls;
@@ -3141,13 +3141,8 @@ array_map(FunctionCallInfo fcinfo, Oid retType, ArrayMapState *amstate)
        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_P(0);
+       Datum      *transform_source = exprstate->innermost_caseval;
+       bool       *transform_source_isnull = exprstate->innermost_casenull;
 
        inpType = AARR_ELEMTYPE(v);
        ndim = AARR_NDIM(v);
@@ -3158,7 +3153,7 @@ array_map(FunctionCallInfo fcinfo, Oid retType, ArrayMapState *amstate)
        if (nitems <= 0)
        {
                /* Return empty array */
-               PG_RETURN_ARRAYTYPE_P(construct_empty_array(retType));
+               return PointerGetDatum(construct_empty_array(retType));
        }
 
        /*
@@ -3203,39 +3198,15 @@ array_map(FunctionCallInfo fcinfo, Oid retType, ArrayMapState *amstate)
 
        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;
+               *transform_source =
+                       array_iter_next(&iter, transform_source_isnull, i,
+                                                       inp_typlen, inp_typbyval, inp_typalign);
 
-                       for (j = 0; j < fcinfo->nargs; j++)
-                       {
-                               if (fcinfo->argnull[j])
-                               {
-                                       callit = false;
-                                       break;
-                               }
-                       }
-               }
+               /* Apply the given expression to source element */
+               values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]);
 
-               if (callit)
-               {
-                       fcinfo->isnull = false;
-                       values[i] = FunctionCallInvoke(fcinfo);
-               }
-               else
-                       fcinfo->isnull = true;
-
-               nulls[i] = fcinfo->isnull;
-               if (fcinfo->isnull)
+               if (nulls[i])
                        hasnulls = true;
                else
                {
@@ -3254,7 +3225,7 @@ array_map(FunctionCallInfo fcinfo, Oid retType, ArrayMapState *amstate)
                }
        }
 
-       /* Allocate and initialize the result array */
+       /* Allocate and fill the result array */
        if (hasnulls)
        {
                dataoffset = ARR_OVERHEAD_WITHNULLS(ndim, nitems);
@@ -3273,18 +3244,18 @@ array_map(FunctionCallInfo fcinfo, Oid retType, ArrayMapState *amstate)
        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);
 
+       /*
+        * Note: do not risk trying to pfree the results of the called expression
+        */
        pfree(values);
        pfree(nulls);
 
-       PG_RETURN_ARRAYTYPE_P(result);
+       return PointerGetDatum(result);
 }
 
 /*
index db1792bf8d4091474962db368d39a43a24d97310..7361e9d43caa58320c715865a5a19b17400b2a0d 100644 (file)
@@ -1816,10 +1816,19 @@ strip_array_coercion(Node *node)
 {
        for (;;)
        {
-               if (node && IsA(node, ArrayCoerceExpr) &&
-                       ((ArrayCoerceExpr *) node)->elemfuncid == InvalidOid)
+               if (node && IsA(node, ArrayCoerceExpr))
                {
-                       node = (Node *) ((ArrayCoerceExpr *) node)->arg;
+                       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))
                {
index a7b07827e01c95f032cb6ac3060d1cab55715f06..919733517bda369f80f27e77bf0e6e2c9aa003e6 100644 (file)
@@ -1941,8 +1941,6 @@ get_call_expr_argtype(Node *expr, int argnum)
                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))
@@ -1956,16 +1954,12 @@ get_call_expr_argtype(Node *expr, int argnum)
        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.
+        * 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);
-       else if (IsA(expr, ArrayCoerceExpr) &&
-                        argnum == 0)
-               argtype = get_base_element_type(argtype);
 
        return argtype;
 }
@@ -2012,8 +2006,6 @@ get_call_expr_arg_stable(Node *expr, int argnum)
                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))
index 5d57a95d8bb222fe3cc7706e623d2f1e832e8fd6..2c382a73cfbb2b87be3cfbd5b8536c5a75afb61f 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201709191
+#define CATALOG_VERSION_NO     201709301
 
 #endif
index 8ee0496e0106b6b06c7d1fb3ae5ad8d34b32a512..78d22478166fac22fc474931ec7c76ac3887d498 100644 (file)
@@ -385,10 +385,8 @@ typedef struct ExprEvalStep
                /* for EEOP_ARRAYCOERCE */
                struct
                {
-                       ArrayCoerceExpr *coerceexpr;
+                       ExprState  *elemexprstate;      /* null if no per-element work */
                        Oid                     resultelemtype; /* element type of result array */
-                       FmgrInfo   *elemfunc;   /* lookup info for element coercion
-                                                                        * function */
                        struct ArrayMapState *amstate;  /* workspace for array_map */
                }                       arraycoerce;
 
@@ -621,7 +619,8 @@ extern void ExecEvalRowNull(ExprState *state, ExprEvalStep *op,
 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 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,
index 8c536a8d38d8c2fb811b4d481125d6be4c522a33..ccb5123e2ec64e657d9a0d0f049bbbc454579e7b 100644 (file)
@@ -820,11 +820,12 @@ typedef struct CoerceViaIO
  * 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).
+ * 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.
  * ----------------
  */
 
@@ -832,11 +833,10 @@ typedef struct ArrayCoerceExpr
 {
        Expr            xpr;
        Expr       *arg;                        /* input expression (yields an array) */
-       Oid                     elemfuncid;             /* OID of element coercion function, or 0 */
+       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 */
-       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;
index 06f65293cb39ddf2ee44e5bfc8c2e335dc846541..e560f0c96e44331f95bbf82dd0f3daf473e19ad8 100644 (file)
@@ -48,9 +48,8 @@ extern Node *coerce_type(ParseState *pstate, Node *node,
                        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);
+                                CoercionContext ccontext, CoercionForm cformat, int location,
+                                bool hideInputCoercion);
 
 extern Node *coerce_to_boolean(ParseState *pstate, Node *node,
                                  const char *constructName);
index d6d3c582b6a8613443a04c6fade0e075a24ec353..cc19879a9a7336eb288de75c6de822af55aac320 100644 (file)
 #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
@@ -360,8 +364,9 @@ extern ArrayType *array_set(ArrayType *array, int nSubscripts, int *indx,
                  Datum dataValue, bool isNull,
                  int arraytyplen, int elmlen, bool elmbyval, char elmalign);
 
-extern Datum array_map(FunctionCallInfo fcinfo, Oid retType,
-                 ArrayMapState *amstate);
+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,
index 3acc696863d17480a2a6dbff0369f3224bb30e8e..1e62c57a688636f093a0a32ba2aa48c3e66ad335 100644 (file)
@@ -310,6 +310,101 @@ Rules:
 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);
index 0fd383e2721a612184630567936fb107e7c5ad88..8fb3e2086a12d2a315814510d403c77c0d2a56c0 100644 (file)
@@ -166,6 +166,49 @@ drop table dcomptable;
 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;