diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 397bd4b8b079e84482b2f5570f9a2c000009baba..18224a7e3fef45e63f0b305cb515318da4c391f4 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.81 2002/08/31 22:10:46 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.82 2002/09/01 02:27:32 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -27,24 +27,27 @@ #include "utils/syscache.h" -Oid DemoteType(Oid inType); -Oid PromoteTypeToNext(Oid inType); - static Oid PreferredType(CATEGORY category, Oid type); -static Node *build_func_call(Oid funcid, Oid rettype, List *args); -static Oid find_coercion_function(Oid targetTypeId, Oid sourceTypeId, - bool isExplicit); +static bool find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId, + bool isExplicit, + Oid *funcid); static Oid find_typmod_coercion_function(Oid typeId); +static Node *build_func_call(Oid funcid, Oid rettype, List *args); -/* coerce_type() - * Convert a function argument to a different type. +/* + * coerce_type() + * Convert a function argument to a different type. + * + * The caller should already have determined that the coercion is possible; + * see can_coerce_type. */ Node * coerce_type(ParseState *pstate, Node *node, Oid inputTypeId, Oid targetTypeId, int32 atttypmod, bool isExplicit) { Node *result; + Oid funcId; if (targetTypeId == inputTypeId || node == NULL) @@ -118,25 +121,71 @@ coerce_type(ParseState *pstate, Node *node, Oid inputTypeId, /* assume can_coerce_type verified that implicit coercion is okay */ result = node; } - else if (IsBinaryCompatible(inputTypeId, targetTypeId)) + else if (find_coercion_pathway(targetTypeId, inputTypeId, isExplicit, + &funcId)) { - /* - * We don't really need to do a 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. - */ - result = coerce_type_constraints(pstate, node, targetTypeId, true); - /* - * XXX could we label result with exprTypmod(node) instead of - * default -1 typmod, to save a possible length-coercion later? - * Would work if both types have same interpretation of typmod, - * which is likely but not certain (wrong if target is a domain, - * in any case). - */ - result = (Node *) makeRelabelType(result, targetTypeId, -1); + 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. + */ + Oid baseTypeId = getBaseType(targetTypeId); + + result = build_func_call(funcId, baseTypeId, makeList1(node)); + + /* + * If domain, test against domain constraints and relabel with + * domain type ID + */ + if (targetTypeId != baseTypeId) + { + result = coerce_type_constraints(pstate, result, + targetTypeId, true); + result = (Node *) makeRelabelType(result, targetTypeId, -1); + } + + /* + * If the input is a constant, apply the type conversion function + * now instead of delaying to runtime. (We could, of course, just + * leave this to be done during planning/optimization; but it's a + * very frequent special case, and we save cycles in the rewriter + * if we fold the expression now.) + * + * Note that no folding will occur if the conversion function is + * not marked 'immutable'. + * + * HACK: if constant is NULL, don't fold it here. This is needed + * by make_subplan(), which calls this routine on placeholder + * Const nodes that mustn't be collapsed. (It'd be a lot cleaner + * to make a separate node type for that purpose...) + */ + if (IsA(node, Const) && + !((Const *) node)->constisnull) + result = eval_const_expressions(result); + } + 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. + * + * Also, domains may have value restrictions beyond the base type + * that must be accounted for. + */ + result = coerce_type_constraints(pstate, node, + targetTypeId, true); + /* + * XXX could we label result with exprTypmod(node) instead of + * default -1 typmod, to save a possible length-coercion later? + * Would work if both types have same interpretation of typmod, + * which is likely but not certain (wrong if target is a domain, + * in any case). + */ + result = (Node *) makeRelabelType(result, targetTypeId, -1); + } } else if (typeInheritsFrom(inputTypeId, targetTypeId)) { @@ -149,74 +198,23 @@ coerce_type(ParseState *pstate, Node *node, Oid inputTypeId, } else { - /* - * Otherwise, find the appropriate type conversion function - * (caller should have determined that there is one), and generate - * an expression tree representing run-time application of the - * conversion function. - * - * For domains, we use the coercion function for the base type. - */ - Oid baseTypeId = getBaseType(targetTypeId); - Oid funcId; - - funcId = find_coercion_function(baseTypeId, - getBaseType(inputTypeId), - isExplicit); - if (!OidIsValid(funcId)) - elog(ERROR, "coerce_type: no conversion function from '%s' to '%s'", - format_type_be(inputTypeId), format_type_be(targetTypeId)); - - result = build_func_call(funcId, baseTypeId, makeList1(node)); - - /* - * If domain, test against domain constraints and relabel with - * domain type ID - */ - if (targetTypeId != baseTypeId) - { - result = coerce_type_constraints(pstate, result, targetTypeId, - true); - result = (Node *) makeRelabelType(result, targetTypeId, -1); - } - - /* - * If the input is a constant, apply the type conversion function - * now instead of delaying to runtime. (We could, of course, just - * leave this to be done during planning/optimization; but it's a - * very frequent special case, and we save cycles in the rewriter - * if we fold the expression now.) - * - * Note that no folding will occur if the conversion function is not - * marked 'iscachable'. - * - * HACK: if constant is NULL, don't fold it here. This is needed by - * make_subplan(), which calls this routine on placeholder Const - * nodes that mustn't be collapsed. (It'd be a lot cleaner to - * make a separate node type for that purpose...) - */ - if (IsA(node, Const) && - !((Const *) node)->constisnull) - result = eval_const_expressions(result); + /* If we get here, caller blew it */ + elog(ERROR, "coerce_type: no conversion function from %s to %s", + format_type_be(inputTypeId), format_type_be(targetTypeId)); + result = NULL; /* keep compiler quiet */ } return result; } -/* can_coerce_type() - * Can input_typeids be coerced to func_typeids? - * - * There are a few types which are known apriori to be convertible. - * We will check for those cases first, and then look for possible - * conversion functions. +/* + * can_coerce_type() + * Can input_typeids be coerced to func_typeids? * * We must be told whether this is an implicit or explicit coercion * (explicit being a CAST construct, explicit function call, etc). * We will accept a wider set of coercion cases for an explicit coercion. - * - * Notes: - * This uses the same mechanism as the CAST() SQL construct in gram.y. */ bool can_coerce_type(int nargs, Oid *input_typeids, Oid *func_typeids, @@ -278,35 +276,101 @@ can_coerce_type(int nargs, Oid *input_typeids, Oid *func_typeids, } /* - * one of the known-good transparent conversions? then drop - * through... + * If pg_cast shows that we can coerce, accept. This test now + * covers both binary-compatible and coercion-function cases. */ - if (IsBinaryCompatible(inputTypeId, targetTypeId)) + if (find_coercion_pathway(targetTypeId, inputTypeId, isExplicit, + &funcId)) continue; /* - * If input is a class type that inherits from target, no problem + * If input is a class type that inherits from target, accept */ if (typeInheritsFrom(inputTypeId, targetTypeId)) continue; /* - * Else, try for run-time conversion using functions: look for a - * single-argument function named with the target type name and - * accepting the source type. - * - * If either type is a domain, use its base type instead. + * Else, cannot coerce at this argument position */ - funcId = find_coercion_function(getBaseType(targetTypeId), - getBaseType(inputTypeId), - isExplicit); - if (!OidIsValid(funcId)) - return false; + return false; } return true; } + +/* + * Create an expression tree to enforce the constraints (if any) + * that should be applied by the type. Currently this is only + * interesting for domain types. + */ +Node * +coerce_type_constraints(ParseState *pstate, Node *arg, + Oid typeId, bool applyTypmod) +{ + char *notNull = NULL; + int32 typmod = -1; + + for (;;) + { + HeapTuple tup; + Form_pg_type typTup; + + tup = SearchSysCache(TYPEOID, + ObjectIdGetDatum(typeId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "coerce_type_constraints: failed to lookup type %u", + typeId); + typTup = (Form_pg_type) GETSTRUCT(tup); + + /* Test for NOT NULL Constraint */ + if (typTup->typnotnull && notNull == NULL) + notNull = pstrdup(NameStr(typTup->typname)); + + /* TODO: Add CHECK Constraints to domains */ + + if (typTup->typtype != 'd') + { + /* Not a domain, so done */ + ReleaseSysCache(tup); + break; + } + + Assert(typmod < 0); + + typeId = typTup->typbasetype; + typmod = typTup->typtypmod; + ReleaseSysCache(tup); + } + + /* + * If domain applies a typmod to its base type, do length coercion. + */ + if (applyTypmod && typmod >= 0) + arg = coerce_type_typmod(pstate, arg, typeId, typmod); + + /* + * Only need to add one NOT NULL check regardless of how many + * domains in the stack request it. The topmost domain that + * requested it is used as the constraint name. + */ + if (notNull) + { + ConstraintTest *r = makeNode(ConstraintTest); + + r->arg = arg; + r->testtype = CONSTR_TEST_NOTNULL; + r->name = notNull; + r->check_expr = NULL; + + arg = (Node *) r; + } + + return arg; +} + + /* coerce_type_typmod() * Force a value to a particular typmod, if meaningful and possible. * @@ -317,21 +381,9 @@ can_coerce_type(int nargs, Oid *input_typeids, Oid *func_typeids, * The caller must have already ensured that the value is of the correct * type, typically by applying coerce_type. * - * If the target column type possesses a function named for the type - * and having parameter signature (columntype, int4), we assume that - * the type requires coercion to its own length and that the said - * function should be invoked to do that. - * - * "bpchar" (ie, char(N)) and "numeric" are examples of such types. - * - * 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 ... - * * 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. + * needed to produce the domain value in the first place. So, no getBaseType. */ Node * coerce_type_typmod(ParseState *pstate, Node *node, @@ -600,100 +652,6 @@ TypeCategory(Oid inType) } /* TypeCategory() */ -/* IsBinaryCompatible() - * Check if two types are binary-compatible. - * - * This notion allows us to cheat and directly exchange values without - * going through the trouble of calling a conversion function. - * - * XXX This should be moved to system catalog lookups - * to allow for better type extensibility. - */ - -#define TypeIsTextGroup(t) \ - ((t) == TEXTOID || \ - (t) == BPCHAROID || \ - (t) == VARCHAROID) - -/* Notice OidGroup is a subset of Int4GroupA */ -#define TypeIsOidGroup(t) \ - ((t) == OIDOID || \ - (t) == REGPROCOID || \ - (t) == REGPROCEDUREOID || \ - (t) == REGOPEROID || \ - (t) == REGOPERATOROID || \ - (t) == REGCLASSOID || \ - (t) == REGTYPEOID) - -/* - * INT4 is binary-compatible with many types, but we don't want to allow - * implicit coercion directly between, say, OID and AbsTime. So we subdivide - * the categories. - */ -#define TypeIsInt4GroupA(t) \ - ((t) == INT4OID || \ - TypeIsOidGroup(t)) - -#define TypeIsInt4GroupB(t) \ - ((t) == INT4OID || \ - (t) == ABSTIMEOID) - -#define TypeIsInt4GroupC(t) \ - ((t) == INT4OID || \ - (t) == RELTIMEOID) - -#define TypeIsInetGroup(t) \ - ((t) == INETOID || \ - (t) == CIDROID) - -#define TypeIsBitGroup(t) \ - ((t) == BITOID || \ - (t) == VARBITOID) - - -static bool -DirectlyBinaryCompatible(Oid type1, Oid type2) -{ - HeapTuple tuple; - bool result; - - if (type1 == type2) - return true; - - tuple = SearchSysCache(CASTSOURCETARGET, type1, type2, 0, 0); - if (HeapTupleIsValid(tuple)) - { - Form_pg_cast caststruct; - - caststruct = (Form_pg_cast) GETSTRUCT(tuple); - result = caststruct->castfunc == InvalidOid && caststruct->castimplicit; - ReleaseSysCache(tuple); - } - else - result = false; - - return result; -} - - -bool -IsBinaryCompatible(Oid type1, Oid type2) -{ - if (DirectlyBinaryCompatible(type1, type2)) - return true; - /* - * Perhaps the types are domains; if so, look at their base types - */ - if (OidIsValid(type1)) - type1 = getBaseType(type1); - if (OidIsValid(type2)) - type2 = getBaseType(type2); - if (DirectlyBinaryCompatible(type1, type2)) - return true; - return false; -} - - /* IsPreferredType() * Check if this type is a preferred type. * XXX This should be moved to system catalog lookups @@ -733,7 +691,13 @@ PreferredType(CATEGORY category, Oid type) break; case (NUMERIC_TYPE): - if (TypeIsOidGroup(type)) + if (type == OIDOID || + type == REGPROCOID || + type == REGPROCEDUREOID || + type == REGOPEROID || + type == REGOPERATOROID || + type == REGCLASSOID || + type == REGTYPEOID) result = OIDOID; else if (type == NUMERICOID) result = NUMERICOID; @@ -768,30 +732,85 @@ PreferredType(CATEGORY category, Oid type) return result; } /* PreferredType() */ -/* - * find_coercion_function - * Look for a coercion function between two types. + +/* IsBinaryCompatible() + * Check if two types are binary-compatible. * - * A coercion function must be named after (the internal name of) its - * result type, and must accept exactly the specified input type. We - * also require it to be defined in the same namespace as its result type. - * Furthermore, unless we are doing explicit coercion the function must - * be marked as usable for implicit coercion --- this allows coercion - * functions to be provided that aren't implicitly invokable. + * This notion allows us to cheat and directly exchange values without + * going through the trouble of calling a conversion function. * - * This routine is also used to look for length-coercion functions, which - * are similar but accept a second argument. secondArgType is the type - * of the second argument (normally INT4OID), or InvalidOid if we are - * looking for a regular coercion function. + * As of 7.3, binary compatibility isn't hardwired into the code anymore. + * We consider two types binary-compatible if there is an implicit, + * no-function-needed pg_cast entry. NOTE that we assume that such + * entries are symmetric, ie, it doesn't matter which type we consider + * source and which target. (cf. checks in opr_sanity regression test) + */ +bool +IsBinaryCompatible(Oid type1, Oid type2) +{ + HeapTuple tuple; + Form_pg_cast castForm; + bool result; + + /* Fast path if same type */ + if (type1 == type2) + return true; + + /* Perhaps the types are domains; if so, look at their base types */ + if (OidIsValid(type1)) + type1 = getBaseType(type1); + if (OidIsValid(type2)) + type2 = getBaseType(type2); + + /* Somewhat-fast path if same base type */ + if (type1 == type2) + return true; + + /* Else look in pg_cast */ + tuple = SearchSysCache(CASTSOURCETARGET, + ObjectIdGetDatum(type1), + ObjectIdGetDatum(type2), + 0, 0); + if (!HeapTupleIsValid(tuple)) + return false; /* no cast */ + castForm = (Form_pg_cast) GETSTRUCT(tuple); + + result = (castForm->castfunc == InvalidOid) && castForm->castimplicit; + + ReleaseSysCache(tuple); + + return result; +} + + +/* + * find_coercion_pathway + * Look for a coercion pathway between two types. * - * If a function is found, return its pg_proc OID; else return InvalidOid. + * If we find a matching entry in pg_cast, return TRUE, and set *funcid + * to the castfunc value (which may be InvalidOid for a binary-compatible + * coercion). */ -static Oid -find_coercion_function(Oid targetTypeId, Oid sourceTypeId, bool isExplicit) +static bool +find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId, bool isExplicit, + Oid *funcid) { - Oid funcid = InvalidOid; + bool result = false; HeapTuple tuple; + *funcid = InvalidOid; + + /* Perhaps the types are domains; if so, look at their base types */ + if (OidIsValid(sourceTypeId)) + sourceTypeId = getBaseType(sourceTypeId); + if (OidIsValid(targetTypeId)) + targetTypeId = getBaseType(targetTypeId); + + /* Domains are automatically binary-compatible with their base type */ + if (sourceTypeId == targetTypeId) + return true; + + /* Else look in pg_cast */ tuple = SearchSysCache(CASTSOURCETARGET, ObjectIdGetDatum(sourceTypeId), ObjectIdGetDatum(targetTypeId), @@ -799,18 +818,36 @@ find_coercion_function(Oid targetTypeId, Oid sourceTypeId, bool isExplicit) if (HeapTupleIsValid(tuple)) { - Form_pg_cast cform = (Form_pg_cast) GETSTRUCT(tuple); + Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple); - if (isExplicit || cform->castimplicit) - funcid = cform->castfunc; + if (isExplicit || castForm->castimplicit) + { + *funcid = castForm->castfunc; + result = true; + } ReleaseSysCache(tuple); } - return funcid; + return result; } +/* + * 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. + * + * "bpchar" (ie, char(N)) and "numeric" are examples of such types. + * + * 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 ... + */ static Oid find_typmod_coercion_function(Oid typeId) { @@ -849,6 +886,7 @@ find_typmod_coercion_function(Oid typeId) } ReleaseSysCache(targetType); + return funcid; } @@ -877,74 +915,3 @@ build_func_call(Oid funcid, Oid rettype, List *args) return (Node *) expr; } - -/* - * Create an expression tree to enforce the constraints (if any) - * that should be applied by the type. Currently this is only - * interesting for domain types. - */ -Node * -coerce_type_constraints(ParseState *pstate, Node *arg, - Oid typeId, bool applyTypmod) -{ - char *notNull = NULL; - int32 typmod = -1; - - for (;;) - { - HeapTuple tup; - Form_pg_type typTup; - - tup = SearchSysCache(TYPEOID, - ObjectIdGetDatum(typeId), - 0, 0, 0); - if (!HeapTupleIsValid(tup)) - elog(ERROR, "coerce_type_constraints: failed to lookup type %u", - typeId); - typTup = (Form_pg_type) GETSTRUCT(tup); - - /* Test for NOT NULL Constraint */ - if (typTup->typnotnull && notNull == NULL) - notNull = pstrdup(NameStr(typTup->typname)); - - /* TODO: Add CHECK Constraints to domains */ - - if (typTup->typtype != 'd') - { - /* Not a domain, so done */ - ReleaseSysCache(tup); - break; - } - - Assert(typmod < 0); - - typeId = typTup->typbasetype; - typmod = typTup->typtypmod; - ReleaseSysCache(tup); - } - - /* - * If domain applies a typmod to its base type, do length coercion. - */ - if (applyTypmod && typmod >= 0) - arg = coerce_type_typmod(pstate, arg, typeId, typmod); - - /* - * Only need to add one NOT NULL check regardless of how many - * domains in the stack request it. The topmost domain that - * requested it is used as the constraint name. - */ - if (notNull) - { - ConstraintTest *r = makeNode(ConstraintTest); - - r->arg = arg; - r->testtype = CONSTR_TEST_NOTNULL; - r->name = notNull; - r->check_expr = NULL; - - arg = (Node *) r; - } - - return arg; -}