APP_JUMB(acexpr->resulttype);
JumbleExpr(jstate, (Node *) acexpr->arg);
+ JumbleExpr(jstate, (Node *) acexpr->elemexpr);
}
break;
case T_ConvertRowtypeExpr:
<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">
{
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))
DefineDomain(CreateDomainStmt *stmt)
{
char *domainName;
+ char *domainArrayName;
Oid domainNamespace;
AclResult aclresult;
int16 internalLength;
Oid basetypeoid;
Oid old_type_oid;
Oid domaincoll;
+ Oid domainArrayOid;
Form_pg_type baseType;
int32 basetypeMod;
Oid baseColl;
}
}
+ /* Allocate OID for array type */
+ domainArrayOid = AssignTypeArrayOid();
+
/*
* Have TypeCreate do all the real work.
*/
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) */
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
*/
errmsg("type \"%s\" already exists", enumName)));
}
+ /* Allocate OID for array type */
enumArrayOid = AssignTypeArrayOid();
/* Create the pg_type entry */
{
ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
Oid resultelemtype;
+ ExprState *elemstate;
/* evaluate argument into step's result area */
ExecInitExprRec(acoerce->arg, parent, state, resv, resnull);
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;
}
*
* 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
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);
/*
/*
* 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)
{
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)
EEO_CASE(EEOP_ARRAYCOERCE)
{
/* too complex for an inline implementation */
- ExecEvalArrayCoerce(state, op);
+ ExecEvalArrayCoerce(state, op, econtext);
EEO_NEXT();
}
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.
* 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)
* 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);
}
/*
- * 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);
}
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);
_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);
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;
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:
FLATCOPY(newnode, acoerce, ArrayCoerceExpr);
MUTATE(newnode->arg, acoerce->arg, Expr *);
+ MUTATE(newnode->elemexpr, acoerce->elemexpr, Expr *);
return (Node *) newnode;
}
break;
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);
}
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);
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))
{
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;
new_expr = coerce_to_domain(new_expr,
InvalidOid, -1,
atttype,
+ COERCION_IMPLICIT,
COERCE_IMPLICIT_CAST,
-1,
- false,
false);
}
else
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))
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);
}
}
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);
}
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);
{
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,
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,
*/
result = coerce_type_typmod(result,
targettype, targettypmod,
- cformat, location,
- (cformat != COERCE_IMPLICIT_CAST),
+ ccontext, cformat, location,
(result != expr && !IsA(result, Const)));
if (expr != origexpr)
result = coerce_to_domain(result,
baseTypeId, baseTypeMod,
targetTypeId,
- cformat, location, false, false);
+ ccontext, cformat, location,
+ false);
ReleaseSysCache(baseType);
* 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;
}
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
{
* 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)
{
/*
* '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;
* 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
* 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
*/
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;
node = build_coercion_expression(node, pathtype, funcId,
targetTypeId, targetTypMod,
- cformat, location,
- isExplicit);
+ ccontext, cformat, location);
}
return node;
CoercionPathType pathtype,
Oid funcId,
Oid targetTypeId, int32 targetTypMod,
- CoercionForm cformat, int location,
- bool isExplicit)
+ CoercionContext ccontext, CoercionForm cformat,
+ int location)
{
int nargs = 0;
-1,
InvalidOid,
sizeof(bool),
- BoolGetDatum(isExplicit),
+ BoolGetDatum(ccontext == COERCION_EXPLICIT),
false,
true);
{
/* 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;
* 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
*
{
/*
* 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
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;
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;
}
}
}
* 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:
new_expr = coerce_to_domain(new_expr,
InvalidOid, -1,
att_tup->atttypid,
+ COERCION_IMPLICIT,
COERCE_IMPLICIT_CAST,
-1,
- false,
false);
}
}
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);
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",
/*
* 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.
*
*
* 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;
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);
if (nitems <= 0)
{
/* Return empty array */
- PG_RETURN_ARRAYTYPE_P(construct_empty_array(retType));
+ return PointerGetDatum(construct_empty_array(retType));
}
/*
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
{
}
}
- /* Allocate and initialize the result array */
+ /* Allocate and fill the result array */
if (hasnulls)
{
dataoffset = ARR_OVERHEAD_WITHNULLS(ndim, nitems);
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);
}
/*
{
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))
{
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))
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;
}
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))
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201709191
+#define CATALOG_VERSION_NO 201709301
#endif
/* 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;
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,
* 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.
* ----------------
*/
{
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;
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);
#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
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,
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);
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;