X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Fparser%2Fparse_coerce.c;h=5a343e768dda1a9492fb68f45a4c259e1ae88925;hb=147d4bf3e5e3da2ee0f0cc132718ab1c4912a877;hp=eba6f5a13334d907606dae7b6ac74e8b4536eb6e;hpb=b3c0551edaf390ab7bde4ebcc2299d1b0da686c5;p=postgresql diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index eba6f5a133..5a343e768d 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -3,12 +3,12 @@ * parse_coerce.c * handle type coercions/conversions for parser * - * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.101 2003/06/27 00:33:25 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.136 2006/04/04 19:35:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -19,21 +19,31 @@ #include "nodes/makefuncs.h" #include "nodes/params.h" #include "optimizer/clauses.h" +#include "parser/parsetree.h" #include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "parser/parse_func.h" +#include "parser/parse_relation.h" #include "parser/parse_type.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/syscache.h" +#include "utils/typcache.h" static Node *coerce_type_typmod(Node *node, - Oid targetTypeId, int32 targetTypMod, - CoercionForm cformat, bool isExplicit); -static Node *build_func_call(Oid funcid, Oid rettype, List *args, - CoercionForm fformat); + Oid targetTypeId, int32 targetTypMod, + CoercionForm cformat, bool isExplicit, + bool hideInputCoercion); +static void hide_coercion_node(Node *node); +static Node *build_coercion_expression(Node *node, Oid funcId, + Oid targetTypeId, int32 targetTypMod, + CoercionForm cformat, bool isExplicit); +static Node *coerce_record_to_complex(ParseState *pstate, Node *node, + Oid targetTypeId, + CoercionContext ccontext, + CoercionForm cformat); /* @@ -41,12 +51,12 @@ static Node *build_func_call(Oid funcid, Oid rettype, List *args, * Convert an expression to a target type and typmod. * * This is the general-purpose entry point for arbitrary type coercion - * operations. Direct use of the component operations can_coerce_type, + * operations. Direct use of the component operations can_coerce_type, * coerce_type, and coerce_type_typmod should be restricted to special * cases (eg, when the conversion is expected to succeed). * * Returns the possibly-transformed expression tree, or NULL if the type - * conversion is not possible. (We do this, rather than elog'ing directly, + * conversion is not possible. (We do this, rather than ereport'ing directly, * so that callers can generate custom error messages indicating context.) * * pstate - parse state (can be NULL, see coerce_type) @@ -62,63 +72,27 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype, CoercionContext ccontext, CoercionForm cformat) { - if (can_coerce_type(1, &exprtype, &targettype, ccontext)) - expr = coerce_type(pstate, expr, exprtype, targettype, - ccontext, cformat); - else if (ccontext >= COERCION_ASSIGNMENT) - { - /* - * String hacks to get transparent conversions for char and varchar: - * if a coercion to text is available, use it for forced coercions to - * char(n) or varchar(n) or domains thereof. - * - * This is pretty grotty, but seems easier to maintain than providing - * entries in pg_cast that parallel all the ones for text. - */ - Oid targetbasetype = getBaseType(targettype); + Node *result; - if (targetbasetype == BPCHAROID || targetbasetype == VARCHAROID) - { - Oid text_id = TEXTOID; + if (!can_coerce_type(1, &exprtype, &targettype, ccontext)) + return NULL; - if (can_coerce_type(1, &exprtype, &text_id, ccontext)) - { - expr = coerce_type(pstate, expr, exprtype, text_id, - ccontext, cformat); - if (targetbasetype != targettype) - { - /* need to coerce to domain over char or varchar */ - expr = coerce_to_domain(expr, targetbasetype, targettype, - cformat); - } - else - { - /* need a RelabelType if no typmod coercion will be performed */ - if (targettypmod < 0) - expr = (Node *) makeRelabelType((Expr *) expr, - targettype, -1, - cformat); - } - } - else - expr = NULL; - } - else - expr = NULL; - } - else - expr = NULL; + result = coerce_type(pstate, expr, exprtype, + targettype, targettypmod, + ccontext, cformat); /* - * If the target is a fixed-length type, it may need a length coercion - * as well as a type coercion. + * If the target is a fixed-length type, it may need a length coercion as + * well as a type coercion. If we find ourselves adding both, force the + * inner coercion node to implicit display form. */ - if (expr != NULL) - expr = coerce_type_typmod(expr, targettype, targettypmod, - cformat, - (cformat != COERCE_IMPLICIT_CAST)); + result = coerce_type_typmod(result, + targettype, targettypmod, + cformat, + (cformat != COERCE_IMPLICIT_CAST), + (result != expr && !IsA(result, Const))); - return expr; + return result; } @@ -129,18 +103,21 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype, * The caller should already have determined that the coercion is possible; * see can_coerce_type. * - * No coercion to a typmod (length) is performed here. The caller must - * call coerce_type_typmod as well, if a typmod constraint is wanted. + * Normally, no coercion to a typmod (length) is performed here. The caller + * must call coerce_type_typmod as well, if a typmod constraint is wanted. * (But if the target type is a domain, it may internally contain a * typmod constraint, which will be applied inside coerce_to_domain.) + * In some cases pg_cast specifies a type coercion function that also + * applies length conversion, and in those cases only, the result will + * already be properly coerced to the specified typmod. * * pstate is only used in the case that we are able to resolve the type of - * a previously UNKNOWN Param. It is okay to pass pstate = NULL if the + * a previously UNKNOWN Param. It is okay to pass pstate = NULL if the * caller does not want type information updated for Params. */ Node * coerce_type(ParseState *pstate, Node *node, - Oid inputTypeId, Oid targetTypeId, + Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod, CoercionContext ccontext, CoercionForm cformat) { Node *result; @@ -152,12 +129,20 @@ coerce_type(ParseState *pstate, Node *node, /* no conversion needed */ return node; } + if (targetTypeId == ANYOID || + targetTypeId == ANYARRAYOID || + targetTypeId == ANYELEMENTOID) + { + /* assume can_coerce_type verified that implicit coercion is okay */ + /* NB: we do NOT want a RelabelType here */ + return node; + } if (inputTypeId == UNKNOWNOID && IsA(node, Const)) { /* - * Input is a string constant with previously undetermined type. - * Apply the target type's typinput function to it to produce a - * constant of the target type. + * Input is a string constant with previously undetermined type. Apply + * the target type's typinput function to it to produce a constant of + * the target type. * * NOTE: this case cannot be folded together with the other * constant-input case, since the typinput function does not @@ -166,10 +151,10 @@ coerce_type(ParseState *pstate, Node *node, * float-to-int type conversion will round to integer. * * XXX if the typinput function is not immutable, we really ought to - * postpone evaluation of the function call until runtime. But - * there is no way to represent a typinput function call as an - * expression tree, because C-string values are not Datums. (XXX - * This *is* possible as of 7.3, do we want to do it?) + * postpone evaluation of the function call until runtime. But there + * is no way to represent a typinput function call as an expression + * tree, because C-string values are not Datums. (XXX This *is* + * possible as of 7.3, do we want to do it?) */ Const *con = (Const *) node; Const *newcon = makeNode(Const); @@ -181,31 +166,28 @@ coerce_type(ParseState *pstate, Node *node, newcon->constbyval = typeByVal(targetType); newcon->constisnull = con->constisnull; + /* + * We pass typmod -1 to the input routine, primarily because + * existing input routines follow implicit-coercion semantics for + * length checks, which is not always what we want here. Any + * length constraint will be applied later by our caller. + * + * We assume here that UNKNOWN's internal representation is the + * same as CSTRING. + */ if (!con->constisnull) - { - char *val = DatumGetCString(DirectFunctionCall1(unknownout, - con->constvalue)); - - /* - * We pass typmod -1 to the input routine, primarily because - * existing input routines follow implicit-coercion semantics - * for length checks, which is not always what we want here. - * Any length constraint will be applied later by our caller. - * - * Note that we call stringTypeDatum using the domain's pg_type - * row, if it's a domain. This works because the domain row has - * the same typinput and typelem as the base type --- ugly... - */ - newcon->constvalue = stringTypeDatum(targetType, val, -1); - pfree(val); - } + newcon->constvalue = stringTypeDatum(targetType, + DatumGetCString(con->constvalue), + -1); + else + newcon->constvalue = stringTypeDatum(targetType, NULL, -1); result = (Node *) newcon; /* If target is a domain, apply constraints. */ if (targetTyptype == 'd') result = coerce_to_domain(result, InvalidOid, targetTypeId, - cformat); + cformat, false, false); ReleaseSysCache(targetType); @@ -216,8 +198,8 @@ coerce_type(ParseState *pstate, Node *node, pstate != NULL && pstate->p_variableparams) { /* - * Input is a Param of previously undetermined type, and we want - * to update our knowledge of the Param's type. Find the topmost + * Input is a Param of previously undetermined type, and we want to + * update our knowledge of the Param's type. Find the topmost * ParseState and update the state. */ Param *param = (Param *) node; @@ -230,37 +212,36 @@ coerce_type(ParseState *pstate, Node *node, if (paramno <= 0 || /* shouldn't happen, but... */ paramno > toppstate->p_numparams) - elog(ERROR, "Parameter '$%d' is out of range", paramno); + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_PARAMETER), + errmsg("there is no parameter $%d", paramno))); - if (toppstate->p_paramtypes[paramno-1] == UNKNOWNOID) + if (toppstate->p_paramtypes[paramno - 1] == UNKNOWNOID) { /* We've successfully resolved the type */ - toppstate->p_paramtypes[paramno-1] = targetTypeId; + toppstate->p_paramtypes[paramno - 1] = targetTypeId; } - else if (toppstate->p_paramtypes[paramno-1] == targetTypeId) + else if (toppstate->p_paramtypes[paramno - 1] == targetTypeId) { /* We previously resolved the type, and it matches */ } else { /* Ooops */ - elog(ERROR, "Inconsistent types deduced for parameter '$%d'" - "\n\tCould be either %s or %s", - paramno, - format_type_be(toppstate->p_paramtypes[paramno-1]), - format_type_be(targetTypeId)); + ereport(ERROR, + (errcode(ERRCODE_AMBIGUOUS_PARAMETER), + errmsg("inconsistent types deduced for parameter $%d", + paramno), + errdetail("%s versus %s", + format_type_be(toppstate->p_paramtypes[paramno - 1]), + format_type_be(targetTypeId)))); } param->paramtype = targetTypeId; - return (Node *) param; - } - if (targetTypeId == ANYOID || - targetTypeId == ANYARRAYOID || - targetTypeId == ANYELEMENTOID) - { - /* assume can_coerce_type verified that implicit coercion is okay */ - /* NB: we do NOT want a RelabelType here */ - return node; + + /* Apply domain constraints, if necessary */ + return coerce_to_domain((Node *) param, InvalidOid, targetTypeId, + cformat, false, false); } if (find_coercion_pathway(targetTypeId, inputTypeId, ccontext, &funcId)) @@ -268,38 +249,49 @@ coerce_type(ParseState *pstate, Node *node, if (OidIsValid(funcId)) { /* - * Generate an expression tree representing run-time - * application of the conversion function. If we are dealing - * with a domain target type, the conversion function will - * yield the base type. + * Generate an expression tree representing run-time application + * of the conversion function. If we are dealing with a domain + * target type, the conversion function will yield the base type, + * and we need to extract the correct typmod to use from the + * domain's typtypmod. */ Oid baseTypeId = getBaseType(targetTypeId); + int32 baseTypeMod; - result = build_func_call(funcId, baseTypeId, makeList1(node), - cformat); + if (targetTypeId != baseTypeId) + baseTypeMod = get_typtypmod(targetTypeId); + else + baseTypeMod = targetTypeMod; + + result = build_coercion_expression(node, funcId, + baseTypeId, baseTypeMod, + cformat, + (cformat != COERCE_IMPLICIT_CAST)); /* - * If domain, coerce to the domain type and relabel with - * domain type ID + * If domain, coerce to the domain type and relabel with domain + * type ID. We can skip the internal length-coercion step if the + * selected coercion function was a type-and-length coercion. */ if (targetTypeId != baseTypeId) result = coerce_to_domain(result, baseTypeId, targetTypeId, - cformat); + cformat, true, + exprIsLengthCoercion(result, + NULL)); } else { /* - * We don't need to do a physical conversion, but we do need - * to attach a RelabelType node so that the expression will be - * seen to have the intended type when inspected by - * higher-level code. + * We don't need to do a physical conversion, but we do need to + * attach a RelabelType node so that the expression will be seen + * to have the intended type when inspected by higher-level code. * * Also, domains may have value restrictions beyond the base type - * that must be accounted for. If the destination is a domain + * that must be accounted for. If the destination is a domain * then we won't need a RelabelType node. */ result = coerce_to_domain(node, InvalidOid, targetTypeId, - cformat); + cformat, false, false); if (result == node) { /* @@ -315,19 +307,36 @@ coerce_type(ParseState *pstate, Node *node, } return result; } + if (inputTypeId == RECORDOID && + ISCOMPLEX(targetTypeId)) + { + /* Coerce a RECORD to a specific complex type */ + return coerce_record_to_complex(pstate, node, targetTypeId, + ccontext, cformat); + } + if (targetTypeId == RECORDOID && + ISCOMPLEX(inputTypeId)) + { + /* Coerce a specific complex type to RECORD */ + /* NB: we do NOT want a RelabelType here */ + return node; + } if (typeInheritsFrom(inputTypeId, targetTypeId)) { /* - * Input class type is a subclass of target, so nothing to do --- - * except relabel the type. This is binary compatibility for - * complex types. + * Input class type is a subclass of target, so generate an + * appropriate runtime conversion (removing unneeded columns and + * possibly rearranging the ones that are wanted). */ - return (Node *) makeRelabelType((Expr *) node, - targetTypeId, -1, - cformat); + ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); + + r->arg = (Expr *) node; + r->resulttype = targetTypeId; + r->convertformat = cformat; + return (Node *) r; } /* If we get here, caller blew it */ - elog(ERROR, "coerce_type: no conversion function from %s to %s", + elog(ERROR, "failed to find conversion function from %s to %s", format_type_be(inputTypeId), format_type_be(targetTypeId)); return NULL; /* keep compiler quiet */ } @@ -358,23 +367,6 @@ can_coerce_type(int nargs, Oid *input_typeids, Oid *target_typeids, if (inputTypeId == targetTypeId) continue; - /* don't choke on references to no-longer-existing types */ - if (!typeidIsValid(inputTypeId)) - return false; - if (!typeidIsValid(targetTypeId)) - return false; - - /* - * If input is an untyped string constant, assume we can convert - * it to anything except a class type. - */ - if (inputTypeId == UNKNOWNOID) - { - if (ISCOMPLEX(targetTypeId)) - return false; - continue; - } - /* accept if target is ANY */ if (targetTypeId == ANYOID) continue; @@ -383,18 +375,40 @@ can_coerce_type(int nargs, Oid *input_typeids, Oid *target_typeids, if (targetTypeId == ANYARRAYOID || targetTypeId == ANYELEMENTOID) { - have_generics = true; /* do more checking later */ + have_generics = true; /* do more checking later */ continue; } /* - * If pg_cast shows that we can coerce, accept. This test now - * covers both binary-compatible and coercion-function cases. + * If input is an untyped string constant, assume we can convert it to + * anything. + */ + if (inputTypeId == UNKNOWNOID) + continue; + + /* + * If pg_cast shows that we can coerce, accept. This test now covers + * both binary-compatible and coercion-function cases. */ if (find_coercion_pathway(targetTypeId, inputTypeId, ccontext, &funcId)) continue; + /* + * If input is RECORD and target is a composite type, assume we can + * coerce (may need tighter checking here) + */ + if (inputTypeId == RECORDOID && + ISCOMPLEX(targetTypeId)) + continue; + + /* + * If input is a composite type and target is RECORD, accept + */ + if (targetTypeId == RECORDOID && + ISCOMPLEX(inputTypeId)) + continue; + /* * If input is a class type that inherits from target, accept */ @@ -427,14 +441,17 @@ can_coerce_type(int nargs, Oid *input_typeids, Oid *target_typeids, * has not bothered to look this up) * 'typeId': target type to coerce to * 'cformat': coercion format + * 'hideInputCoercion': if true, hide the input coercion under this one. + * 'lengthCoercionDone': if true, caller already accounted for length. * * If the target type isn't a domain, the given 'arg' is returned as-is. */ Node * -coerce_to_domain(Node *arg, Oid baseTypeId, Oid typeId, CoercionForm cformat) +coerce_to_domain(Node *arg, Oid baseTypeId, Oid typeId, + CoercionForm cformat, bool hideInputCoercion, + bool lengthCoercionDone) { CoerceToDomain *result; - int32 typmod; /* Get the base type if it hasn't been supplied */ if (baseTypeId == InvalidOid) @@ -444,6 +461,10 @@ coerce_to_domain(Node *arg, Oid baseTypeId, Oid typeId, CoercionForm cformat) if (baseTypeId == typeId) return arg; + /* Suppress display of nested coercion steps */ + if (hideInputCoercion) + hide_coercion_node(arg); + /* * If the domain applies a typmod to its base type, build the appropriate * coercion step. Mark it implicit for display purposes, because we don't @@ -457,16 +478,21 @@ coerce_to_domain(Node *arg, Oid baseTypeId, Oid typeId, CoercionForm cformat) * would be safe to do anyway, without lots of knowledge about what the * base type thinks the typmod means. */ - typmod = get_typtypmod(typeId); - if (typmod >= 0) - arg = coerce_type_typmod(arg, baseTypeId, typmod, - COERCE_IMPLICIT_CAST, - (cformat != COERCE_IMPLICIT_CAST)); + if (!lengthCoercionDone) + { + int32 typmod = get_typtypmod(typeId); + + if (typmod >= 0) + arg = coerce_type_typmod(arg, baseTypeId, typmod, + COERCE_IMPLICIT_CAST, + (cformat != COERCE_IMPLICIT_CAST), + false); + } /* - * Now build the domain coercion node. This represents run-time checking - * of any constraints currently attached to the domain. This also - * ensures that the expression is properly labeled as to result type. + * Now build the domain coercion node. This represents run-time checking + * of any constraints currently attached to the domain. This also ensures + * that the expression is properly labeled as to result type. */ result = makeNode(CoerceToDomain); result->arg = (Expr *) arg; @@ -489,58 +515,256 @@ coerce_to_domain(Node *arg, Oid baseTypeId, Oid typeId, CoercionForm cformat) * The caller must have already ensured that the value is of the correct * type, typically by applying coerce_type. * + * cformat determines the display properties of the generated node (if any), + * while isExplicit may affect semantics. If hideInputCoercion is true + * *and* we generate a node, the input node is forced to IMPLICIT display + * form, so that only the typmod coercion node will be visible when + * displaying the expression. + * * NOTE: this does not need to work on domain types, because any typmod * coercion for a domain is considered to be part of the type coercion * needed to produce the domain value in the first place. So, no getBaseType. */ static Node * coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod, - CoercionForm cformat, bool isExplicit) + CoercionForm cformat, bool isExplicit, + bool hideInputCoercion) { Oid funcId; - int nargs; /* - * A negative typmod is assumed to mean that no coercion is wanted. + * A negative typmod is assumed to mean that no coercion is wanted. Also, + * skip coercion if already done. */ if (targetTypMod < 0 || targetTypMod == exprTypmod(node)) return node; - funcId = find_typmod_coercion_function(targetTypeId, &nargs); + funcId = find_typmod_coercion_function(targetTypeId); if (OidIsValid(funcId)) { - List *args; - Const *cons; + /* Suppress display of nested coercion steps */ + if (hideInputCoercion) + hide_coercion_node(node); + + node = build_coercion_expression(node, funcId, + targetTypeId, targetTypMod, + cformat, isExplicit); + } + + return node; +} - /* Pass given value, plus target typmod as an int4 constant */ +/* + * Mark a coercion node as IMPLICIT so it will never be displayed by + * ruleutils.c. We use this when we generate a nest of coercion nodes + * to implement what is logically one conversion; the inner nodes are + * forced to IMPLICIT_CAST format. This does not change their semantics, + * only display behavior. + * + * It is caller error to call this on something that doesn't have a + * CoercionForm field. + */ +static void +hide_coercion_node(Node *node) +{ + if (IsA(node, FuncExpr)) + ((FuncExpr *) node)->funcformat = COERCE_IMPLICIT_CAST; + else if (IsA(node, RelabelType)) + ((RelabelType *) node)->relabelformat = COERCE_IMPLICIT_CAST; + else if (IsA(node, ConvertRowtypeExpr)) + ((ConvertRowtypeExpr *) node)->convertformat = COERCE_IMPLICIT_CAST; + else if (IsA(node, RowExpr)) + ((RowExpr *) node)->row_format = COERCE_IMPLICIT_CAST; + else if (IsA(node, CoerceToDomain)) + ((CoerceToDomain *) node)->coercionformat = COERCE_IMPLICIT_CAST; + else + elog(ERROR, "unsupported node type: %d", (int) nodeTag(node)); +} + +/* + * build_coercion_expression() + * Construct a function-call expression for applying a pg_cast entry. + * + * This is used for both type-coercion and length-coercion functions, + * since there is no difference in terms of the calling convention. + */ +static Node * +build_coercion_expression(Node *node, Oid funcId, + Oid targetTypeId, int32 targetTypMod, + CoercionForm cformat, bool isExplicit) +{ + HeapTuple tp; + Form_pg_proc procstruct; + int nargs; + List *args; + Const *cons; + + tp = SearchSysCache(PROCOID, + ObjectIdGetDatum(funcId), + 0, 0, 0); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for function %u", funcId); + procstruct = (Form_pg_proc) GETSTRUCT(tp); + + /* + * Asserts essentially check that function is a legal coercion function. + * We can't make the seemingly obvious tests on prorettype and + * proargtypes[0], because of various binary-compatibility cases. + */ + /* Assert(targetTypeId == procstruct->prorettype); */ + Assert(!procstruct->proretset); + Assert(!procstruct->proisagg); + nargs = procstruct->pronargs; + Assert(nargs >= 1 && nargs <= 3); + /* Assert(procstruct->proargtypes.values[0] == exprType(node)); */ + Assert(nargs < 2 || procstruct->proargtypes.values[1] == INT4OID); + Assert(nargs < 3 || procstruct->proargtypes.values[2] == BOOLOID); + + ReleaseSysCache(tp); + + args = list_make1(node); + + if (nargs >= 2) + { + /* Pass target typmod as an int4 constant */ cons = makeConst(INT4OID, sizeof(int32), Int32GetDatum(targetTypMod), false, true); - args = makeList2(node, cons); + args = lappend(args, cons); + } - if (nargs == 3) - { - /* Pass it a boolean isExplicit parameter, too */ - cons = makeConst(BOOLOID, - sizeof(bool), - BoolGetDatum(isExplicit), - false, - true); - - args = lappend(args, cons); - } + if (nargs == 3) + { + /* Pass it a boolean isExplicit parameter, too */ + cons = makeConst(BOOLOID, + sizeof(bool), + BoolGetDatum(isExplicit), + false, + true); - node = build_func_call(funcId, targetTypeId, args, cformat); + args = lappend(args, cons); } - return node; + return (Node *) makeFuncExpr(funcId, targetTypeId, args, cformat); } +/* + * coerce_record_to_complex + * Coerce a RECORD to a specific composite type. + * + * Currently we only support this for inputs that are RowExprs or whole-row + * Vars. + */ +static Node * +coerce_record_to_complex(ParseState *pstate, Node *node, + Oid targetTypeId, + CoercionContext ccontext, + CoercionForm cformat) +{ + RowExpr *rowexpr; + TupleDesc tupdesc; + List *args = NIL; + List *newargs; + int i; + int ucolno; + ListCell *arg; + + if (node && IsA(node, RowExpr)) + { + /* + * Since the RowExpr must be of type RECORD, we needn't worry about it + * containing any dropped columns. + */ + args = ((RowExpr *) node)->args; + } + else if (node && IsA(node, Var) && + ((Var *) node)->varattno == InvalidAttrNumber) + { + int rtindex = ((Var *) node)->varno; + int sublevels_up = ((Var *) node)->varlevelsup; + RangeTblEntry *rte; + + rte = GetRTEByRangeTablePosn(pstate, rtindex, sublevels_up); + expandRTE(rte, rtindex, sublevels_up, false, + NULL, &args); + } + else + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(RECORDOID), + format_type_be(targetTypeId)))); + + tupdesc = lookup_rowtype_tupdesc(targetTypeId, -1); + newargs = NIL; + ucolno = 1; + arg = list_head(args); + for (i = 0; i < tupdesc->natts; i++) + { + Node *expr; + Oid exprtype; + + /* Fill in NULLs for dropped columns in rowtype */ + if (tupdesc->attrs[i]->attisdropped) + { + /* + * can't use atttypid here, but it doesn't really matter what type + * the Const claims to be. + */ + newargs = lappend(newargs, makeNullConst(INT4OID)); + continue; + } + + if (arg == NULL) + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(RECORDOID), + format_type_be(targetTypeId)), + errdetail("Input has too few columns."))); + expr = (Node *) lfirst(arg); + exprtype = exprType(expr); + + expr = coerce_to_target_type(pstate, + expr, exprtype, + tupdesc->attrs[i]->atttypid, + tupdesc->attrs[i]->atttypmod, + ccontext, + COERCE_IMPLICIT_CAST); + if (expr == NULL) + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(RECORDOID), + format_type_be(targetTypeId)), + errdetail("Cannot cast type %s to %s in column %d.", + format_type_be(exprtype), + format_type_be(tupdesc->attrs[i]->atttypid), + ucolno))); + newargs = lappend(newargs, expr); + ucolno++; + arg = lnext(arg); + } + if (arg != NULL) + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(RECORDOID), + format_type_be(targetTypeId)), + errdetail("Input has too many columns."))); + + rowexpr = makeNode(RowExpr); + rowexpr->args = newargs; + rowexpr->row_typeid = targetTypeId; + rowexpr->row_format = cformat; + return (Node *) rowexpr; +} + /* coerce_to_boolean() * Coerce an argument of a construct that requires boolean input * (AND, OR, NOT, etc). Also check that input is not a set. @@ -563,20 +787,59 @@ coerce_to_boolean(ParseState *pstate, Node *node, COERCION_ASSIGNMENT, COERCE_IMPLICIT_CAST); if (node == NULL) - { + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), /* translator: first %s is name of a SQL construct, eg WHERE */ - elog(ERROR, "Argument of %s must be type boolean, not type %s", - constructName, format_type_be(inputTypeId)); - } + errmsg("argument of %s must be type boolean, not type %s", + constructName, format_type_be(inputTypeId)))); } if (expression_returns_set(node)) - { + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), /* translator: %s is name of a SQL construct, eg WHERE */ - elog(ERROR, "Argument of %s must not be a set function", - constructName); + errmsg("argument of %s must not return a set", + constructName))); + + return node; +} + +/* coerce_to_integer() + * Coerce an argument of a construct that requires integer input + * (LIMIT, OFFSET, etc). Also check that input is not a set. + * + * Returns the possibly-transformed node tree. + * + * As with coerce_type, pstate may be NULL if no special unknown-Param + * processing is wanted. + */ +Node * +coerce_to_integer(ParseState *pstate, Node *node, + const char *constructName) +{ + Oid inputTypeId = exprType(node); + + if (inputTypeId != INT4OID) + { + node = coerce_to_target_type(pstate, node, inputTypeId, + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST); + if (node == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + /* translator: first %s is name of a SQL construct, eg LIMIT */ + errmsg("argument of %s must be type integer, not type %s", + constructName, format_type_be(inputTypeId)))); } + if (expression_returns_set(node)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + /* translator: %s is name of a SQL construct, eg LIMIT */ + errmsg("argument of %s must not return a set", + constructName))); + return node; } @@ -596,14 +859,15 @@ select_common_type(List *typeids, const char *context) { Oid ptype; CATEGORY pcategory; - List *l; + ListCell *type_item; Assert(typeids != NIL); - ptype = getBaseType(lfirsto(typeids)); + ptype = getBaseType(linitial_oid(typeids)); pcategory = TypeCategory(ptype); - foreach(l, lnext(typeids)) + + for_each_cell(type_item, lnext(list_head(typeids))) { - Oid ntype = getBaseType(lfirsto(l)); + Oid ntype = getBaseType(lfirst_oid(type_item)); /* move on to next one if no new information... */ if ((ntype != InvalidOid) && (ntype != UNKNOWNOID) && (ntype != ptype)) @@ -617,11 +881,18 @@ select_common_type(List *typeids, const char *context) else if (TypeCategory(ntype) != pcategory) { /* - * both types in different categories? then not much - * hope... + * both types in different categories? then not much hope... */ - elog(ERROR, "%s types '%s' and '%s' not matched", - context, format_type_be(ptype), format_type_be(ntype)); + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + + /* + * translator: first %s is name of a SQL construct, eg CASE + */ + errmsg("%s types %s and %s cannot be matched", + context, + format_type_be(ptype), + format_type_be(ntype)))); } else if (!IsPreferredType(pcategory, ptype) && can_coerce_type(1, &ptype, &ntype, COERCION_IMPLICIT) && @@ -638,15 +909,15 @@ select_common_type(List *typeids, const char *context) } /* - * If all the inputs were UNKNOWN type --- ie, unknown-type literals - * --- then resolve as type TEXT. This situation comes up with - * constructs like SELECT (CASE WHEN foo THEN 'bar' ELSE 'baz' END); - * SELECT 'foo' UNION SELECT 'bar'; It might seem desirable to leave - * the construct's output type as UNKNOWN, but that really doesn't - * work, because we'd probably end up needing a runtime coercion from - * UNKNOWN to something else, and we usually won't have it. We need - * to coerce the unknown literals while they are still literals, so a - * decision has to be made now. + * If all the inputs were UNKNOWN type --- ie, unknown-type literals --- + * then resolve as type TEXT. This situation comes up with constructs + * like SELECT (CASE WHEN foo THEN 'bar' ELSE 'baz' END); SELECT 'foo' + * UNION SELECT 'bar'; It might seem desirable to leave the construct's + * output type as UNKNOWN, but that really doesn't work, because we'd + * probably end up needing a runtime coercion from UNKNOWN to something + * else, and we usually won't have it. We need to coerce the unknown + * literals while they are still literals, so a decision has to be made + * now. */ if (ptype == UNKNOWNOID) ptype = TEXTOID; @@ -673,11 +944,16 @@ coerce_to_common_type(ParseState *pstate, Node *node, if (inputTypeId == targetTypeId) return node; /* no work */ if (can_coerce_type(1, &inputTypeId, &targetTypeId, COERCION_IMPLICIT)) - node = coerce_type(pstate, node, inputTypeId, targetTypeId, + node = coerce_type(pstate, node, inputTypeId, targetTypeId, -1, COERCION_IMPLICIT, COERCE_IMPLICIT_CAST); else - elog(ERROR, "%s unable to convert to type %s", - context, format_type_be(targetTypeId)); + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + /* translator: first %s is name of a SQL construct, eg CASE */ + errmsg("%s could not convert type %s to %s", + context, + format_type_be(inputTypeId), + format_type_be(targetTypeId)))); return node; } @@ -692,13 +968,19 @@ coerce_to_common_type(ParseState *pstate, Node *node, * and must in fact be varlena arrays. * 2) All arguments declared ANYELEMENT must have matching datatypes. * 3) If there are arguments of both ANYELEMENT and ANYARRAY, make sure - * the actual ANYELEMENT datatype is in fact the element type for - * the actual ANYARRAY datatype. + * the actual ANYELEMENT datatype is in fact the element type for + * the actual ANYARRAY datatype. * * If we have UNKNOWN input (ie, an untyped literal) for any ANYELEMENT * or ANYARRAY argument, assume it is okay. * - * We do not elog here, but just return FALSE if a rule is violated. + * If an input is of type ANYARRAY (ie, we know it's an array, but not + * what element type), we will accept it as a match to an argument declared + * ANYARRAY, so long as we don't have to determine an element type --- + * that is, so long as there is no use of ANYELEMENT. This is mostly for + * backwards compatibility with the pre-7.4 behavior of ANYARRAY. + * + * We do not ereport here, but just return FALSE if a rule is violated. */ bool check_generic_type_consistency(Oid *actual_arg_types, @@ -709,18 +991,19 @@ check_generic_type_consistency(Oid *actual_arg_types, Oid elem_typeid = InvalidOid; Oid array_typeid = InvalidOid; Oid array_typelem; + bool have_anyelement = false; /* - * Loop through the arguments to see if we have any that are - * ANYARRAY or ANYELEMENT. If so, require the actual types to be - * self-consistent + * Loop through the arguments to see if we have any that are ANYARRAY or + * ANYELEMENT. If so, require the actual types to be self-consistent */ for (j = 0; j < nargs; j++) { - Oid actual_type = actual_arg_types[j]; + Oid actual_type = actual_arg_types[j]; if (declared_arg_types[j] == ANYELEMENTOID) { + have_anyelement = true; if (actual_type == UNKNOWNOID) continue; if (OidIsValid(elem_typeid) && actual_type != elem_typeid) @@ -740,13 +1023,23 @@ check_generic_type_consistency(Oid *actual_arg_types, /* Get the element type based on the array type, if we have one */ if (OidIsValid(array_typeid)) { + if (array_typeid == ANYARRAYOID) + { + /* Special case for ANYARRAY input: okay iff no ANYELEMENT */ + if (have_anyelement) + return false; + return true; + } + array_typelem = get_element_type(array_typeid); if (!OidIsValid(array_typelem)) return false; /* should be an array, but isn't */ if (!OidIsValid(elem_typeid)) { - /* if we don't have an element type yet, use the one we just got */ + /* + * if we don't have an element type yet, use the one we just got + */ elem_typeid = array_typelem; } else if (array_typelem != elem_typeid) @@ -779,23 +1072,23 @@ check_generic_type_consistency(Oid *actual_arg_types, * if it is declared ANYARRAY or ANYELEMENT: * * 1) If return type is ANYARRAY, and any argument is ANYARRAY, use the - * argument's actual type as the function's return type. + * argument's actual type as the function's return type. * 2) If return type is ANYARRAY, no argument is ANYARRAY, but any argument - * is ANYELEMENT, use the actual type of the argument to determine - * the function's return type, i.e. the element type's corresponding - * array type. + * is ANYELEMENT, use the actual type of the argument to determine + * the function's return type, i.e. the element type's corresponding + * array type. * 3) If return type is ANYARRAY, no argument is ANYARRAY or ANYELEMENT, - * generate an ERROR. This condition is prevented by CREATE FUNCTION - * and is therefore not expected here. + * generate an ERROR. This condition is prevented by CREATE FUNCTION + * and is therefore not expected here. * 4) If return type is ANYELEMENT, and any argument is ANYELEMENT, use the - * argument's actual type as the function's return type. + * argument's actual type as the function's return type. * 5) If return type is ANYELEMENT, no argument is ANYELEMENT, but any - * argument is ANYARRAY, use the actual type of the argument to determine - * the function's return type, i.e. the array type's corresponding - * element type. + * argument is ANYARRAY, use the actual type of the argument to determine + * the function's return type, i.e. the array type's corresponding + * element type. * 6) If return type is ANYELEMENT, no argument is ANYARRAY or ANYELEMENT, - * generate an ERROR. This condition is prevented by CREATE FUNCTION - * and is therefore not expected here. + * generate an ERROR. This condition is prevented by CREATE FUNCTION + * and is therefore not expected here. */ Oid enforce_generic_type_consistency(Oid *actual_arg_types, @@ -808,29 +1101,32 @@ enforce_generic_type_consistency(Oid *actual_arg_types, bool have_unknowns = false; Oid elem_typeid = InvalidOid; Oid array_typeid = InvalidOid; - Oid array_typelem = InvalidOid; + Oid array_typelem; + bool have_anyelement = (rettype == ANYELEMENTOID); /* - * Loop through the arguments to see if we have any that are - * ANYARRAY or ANYELEMENT. If so, require the actual types to be - * self-consistent + * Loop through the arguments to see if we have any that are ANYARRAY or + * ANYELEMENT. If so, require the actual types to be self-consistent */ for (j = 0; j < nargs; j++) { - Oid actual_type = actual_arg_types[j]; + Oid actual_type = actual_arg_types[j]; if (declared_arg_types[j] == ANYELEMENTOID) { - have_generics = true; + have_generics = have_anyelement = true; if (actual_type == UNKNOWNOID) { have_unknowns = true; continue; } if (OidIsValid(elem_typeid) && actual_type != elem_typeid) - elog(ERROR, "Arguments declared ANYELEMENT are not all alike: %s vs %s", - format_type_be(elem_typeid), - format_type_be(actual_type)); + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("arguments declared \"anyelement\" are not all alike"), + errdetail("%s versus %s", + format_type_be(elem_typeid), + format_type_be(actual_type)))); elem_typeid = actual_type; } else if (declared_arg_types[j] == ANYARRAYOID) @@ -842,16 +1138,19 @@ enforce_generic_type_consistency(Oid *actual_arg_types, continue; } if (OidIsValid(array_typeid) && actual_type != array_typeid) - elog(ERROR, "Arguments declared ANYARRAY are not all alike: %s vs %s", - format_type_be(array_typeid), - format_type_be(actual_type)); + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("arguments declared \"anyarray\" are not all alike"), + errdetail("%s versus %s", + format_type_be(array_typeid), + format_type_be(actual_type)))); array_typeid = actual_type; } } /* - * Fast Track: if none of the arguments are ANYARRAY or ANYELEMENT, - * return the unmodified rettype. + * Fast Track: if none of the arguments are ANYARRAY or ANYELEMENT, return + * the unmodified rettype. */ if (!have_generics) return rettype; @@ -859,29 +1158,45 @@ enforce_generic_type_consistency(Oid *actual_arg_types, /* Get the element type based on the array type, if we have one */ if (OidIsValid(array_typeid)) { - array_typelem = get_element_type(array_typeid); - if (!OidIsValid(array_typelem)) - elog(ERROR, "Argument declared ANYARRAY is not an array: %s", - format_type_be(array_typeid)); + if (array_typeid == ANYARRAYOID && !have_anyelement) + { + /* Special case for ANYARRAY input: okay iff no ANYELEMENT */ + array_typelem = InvalidOid; + } + else + { + array_typelem = get_element_type(array_typeid); + if (!OidIsValid(array_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared \"anyarray\" is not an array but type %s", + format_type_be(array_typeid)))); + } if (!OidIsValid(elem_typeid)) { - /* if we don't have an element type yet, use the one we just got */ + /* + * if we don't have an element type yet, use the one we just got + */ elem_typeid = array_typelem; } else if (array_typelem != elem_typeid) { /* otherwise, they better match */ - elog(ERROR, "Argument declared ANYARRAY is not consistent with " - "argument declared ANYELEMENT: %s vs %s", - format_type_be(array_typeid), - format_type_be(elem_typeid)); + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared \"anyarray\" is not consistent with argument declared \"anyelement\""), + errdetail("%s versus %s", + format_type_be(array_typeid), + format_type_be(elem_typeid)))); } } else if (!OidIsValid(elem_typeid)) { /* Only way to get here is if all the generic args are UNKNOWN */ - elog(ERROR, "Cannot determine ANYARRAY/ANYELEMENT type because input is UNKNOWN"); + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("could not determine anyarray/anyelement type because input has type \"unknown\""))); } /* @@ -891,23 +1206,23 @@ enforce_generic_type_consistency(Oid *actual_arg_types, { for (j = 0; j < nargs; j++) { - Oid actual_type = actual_arg_types[j]; + Oid actual_type = actual_arg_types[j]; if (actual_type != UNKNOWNOID) continue; if (declared_arg_types[j] == ANYELEMENTOID) - { declared_arg_types[j] = elem_typeid; - } else if (declared_arg_types[j] == ANYARRAYOID) { if (!OidIsValid(array_typeid)) { array_typeid = get_array_type(elem_typeid); if (!OidIsValid(array_typeid)) - elog(ERROR, "Cannot find array type for datatype %s", - format_type_be(elem_typeid)); + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find array type for data type %s", + format_type_be(elem_typeid)))); } declared_arg_types[j] = array_typeid; } @@ -921,8 +1236,10 @@ enforce_generic_type_consistency(Oid *actual_arg_types, { array_typeid = get_array_type(elem_typeid); if (!OidIsValid(array_typeid)) - elog(ERROR, "Cannot find array type for datatype %s", - format_type_be(elem_typeid)); + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find array type for data type %s", + format_type_be(elem_typeid)))); } return array_typeid; } @@ -935,6 +1252,82 @@ enforce_generic_type_consistency(Oid *actual_arg_types, return rettype; } +/* + * resolve_generic_type() + * Deduce an individual actual datatype on the assumption that + * the rules for ANYARRAY/ANYELEMENT are being followed. + * + * declared_type is the declared datatype we want to resolve. + * context_actual_type is the actual input datatype to some argument + * that has declared datatype context_declared_type. + * + * If declared_type isn't polymorphic, we just return it. Otherwise, + * context_declared_type must be polymorphic, and we deduce the correct + * return type based on the relationship of the two polymorphic types. + */ +Oid +resolve_generic_type(Oid declared_type, + Oid context_actual_type, + Oid context_declared_type) +{ + if (declared_type == ANYARRAYOID) + { + if (context_declared_type == ANYARRAYOID) + { + /* Use actual type, but it must be an array */ + Oid array_typelem = get_element_type(context_actual_type); + + if (!OidIsValid(array_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared \"anyarray\" is not an array but type %s", + format_type_be(context_actual_type)))); + return context_actual_type; + } + else if (context_declared_type == ANYELEMENTOID) + { + /* Use the array type corresponding to actual type */ + Oid array_typeid = get_array_type(context_actual_type); + + if (!OidIsValid(array_typeid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find array type for data type %s", + format_type_be(context_actual_type)))); + return array_typeid; + } + } + else if (declared_type == ANYELEMENTOID) + { + if (context_declared_type == ANYARRAYOID) + { + /* Use the element type corresponding to actual type */ + Oid array_typelem = get_element_type(context_actual_type); + + if (!OidIsValid(array_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared \"anyarray\" is not an array but type %s", + format_type_be(context_actual_type)))); + return array_typelem; + } + else if (context_declared_type == ANYELEMENTOID) + { + /* Use the actual type; it doesn't matter if array or not */ + return context_actual_type; + } + } + else + { + /* declared_type isn't polymorphic, so return it as-is */ + return declared_type; + } + /* If we get here, declared_type is polymorphic and context isn't */ + /* NB: this is a calling-code logic error, not a user error */ + elog(ERROR, "could not determine ANYARRAY/ANYELEMENT type because context isn't polymorphic"); + return InvalidOid; /* keep compiler quiet */ +} + /* TypeCategory() * Assign a category to the specified type OID. @@ -1063,8 +1456,8 @@ IsPreferredType(CATEGORY category, Oid type) return false; /* - * This switch should agree with TypeCategory(), above. Note that - * at this point, category certainly matches the type. + * This switch should agree with TypeCategory(), above. Note that at this + * point, category certainly matches the type. */ switch (category) { @@ -1122,7 +1515,7 @@ IsPreferredType(CATEGORY category, Oid type) break; default: - elog(ERROR, "IsPreferredType: unknown category"); + elog(ERROR, "unrecognized type category: %d", (int) category); preftype = UNKNOWNOID; break; } @@ -1135,8 +1528,8 @@ IsPreferredType(CATEGORY category, Oid type) * Check if srctype is binary-coercible to targettype. * * This notion allows us to cheat and directly exchange values without - * going through the trouble of calling a conversion function. Note that - * in general, this should only be an implementation shortcut. Before 7.4, + * going through the trouble of calling a conversion function. Note that + * in general, this should only be an implementation shortcut. Before 7.4, * this was also used as a heuristic for resolving overloaded functions and * operators, but that's basically a bad idea. * @@ -1149,7 +1542,7 @@ IsPreferredType(CATEGORY category, Oid type) * ANYARRAY type. * * This function replaces IsBinaryCompatible(), which was an inherently - * symmetric test. Since the pg_cast entries aren't necessarily symmetric, + * symmetric test. Since the pg_cast entries aren't necessarily symmetric, * the order of the operands is now significant. */ bool @@ -1253,8 +1646,8 @@ find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId, castcontext = COERCION_EXPLICIT; break; default: - elog(ERROR, "find_coercion_pathway: bogus castcontext %c", - castForm->castcontext); + elog(ERROR, "unrecognized castcontext: %d", + (int) castForm->castcontext); castcontext = 0; /* keep compiler quiet */ break; } @@ -1271,21 +1664,43 @@ find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId, else { /* - * 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, use array_type_coerce(). + * 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, + * use array_type_coerce() or array_type_length_coerce(). + * + * Hack: disallow coercions to oidvector and int2vector, which + * otherwise tend to capture coercions that should go to "real" array + * types. We want those types to be considered "real" arrays for many + * purposes, but not this one. (Also, array_type_coerce isn't + * guaranteed to produce an output that meets the restrictions of + * these datatypes, such as being 1-dimensional.) */ Oid targetElemType; Oid sourceElemType; Oid elemfuncid; + if (targetTypeId == OIDVECTOROID || targetTypeId == INT2VECTOROID) + return false; + if ((targetElemType = get_element_type(targetTypeId)) != InvalidOid && (sourceElemType = get_element_type(sourceTypeId)) != InvalidOid) { if (find_coercion_pathway(targetElemType, sourceElemType, ccontext, &elemfuncid)) { - *funcid = F_ARRAY_TYPE_COERCE; + if (!OidIsValid(elemfuncid)) + { + /* binary-compatible element type conversion */ + *funcid = F_ARRAY_TYPE_COERCE; + } + else + { + /* does the function take a typmod arg? */ + if (get_func_nargs(elemfuncid) > 1) + *funcid = F_ARRAY_TYPE_LENGTH_COERCE; + else + *funcid = F_ARRAY_TYPE_COERCE; + } result = true; } } @@ -1298,14 +1713,8 @@ find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId, /* * find_typmod_coercion_function -- does the given type need length coercion? * - * If the target type possesses a function named for the type - * and having parameter signature (targettype, int4), we assume that - * the type requires coercion to its own length and that the said - * function should be invoked to do that. - * - * Alternatively, the length-coercing function may have the signature - * (targettype, int4, bool). On success, *nargs is set to report which - * signature we found. + * If the target type possesses a pg_cast function from itself to itself, + * it must need length coercion. * * "bpchar" (ie, char(N)) and "numeric" are examples of such types. * @@ -1313,23 +1722,15 @@ find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId, * function associated directly with the array type, but instead look for * one associated with the element type. If one exists, we report * array_length_coerce() as the coercion function to use. - * - * This mechanism may seem pretty grotty and in need of replacement by - * something in pg_cast, but since typmod is only interesting for datatypes - * that have special handling in the grammar, there's not really much - * percentage in making it any easier to apply such coercions ... */ Oid -find_typmod_coercion_function(Oid typeId, int *nargs) +find_typmod_coercion_function(Oid typeId) { Oid funcid = InvalidOid; bool isArray = false; Type targetType; Form_pg_type typeForm; - char *typname; - Oid typnamespace; - Oid oid_array[FUNC_MAX_ARGS]; - HeapTuple ftup; + HeapTuple tuple; targetType = typeidType(typeId); typeForm = (Form_pg_type) GETSTRUCT(targetType); @@ -1341,99 +1742,30 @@ find_typmod_coercion_function(Oid typeId, int *nargs) { /* Yes, switch our attention to the element type */ typeId = typeForm->typelem; - ReleaseSysCache(targetType); - targetType = typeidType(typeId); - typeForm = (Form_pg_type) GETSTRUCT(targetType); isArray = true; } + ReleaseSysCache(targetType); - /* Function name is same as type internal name, and in same namespace */ - typname = NameStr(typeForm->typname); - typnamespace = typeForm->typnamespace; - - /* First look for parameters (type, int4) */ - MemSet(oid_array, 0, FUNC_MAX_ARGS * sizeof(Oid)); - oid_array[0] = typeId; - oid_array[1] = INT4OID; - *nargs = 2; - - ftup = SearchSysCache(PROCNAMENSP, - CStringGetDatum(typname), - Int16GetDatum(2), - PointerGetDatum(oid_array), - ObjectIdGetDatum(typnamespace)); - if (HeapTupleIsValid(ftup)) - { - Form_pg_proc pform = (Form_pg_proc) GETSTRUCT(ftup); - - /* Make sure the function's result type is as expected */ - if (pform->prorettype == typeId && !pform->proretset && - !pform->proisagg) - { - /* Okay to use it */ - funcid = HeapTupleGetOid(ftup); - } - ReleaseSysCache(ftup); - } + /* Look in pg_cast */ + tuple = SearchSysCache(CASTSOURCETARGET, + ObjectIdGetDatum(typeId), + ObjectIdGetDatum(typeId), + 0, 0); - if (!OidIsValid(funcid)) + if (HeapTupleIsValid(tuple)) { - /* Didn't find a function, so now try (type, int4, bool) */ - oid_array[2] = BOOLOID; - *nargs = 3; - - ftup = SearchSysCache(PROCNAMENSP, - CStringGetDatum(typname), - Int16GetDatum(3), - PointerGetDatum(oid_array), - ObjectIdGetDatum(typnamespace)); - if (HeapTupleIsValid(ftup)) - { - Form_pg_proc pform = (Form_pg_proc) GETSTRUCT(ftup); + Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple); - /* Make sure the function's result type is as expected */ - if (pform->prorettype == typeId && !pform->proretset && - !pform->proisagg) - { - /* Okay to use it */ - funcid = HeapTupleGetOid(ftup); - } - ReleaseSysCache(ftup); - } + funcid = castForm->castfunc; + ReleaseSysCache(tuple); } - ReleaseSysCache(targetType); - /* * Now, if we did find a coercion function for an array element type, - * report array_length_coerce() as the function to use. We know it - * takes three arguments always. + * report array_length_coerce() as the function to use. */ if (isArray && OidIsValid(funcid)) - { funcid = F_ARRAY_LENGTH_COERCE; - *nargs = 3; - } return funcid; } - -/* - * Build an expression tree representing a function call. - * - * The argument expressions must have been transformed already. - */ -static Node * -build_func_call(Oid funcid, Oid rettype, List *args, CoercionForm fformat) -{ - FuncExpr *funcexpr; - - funcexpr = makeNode(FuncExpr); - funcexpr->funcid = funcid; - funcexpr->funcresulttype = rettype; - funcexpr->funcretset = false; /* only possible case here */ - funcexpr->funcformat = fformat; - funcexpr->args = args; - - return (Node *) funcexpr; -}