diff options
Diffstat (limited to 'src')
30 files changed, 1672 insertions, 262 deletions
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 12cb1720cde..2c327d45f15 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -13,7 +13,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.118 2009/06/11 14:48:55 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.119 2009/10/08 02:39:17 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -36,6 +36,7 @@ #include "catalog/pg_ts_template.h" #include "catalog/pg_type.h" #include "commands/dbcommands.h" +#include "funcapi.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "parser/parse_func.h" @@ -188,6 +189,8 @@ static void InitTempTableNamespace(void); static void RemoveTempRelations(Oid tempNamespaceId); static void RemoveTempRelationsCallback(int code, Datum arg); static void NamespaceCallback(Datum arg, int cacheid, ItemPointer tuplePtr); +static bool MatchNamedCall(HeapTuple proctup, int nargs, List *argnames, + int **argnumbers); /* These don't really need to appear in any header file */ Datum pg_table_is_visible(PG_FUNCTION_ARGS); @@ -562,8 +565,14 @@ TypeIsVisible(Oid typid) * retrieve a list of the possible matches. * * If nargs is -1, we return all functions matching the given name, - * regardless of argument count. (expand_variadic and expand_defaults must be - * false in this case.) + * regardless of argument count. (argnames must be NIL, and expand_variadic + * and expand_defaults must be false, in this case.) + * + * If argnames isn't NIL, we are considering a named- or mixed-notation call, + * and only functions having all the listed argument names will be returned. + * (We assume that length(argnames) <= nargs and all the passed-in names are + * distinct.) The returned structs will include an argnumbers array showing + * the actual argument index for each logical argument position. * * If expand_variadic is true, then variadic functions having the same number * or fewer arguments will be retrieved, with the variadic argument and any @@ -583,6 +592,13 @@ TypeIsVisible(Oid typid) * than nargs arguments while the variadic transformation requires the same * number or less. * + * When argnames isn't NIL, the returned args[] type arrays are not ordered + * according to the functions' declarations, but rather according to the call: + * first any positional arguments, then the named arguments, then defaulted + * arguments (if needed and allowed by expand_defaults). The argnumbers[] + * array can be used to map this back to the catalog information. + * argnumbers[k] is set to the proargtypes index of the k'th call argument. + * * We search a single namespace if the function name is qualified, else * all namespaces in the search path. In the multiple-namespace case, * we arrange for entries in earlier namespaces to mask identical entries in @@ -596,15 +612,16 @@ TypeIsVisible(Oid typid) * It is guaranteed that the return list will never contain multiple entries * with identical argument lists. When expand_defaults is true, the entries * could have more than nargs positions, but we still guarantee that they are - * distinct in the first nargs positions. However, if either expand_variadic - * or expand_defaults is true, there might be multiple candidate functions - * that expand to identical argument lists. Rather than throw error here, - * we report such situations by setting oid = 0 in the ambiguous entries. + * distinct in the first nargs positions. However, if argnames isn't NIL or + * either expand_variadic or expand_defaults is true, there might be multiple + * candidate functions that expand to identical argument lists. Rather than + * throw error here, we report such situations by returning a single entry + * with oid = 0 that represents a set of such conflicting candidates. * The caller might end up discarding such an entry anyway, but if it selects * such an entry it should react as though the call were ambiguous. */ FuncCandidateList -FuncnameGetCandidates(List *names, int nargs, +FuncnameGetCandidates(List *names, int nargs, List *argnames, bool expand_variadic, bool expand_defaults) { FuncCandidateList resultList = NULL; @@ -648,42 +665,9 @@ FuncnameGetCandidates(List *names, int nargs, bool variadic; bool use_defaults; Oid va_elem_type; + int *argnumbers = NULL; FuncCandidateList newResult; - /* - * Check if function is variadic, and get variadic element type if so. - * If expand_variadic is false, we should just ignore variadic-ness. - */ - if (pronargs <= nargs && expand_variadic) - { - va_elem_type = procform->provariadic; - variadic = OidIsValid(va_elem_type); - any_special |= variadic; - } - else - { - va_elem_type = InvalidOid; - variadic = false; - } - - /* - * Check if function can match by using parameter defaults. - */ - if (pronargs > nargs && expand_defaults) - { - /* Ignore if not enough default expressions */ - if (nargs + procform->pronargdefaults < pronargs) - continue; - use_defaults = true; - any_special = true; - } - else - use_defaults = false; - - /* Ignore if it doesn't match requested argument count */ - if (nargs >= 0 && pronargs != nargs && !variadic && !use_defaults) - continue; - if (OidIsValid(namespaceId)) { /* Consider only procs in specified namespace */ @@ -709,6 +693,88 @@ FuncnameGetCandidates(List *names, int nargs, continue; /* proc is not in search path */ } + if (argnames != NIL) + { + /* + * Call uses named or mixed notation + * + * Named or mixed notation can match a variadic function only + * if expand_variadic is off; otherwise there is no way to match + * the presumed-nameless parameters expanded from the variadic + * array. + */ + if (OidIsValid(procform->provariadic) && expand_variadic) + continue; + va_elem_type = InvalidOid; + variadic = false; + + /* + * Check argument count. + */ + Assert(nargs >= 0); /* -1 not supported with argnames */ + + if (pronargs > nargs && expand_defaults) + { + /* Ignore if not enough default expressions */ + if (nargs + procform->pronargdefaults < pronargs) + continue; + use_defaults = true; + } + else + use_defaults = false; + + /* Ignore if it doesn't match requested argument count */ + if (pronargs != nargs && !use_defaults) + continue; + + /* Check for argument name match, generate positional mapping */ + if (!MatchNamedCall(proctup, nargs, argnames, + &argnumbers)) + continue; + + /* Named argument matching is always "special" */ + any_special = true; + } + else + { + /* + * Call uses positional notation + * + * Check if function is variadic, and get variadic element type if + * so. If expand_variadic is false, we should just ignore + * variadic-ness. + */ + if (pronargs <= nargs && expand_variadic) + { + va_elem_type = procform->provariadic; + variadic = OidIsValid(va_elem_type); + any_special |= variadic; + } + else + { + va_elem_type = InvalidOid; + variadic = false; + } + + /* + * Check if function can match by using parameter defaults. + */ + if (pronargs > nargs && expand_defaults) + { + /* Ignore if not enough default expressions */ + if (nargs + procform->pronargdefaults < pronargs) + continue; + use_defaults = true; + any_special = true; + } + else + use_defaults = false; + + /* Ignore if it doesn't match requested argument count */ + if (nargs >= 0 && pronargs != nargs && !variadic && !use_defaults) + continue; + } + /* * We must compute the effective argument list so that we can easily * compare it to earlier results. We waste a palloc cycle if it gets @@ -722,8 +788,22 @@ FuncnameGetCandidates(List *names, int nargs, newResult->pathpos = pathpos; newResult->oid = HeapTupleGetOid(proctup); newResult->nargs = effective_nargs; - memcpy(newResult->args, procform->proargtypes.values, - pronargs * sizeof(Oid)); + newResult->argnumbers = argnumbers; + if (argnumbers) + { + /* Re-order the argument types into call's logical order */ + Oid *proargtypes = procform->proargtypes.values; + int i; + + for (i = 0; i < pronargs; i++) + newResult->args[i] = proargtypes[argnumbers[i]]; + } + else + { + /* Simple positional case, just copy proargtypes as-is */ + memcpy(newResult->args, procform->proargtypes.values, + pronargs * sizeof(Oid)); + } if (variadic) { int i; @@ -741,9 +821,9 @@ FuncnameGetCandidates(List *names, int nargs, * Does it have the same arguments as something we already accepted? * If so, decide what to do to avoid returning duplicate argument * lists. We can skip this check for the single-namespace case if no - * special (variadic or defaults) match has been made, since then the - * unique index on pg_proc guarantees all the matches have different - * argument lists. + * special (named, variadic or defaults) match has been made, since + * then the unique index on pg_proc guarantees all the matches have + * different argument lists. */ if (resultList != NULL && (any_special || !OidIsValid(namespaceId))) @@ -827,7 +907,8 @@ FuncnameGetCandidates(List *names, int nargs, * both foo(numeric, variadic numeric[]) and * foo(variadic numeric[]) in the same namespace, or * both foo(int) and foo (int, int default something) - * in the same namespace. + * in the same namespace, or both foo(a int, b text) + * and foo(b text, a int) in the same namespace. *---------- */ preference = 0; @@ -886,6 +967,125 @@ FuncnameGetCandidates(List *names, int nargs, } /* + * MatchNamedCall + * Given a pg_proc heap tuple and a call's list of argument names, + * check whether the function could match the call. + * + * The call could match if all supplied argument names are accepted by + * the function, in positions after the last positional argument, and there + * are defaults for all unsupplied arguments. + * + * The number of positional arguments is nargs - list_length(argnames). + * Note caller has already done basic checks on argument count. + * + * On match, return true and fill *argnumbers with a palloc'd array showing + * the mapping from call argument positions to actual function argument + * numbers. Defaulted arguments are included in this map, at positions + * after the last supplied argument. + */ +static bool +MatchNamedCall(HeapTuple proctup, int nargs, List *argnames, + int **argnumbers) +{ + Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); + int pronargs = procform->pronargs; + int numposargs = nargs - list_length(argnames); + int pronallargs; + Oid *p_argtypes; + char **p_argnames; + char *p_argmodes; + bool arggiven[FUNC_MAX_ARGS]; + bool isnull; + int ap; /* call args position */ + int pp; /* proargs position */ + ListCell *lc; + + Assert(argnames != NIL); + Assert(numposargs >= 0); + Assert(nargs <= pronargs); + + /* Ignore this function if its proargnames is null */ + (void) SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_proargnames, + &isnull); + if (isnull) + return false; + + /* OK, let's extract the argument names and types */ + pronallargs = get_func_arg_info(proctup, + &p_argtypes, &p_argnames, &p_argmodes); + Assert(p_argnames != NULL); + + /* initialize state for matching */ + *argnumbers = (int *) palloc(pronargs * sizeof(int)); + memset(arggiven, false, pronargs * sizeof(bool)); + + /* there are numposargs positional args before the named args */ + for (ap = 0; ap < numposargs; ap++) + { + (*argnumbers)[ap] = ap; + arggiven[ap] = true; + } + + /* now examine the named args */ + foreach(lc, argnames) + { + char *argname = (char *) lfirst(lc); + bool found; + int i; + + pp = 0; + found = false; + for (i = 0; i < pronallargs; i++) + { + /* consider only input parameters */ + if (p_argmodes && + (p_argmodes[i] != FUNC_PARAM_IN && + p_argmodes[i] != FUNC_PARAM_INOUT && + p_argmodes[i] != FUNC_PARAM_VARIADIC)) + continue; + if (p_argnames[i] && strcmp(p_argnames[i], argname) == 0) + { + /* fail if argname matches a positional argument */ + if (arggiven[pp]) + return false; + arggiven[pp] = true; + (*argnumbers)[ap] = pp; + found = true; + break; + } + /* increase pp only for input parameters */ + pp++; + } + /* if name isn't in proargnames, fail */ + if (!found) + return false; + ap++; + } + + Assert(ap == nargs); /* processed all actual parameters */ + + /* Check for default arguments */ + if (nargs < pronargs) + { + int first_arg_with_default = pronargs - procform->pronargdefaults; + + for (pp = numposargs; pp < pronargs; pp++) + { + if (arggiven[pp]) + continue; + /* fail if arg not given and no default available */ + if (pp < first_arg_with_default) + return false; + (*argnumbers)[ap++] = pp; + } + } + + Assert(ap == pronargs); /* processed all function parameters */ + + return true; +} + +/* * FunctionIsVisible * Determine whether a function (identified by OID) is visible in the * current search path. Visible means "would be found by searching @@ -932,7 +1132,7 @@ FunctionIsVisible(Oid funcid) visible = false; clist = FuncnameGetCandidates(list_make1(makeString(proname)), - nargs, false, false); + nargs, NIL, false, false); for (; clist; clist = clist->next) { @@ -1202,6 +1402,7 @@ OpernameGetCandidates(List *names, char oprkind) newResult->nargs = 2; newResult->nvargs = 0; newResult->ndargs = 0; + newResult->argnumbers = NULL; newResult->args[0] = operform->oprleft; newResult->args[1] = operform->oprright; newResult->next = resultList; diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c index 8c9f8bce7a7..7fae4b66b4d 100644 --- a/src/backend/catalog/pg_aggregate.c +++ b/src/backend/catalog/pg_aggregate.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/pg_aggregate.c,v 1.102 2009/06/11 14:48:55 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/pg_aggregate.c,v 1.103 2009/10/08 02:39:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -321,7 +321,8 @@ lookup_agg_function(List *fnName, * function's return value. it also returns the true argument types to * the function. */ - fdresult = func_get_detail(fnName, NIL, nargs, input_types, false, false, + fdresult = func_get_detail(fnName, NIL, NIL, + nargs, input_types, false, false, &fnOid, rettype, &retset, &nvargs, &true_oid_array, NULL); @@ -330,12 +331,14 @@ lookup_agg_function(List *fnName, ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function %s does not exist", - func_signature_string(fnName, nargs, input_types)))); + func_signature_string(fnName, nargs, + NIL, input_types)))); if (retset) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("function %s returns a set", - func_signature_string(fnName, nargs, input_types)))); + func_signature_string(fnName, nargs, + NIL, input_types)))); /* * If there are any polymorphic types involved, enforce consistency, and @@ -359,7 +362,8 @@ lookup_agg_function(List *fnName, ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("function %s requires run-time type coercion", - func_signature_string(fnName, nargs, true_oid_array)))); + func_signature_string(fnName, nargs, + NIL, true_oid_array)))); } /* Check aggregate creator has permission to call the function */ diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index db69c127ad4..c7ee17a57b7 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.167 2009/10/05 19:24:36 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.168 2009/10/08 02:39:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -348,6 +348,8 @@ ProcedureCreate(const char *procedureName, { /* There is one; okay to replace it? */ Form_pg_proc oldproc = (Form_pg_proc) GETSTRUCT(oldtup); + Datum proargnames; + bool isnull; if (!replace) ereport(ERROR, @@ -394,6 +396,49 @@ ProcedureCreate(const char *procedureName, } /* + * If there were any named input parameters, check to make sure the + * names have not been changed, as this could break existing calls. + * We allow adding names to formerly unnamed parameters, though. + */ + proargnames = SysCacheGetAttr(PROCNAMEARGSNSP, oldtup, + Anum_pg_proc_proargnames, + &isnull); + if (!isnull) + { + Datum proargmodes; + char **old_arg_names; + char **new_arg_names; + int n_old_arg_names; + int n_new_arg_names; + int j; + + proargmodes = SysCacheGetAttr(PROCNAMEARGSNSP, oldtup, + Anum_pg_proc_proargmodes, + &isnull); + if (isnull) + proargmodes = PointerGetDatum(NULL); /* just to be sure */ + + n_old_arg_names = get_func_input_arg_names(proargnames, + proargmodes, + &old_arg_names); + n_new_arg_names = get_func_input_arg_names(parameterNames, + parameterModes, + &new_arg_names); + for (j = 0; j < n_old_arg_names; j++) + { + if (old_arg_names[j] == NULL) + continue; + if (j >= n_new_arg_names || new_arg_names[j] == NULL || + strcmp(old_arg_names[j], new_arg_names[j]) != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("cannot change name of input parameter \"%s\"", + old_arg_names[j]), + errhint("Use DROP FUNCTION first."))); + } + } + + /* * If there are existing defaults, check compatibility: redefinition * must not remove any defaults nor change their types. (Removing a * default might cause a function to fail to satisfy an existing call. @@ -404,7 +449,6 @@ ProcedureCreate(const char *procedureName, if (oldproc->pronargdefaults != 0) { Datum proargdefaults; - bool isnull; List *oldDefaults; ListCell *oldlc; ListCell *newlc; diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c index 461f81005c2..bafc2856606 100644 --- a/src/backend/commands/aggregatecmds.c +++ b/src/backend/commands/aggregatecmds.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/aggregatecmds.c,v 1.49 2009/06/11 14:48:55 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/aggregatecmds.c,v 1.50 2009/10/08 02:39:18 tgl Exp $ * * DESCRIPTION * The "DefineFoo" routines take the parse tree and pick out the @@ -297,6 +297,7 @@ RenameAggregate(List *name, List *args, const char *newname) errmsg("function %s already exists in schema \"%s\"", funcname_signature_string(newname, procForm->pronargs, + NIL, procForm->proargtypes.values), get_namespace_name(namespaceOid)))); diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index cf206b3f090..40097a80c72 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/functioncmds.c,v 1.111 2009/09/22 23:43:37 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/functioncmds.c,v 1.112 2009/10/08 02:39:19 tgl Exp $ * * DESCRIPTION * These routines take the parse tree and pick out the @@ -285,6 +285,39 @@ examine_parameter_list(List *parameters, Oid languageOid, if (fp->name && fp->name[0]) { + ListCell *px; + + /* + * As of Postgres 8.5 we disallow using the same name for two + * input or two output function parameters. Depending on the + * function's language, conflicting input and output names might + * be bad too, but we leave it to the PL to complain if so. + */ + foreach(px, parameters) + { + FunctionParameter *prevfp = (FunctionParameter *) lfirst(px); + + if (prevfp == fp) + break; + /* pure in doesn't conflict with pure out */ + if ((fp->mode == FUNC_PARAM_IN || + fp->mode == FUNC_PARAM_VARIADIC) && + (prevfp->mode == FUNC_PARAM_OUT || + prevfp->mode == FUNC_PARAM_TABLE)) + continue; + if ((prevfp->mode == FUNC_PARAM_IN || + prevfp->mode == FUNC_PARAM_VARIADIC) && + (fp->mode == FUNC_PARAM_OUT || + fp->mode == FUNC_PARAM_TABLE)) + continue; + if (prevfp->name && prevfp->name[0] && + strcmp(prevfp->name, fp->name) == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("parameter name \"%s\" used more than once", + fp->name))); + } + paramNames[i] = CStringGetTextDatum(fp->name); have_names = true; } @@ -1097,6 +1130,7 @@ RenameFunction(List *name, List *argtypes, const char *newname) errmsg("function %s already exists in schema \"%s\"", funcname_signature_string(newname, procForm->pronargs, + NIL, procForm->proargtypes.values), get_namespace_name(namespaceOid)))); } diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c index 5339e1783c4..8696ea85730 100644 --- a/src/backend/commands/tsearchcmds.c +++ b/src/backend/commands/tsearchcmds.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/tsearchcmds.c,v 1.17 2009/06/11 14:48:56 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tsearchcmds.c,v 1.18 2009/10/08 02:39:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -107,7 +107,7 @@ get_ts_parser_func(DefElem *defel, int attnum) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("function %s should return type %s", - func_signature_string(funcName, nargs, typeId), + func_signature_string(funcName, nargs, NIL, typeId), format_type_be(retTypeId)))); return ObjectIdGetDatum(procOid); @@ -945,7 +945,7 @@ get_ts_template_func(DefElem *defel, int attnum) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("function %s should return type %s", - func_signature_string(funcName, nargs, typeId), + func_signature_string(funcName, nargs, NIL, typeId), format_type_be(retTypeId)))); return ObjectIdGetDatum(procOid); diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 5fa2ac6bc90..4f997a0dce1 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.137 2009/07/30 02:45:36 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.138 2009/10/08 02:39:19 tgl Exp $ * * DESCRIPTION * The "DefineFoo" routines take the parse tree and pick out the @@ -1267,7 +1267,7 @@ findTypeInputFunction(List *procname, Oid typeOid) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function %s does not exist", - func_signature_string(procname, 1, argList)))); + func_signature_string(procname, 1, NIL, argList)))); return InvalidOid; /* keep compiler quiet */ } @@ -1318,7 +1318,7 @@ findTypeOutputFunction(List *procname, Oid typeOid) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function %s does not exist", - func_signature_string(procname, 1, argList)))); + func_signature_string(procname, 1, NIL, argList)))); return InvalidOid; /* keep compiler quiet */ } @@ -1349,7 +1349,7 @@ findTypeReceiveFunction(List *procname, Oid typeOid) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function %s does not exist", - func_signature_string(procname, 1, argList)))); + func_signature_string(procname, 1, NIL, argList)))); return InvalidOid; /* keep compiler quiet */ } @@ -1372,7 +1372,7 @@ findTypeSendFunction(List *procname, Oid typeOid) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function %s does not exist", - func_signature_string(procname, 1, argList)))); + func_signature_string(procname, 1, NIL, argList)))); return InvalidOid; /* keep compiler quiet */ } @@ -1393,7 +1393,7 @@ findTypeTypmodinFunction(List *procname) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function %s does not exist", - func_signature_string(procname, 1, argList)))); + func_signature_string(procname, 1, NIL, argList)))); if (get_func_rettype(procOid) != INT4OID) ereport(ERROR, @@ -1420,7 +1420,7 @@ findTypeTypmodoutFunction(List *procname) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function %s does not exist", - func_signature_string(procname, 1, argList)))); + func_signature_string(procname, 1, NIL, argList)))); if (get_func_rettype(procOid) != CSTRINGOID) ereport(ERROR, @@ -1447,7 +1447,7 @@ findTypeAnalyzeFunction(List *procname, Oid typeOid) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function %s does not exist", - func_signature_string(procname, 1, argList)))); + func_signature_string(procname, 1, NIL, argList)))); if (get_func_rettype(procOid) != BOOLOID) ereport(ERROR, diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 9319aa84c5e..27c231701d6 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.441 2009/10/07 22:14:20 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.442 2009/10/08 02:39:20 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1020,6 +1020,22 @@ _copyFuncExpr(FuncExpr *from) } /* + * _copyNamedArgExpr * + */ +static NamedArgExpr * +_copyNamedArgExpr(NamedArgExpr *from) +{ + NamedArgExpr *newnode = makeNode(NamedArgExpr); + + COPY_NODE_FIELD(arg); + COPY_STRING_FIELD(name); + COPY_SCALAR_FIELD(argnumber); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* * _copyOpExpr */ static OpExpr * @@ -3587,6 +3603,9 @@ copyObject(void *from) case T_FuncExpr: retval = _copyFuncExpr(from); break; + case T_NamedArgExpr: + retval = _copyNamedArgExpr(from); + break; case T_OpExpr: retval = _copyOpExpr(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 6a61112b99c..5766f37c143 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -22,7 +22,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.364 2009/10/07 22:14:20 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.365 2009/10/08 02:39:20 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -242,6 +242,17 @@ _equalFuncExpr(FuncExpr *a, FuncExpr *b) } static bool +_equalNamedArgExpr(NamedArgExpr *a, NamedArgExpr *b) +{ + COMPARE_NODE_FIELD(arg); + COMPARE_STRING_FIELD(name); + COMPARE_SCALAR_FIELD(argnumber); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool _equalOpExpr(OpExpr *a, OpExpr *b) { COMPARE_SCALAR_FIELD(opno); @@ -2375,6 +2386,9 @@ equal(void *a, void *b) case T_FuncExpr: retval = _equalFuncExpr(a, b); break; + case T_NamedArgExpr: + retval = _equalNamedArgExpr(a, b); + break; case T_OpExpr: retval = _equalOpExpr(a, b); break; diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 61c0e21db08..04a39f17526 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/nodeFuncs.c,v 1.42 2009/07/30 02:45:37 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/nodeFuncs.c,v 1.43 2009/10/08 02:39:21 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -69,6 +69,9 @@ exprType(Node *expr) case T_FuncExpr: type = ((FuncExpr *) expr)->funcresulttype; break; + case T_NamedArgExpr: + type = exprType((Node *) ((NamedArgExpr *) expr)->arg); + break; case T_OpExpr: type = ((OpExpr *) expr)->opresulttype; break; @@ -259,6 +262,8 @@ exprTypmod(Node *expr) return coercedTypmod; } break; + case T_NamedArgExpr: + return exprTypmod((Node *) ((NamedArgExpr *) expr)->arg); case T_SubLink: { SubLink *sublink = (SubLink *) expr; @@ -676,6 +681,15 @@ exprLocation(Node *expr) exprLocation((Node *) fexpr->args)); } break; + case T_NamedArgExpr: + { + NamedArgExpr *na = (NamedArgExpr *) expr; + + /* consider both argument name and value */ + loc = leftmostLoc(na->location, + exprLocation((Node *) na->arg)); + } + break; case T_OpExpr: case T_DistinctExpr: /* struct-equivalent to OpExpr */ case T_NullIfExpr: /* struct-equivalent to OpExpr */ @@ -1117,6 +1131,8 @@ expression_tree_walker(Node *node, return true; } break; + case T_NamedArgExpr: + return walker(((NamedArgExpr *) node)->arg, context); case T_OpExpr: { OpExpr *expr = (OpExpr *) node; @@ -1623,6 +1639,16 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } break; + case T_NamedArgExpr: + { + NamedArgExpr *nexpr = (NamedArgExpr *) node; + NamedArgExpr *newnode; + + FLATCOPY(newnode, nexpr, NamedArgExpr); + MUTATE(newnode->arg, nexpr->arg, Expr *); + return (Node *) newnode; + } + break; case T_OpExpr: { OpExpr *expr = (OpExpr *) node; @@ -2382,6 +2408,8 @@ bool /* function name is deemed uninteresting */ } break; + case T_NamedArgExpr: + return walker(((NamedArgExpr *) node)->arg, context); case T_A_Indices: { A_Indices *indices = (A_Indices *) node; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 6751cb1a344..79665ed12a0 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.365 2009/10/06 00:55:26 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.366 2009/10/08 02:39:21 tgl Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -876,6 +876,17 @@ _outFuncExpr(StringInfo str, FuncExpr *node) } static void +_outNamedArgExpr(StringInfo str, NamedArgExpr *node) +{ + WRITE_NODE_TYPE("NAMEDARGEXPR"); + + WRITE_NODE_FIELD(arg); + WRITE_STRING_FIELD(name); + WRITE_INT_FIELD(argnumber); + WRITE_LOCATION_FIELD(location); +} + +static void _outOpExpr(StringInfo str, OpExpr *node) { WRITE_NODE_TYPE("OPEXPR"); @@ -2514,6 +2525,9 @@ _outNode(StringInfo str, void *obj) case T_FuncExpr: _outFuncExpr(str, obj); break; + case T_NamedArgExpr: + _outNamedArgExpr(str, obj); + break; case T_OpExpr: _outOpExpr(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 8f5264d3575..205b6da4cd8 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.223 2009/07/16 06:33:42 petere Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.224 2009/10/08 02:39:21 tgl Exp $ * * NOTES * Path and Plan nodes do not have any readfuncs support, because we @@ -526,6 +526,22 @@ _readFuncExpr(void) } /* + * _readNamedArgExpr + */ +static NamedArgExpr * +_readNamedArgExpr(void) +{ + READ_LOCALS(NamedArgExpr); + + READ_NODE_FIELD(arg); + READ_STRING_FIELD(name); + READ_INT_FIELD(argnumber); + READ_LOCATION_FIELD(location); + + READ_DONE(); +} + +/* * _readOpExpr */ static OpExpr * @@ -1207,6 +1223,8 @@ parseNodeString(void) return_value = _readArrayRef(); else if (MATCH("FUNCEXPR", 8)) return_value = _readFuncExpr(); + else if (MATCH("NAMEDARGEXPR", 12)) + return_value = _readNamedArgExpr(); else if (MATCH("OPEXPR", 6)) return_value = _readOpExpr(); else if (MATCH("DISTINCTEXPR", 12)) diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 3f344b3a145..64f77a5c71e 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.256 2009/06/11 14:48:59 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.257 2009/10/08 02:39:21 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -526,7 +526,8 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind) /* * Simplify constant expressions. * - * Note: one essential effect here is to insert the current actual values + * Note: an essential effect of this is to convert named-argument function + * calls to positional notation and insert the current actual values * of any default arguments for functions. To ensure that happens, we * *must* process all expressions here. Previous PG versions sometimes * skipped const-simplification if it didn't seem worth the trouble, but @@ -2658,9 +2659,10 @@ get_column_info_for_window(PlannerInfo *root, WindowClause *wc, List *tlist, * Currently, we disallow sublinks in standalone expressions, so there's no * real "planning" involved here. (That might not always be true though.) * What we must do is run eval_const_expressions to ensure that any function - * default arguments get inserted. The fact that constant subexpressions - * get simplified is a side-effect that is useful when the expression will - * get evaluated more than once. Also, we must fix operator function IDs. + * calls are converted to positional notation and function default arguments + * get inserted. The fact that constant subexpressions get simplified is a + * side-effect that is useful when the expression will get evaluated more than + * once. Also, we must fix operator function IDs. * * Note: this must not make any damaging changes to the passed-in expression * tree. (It would actually be okay to apply fix_opfuncids to it, but since @@ -2672,7 +2674,10 @@ expression_planner(Expr *expr) { Node *result; - /* Insert default arguments and simplify constant subexprs */ + /* + * Convert named-argument function calls, insert default arguments and + * simplify constant subexprs + */ result = eval_const_expressions(NULL, (Node *) expr); /* Fill in opfuncid values if missing */ diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index f2038c73af1..dcfc731a17a 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.278 2009/07/20 00:24:30 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.279 2009/10/08 02:39:21 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -95,11 +95,18 @@ static List *simplify_and_arguments(List *args, static Expr *simplify_boolean_equality(Oid opno, List *args); static Expr *simplify_function(Oid funcid, Oid result_type, int32 result_typmod, List **args, + bool has_named_args, bool allow_inline, eval_const_expressions_context *context); +static List *reorder_function_arguments(List *args, Oid result_type, + HeapTuple func_tuple, + eval_const_expressions_context *context); static List *add_function_defaults(List *args, Oid result_type, HeapTuple func_tuple, eval_const_expressions_context *context); +static List *fetch_function_defaults(HeapTuple func_tuple); +static void recheck_cast_function_args(List *args, Oid result_type, + HeapTuple func_tuple); static Expr *evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, List *args, HeapTuple func_tuple, @@ -2003,7 +2010,8 @@ rowtype_field_matches(Oid rowtypeid, int fieldnum, * OR clauses into N-argument form. See comments in prepqual.c. * * NOTE: another critical effect is that any function calls that require - * default arguments will be expanded. + * default arguments will be expanded, and named-argument calls will be + * converted to positional notation. The executor won't handle either. *-------------------- */ Node * @@ -2113,17 +2121,26 @@ eval_const_expressions_mutator(Node *node, { FuncExpr *expr = (FuncExpr *) node; List *args; + bool has_named_args; Expr *simple; FuncExpr *newexpr; + ListCell *lc; /* - * Reduce constants in the FuncExpr's arguments. We know args is - * either NIL or a List node, so we can call expression_tree_mutator - * directly rather than recursing to self. + * Reduce constants in the FuncExpr's arguments, and check to see + * if there are any named args. */ - args = (List *) expression_tree_mutator((Node *) expr->args, - eval_const_expressions_mutator, - (void *) context); + args = NIL; + has_named_args = false; + foreach(lc, expr->args) + { + Node *arg = (Node *) lfirst(lc); + + arg = eval_const_expressions_mutator(arg, context); + if (IsA(arg, NamedArgExpr)) + has_named_args = true; + args = lappend(args, arg); + } /* * Code for op/func reduction is pretty bulky, so split it out as a @@ -2134,14 +2151,15 @@ eval_const_expressions_mutator(Node *node, simple = simplify_function(expr->funcid, expr->funcresulttype, exprTypmod(node), &args, - true, context); + has_named_args, true, context); if (simple) /* successfully simplified it */ return (Node *) simple; /* * The expression cannot be simplified any further, so build and * return a replacement FuncExpr node using the possibly-simplified - * arguments. + * arguments. Note that we have also converted the argument list + * to positional notation. */ newexpr = makeNode(FuncExpr); newexpr->funcid = expr->funcid; @@ -2181,7 +2199,7 @@ eval_const_expressions_mutator(Node *node, simple = simplify_function(expr->opfuncid, expr->opresulttype, -1, &args, - true, context); + false, true, context); if (simple) /* successfully simplified it */ return (Node *) simple; @@ -2274,7 +2292,7 @@ eval_const_expressions_mutator(Node *node, simple = simplify_function(expr->opfuncid, expr->opresulttype, -1, &args, - false, context); + false, false, context); if (simple) /* successfully simplified it */ { /* @@ -2466,7 +2484,7 @@ eval_const_expressions_mutator(Node *node, simple = simplify_function(outfunc, CSTRINGOID, -1, &args, - true, context); + false, true, context); if (simple) /* successfully simplified output fn */ { /* @@ -2484,7 +2502,7 @@ eval_const_expressions_mutator(Node *node, simple = simplify_function(infunc, expr->resulttype, -1, &args, - true, context); + false, true, context); if (simple) /* successfully simplified input fn */ return (Node *) simple; } @@ -3241,15 +3259,16 @@ simplify_boolean_equality(Oid opno, List *args) * Returns a simplified expression if successful, or NULL if cannot * simplify the function call. * - * This function is also responsible for adding any default argument - * expressions onto the function argument list; which is a bit grotty, - * but it avoids an extra fetch of the function's pg_proc tuple. For this - * reason, the args list is pass-by-reference, and it may get modified - * even if simplification fails. + * This function is also responsible for converting named-notation argument + * lists into positional notation and/or adding any needed default argument + * expressions; which is a bit grotty, but it avoids an extra fetch of the + * function's pg_proc tuple. For this reason, the args list is + * pass-by-reference, and it may get modified even if simplification fails. */ static Expr * simplify_function(Oid funcid, Oid result_type, int32 result_typmod, List **args, + bool has_named_args, bool allow_inline, eval_const_expressions_context *context) { @@ -3270,8 +3289,14 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod, if (!HeapTupleIsValid(func_tuple)) elog(ERROR, "cache lookup failed for function %u", funcid); - /* While we have the tuple, check if we need to add defaults */ - if (((Form_pg_proc) GETSTRUCT(func_tuple))->pronargs > list_length(*args)) + /* + * While we have the tuple, reorder named arguments and add default + * arguments if needed. + */ + if (has_named_args) + *args = reorder_function_arguments(*args, result_type, func_tuple, + context); + else if (((Form_pg_proc) GETSTRUCT(func_tuple))->pronargs > list_length(*args)) *args = add_function_defaults(*args, result_type, func_tuple, context); newexpr = evaluate_function(funcid, result_type, result_typmod, *args, @@ -3287,12 +3312,112 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod, } /* + * reorder_function_arguments: convert named-notation args to positional args + * + * This function also inserts default argument values as needed, since it's + * impossible to form a truly valid positional call without that. + */ +static List * +reorder_function_arguments(List *args, Oid result_type, HeapTuple func_tuple, + eval_const_expressions_context *context) +{ + Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple); + int pronargs = funcform->pronargs; + int nargsprovided = list_length(args); + Node *argarray[FUNC_MAX_ARGS]; + Bitmapset *defargnumbers; + ListCell *lc; + int i; + + Assert(nargsprovided <= pronargs); + if (pronargs > FUNC_MAX_ARGS) + elog(ERROR, "too many function arguments"); + MemSet(argarray, 0, pronargs * sizeof(Node *)); + + /* Deconstruct the argument list into an array indexed by argnumber */ + i = 0; + foreach(lc, args) + { + Node *arg = (Node *) lfirst(lc); + + if (!IsA(arg, NamedArgExpr)) + { + /* positional argument, assumed to precede all named args */ + Assert(argarray[i] == NULL); + argarray[i++] = arg; + } + else + { + NamedArgExpr *na = (NamedArgExpr *) arg; + + Assert(argarray[na->argnumber] == NULL); + argarray[na->argnumber] = (Node *) na->arg; + } + } + + /* + * Fetch default expressions, if needed, and insert into array at + * proper locations (they aren't necessarily consecutive or all used) + */ + defargnumbers = NULL; + if (nargsprovided < pronargs) + { + List *defaults = fetch_function_defaults(func_tuple); + + i = pronargs - funcform->pronargdefaults; + foreach(lc, defaults) + { + if (argarray[i] == NULL) + { + argarray[i] = (Node *) lfirst(lc); + defargnumbers = bms_add_member(defargnumbers, i); + } + i++; + } + } + + /* Now reconstruct the args list in proper order */ + args = NIL; + for (i = 0; i < pronargs; i++) + { + Assert(argarray[i] != NULL); + args = lappend(args, argarray[i]); + } + + /* Recheck argument types and add casts if needed */ + recheck_cast_function_args(args, result_type, func_tuple); + + /* + * Lastly, we have to recursively simplify the defaults we just added + * (but don't recurse on the args passed in, as we already did those). + * This isn't merely an optimization, it's *necessary* since there could + * be functions with named or defaulted arguments down in there. + * + * Note that we do this last in hopes of simplifying any typecasts that + * were added by recheck_cast_function_args --- there shouldn't be any new + * casts added to the explicit arguments, but casts on the defaults are + * possible. + */ + if (defargnumbers != NULL) + { + i = 0; + foreach(lc, args) + { + if (bms_is_member(i, defargnumbers)) + lfirst(lc) = eval_const_expressions_mutator((Node *) lfirst(lc), + context); + i++; + } + } + + return args; +} + +/* * add_function_defaults: add missing function arguments from its defaults * - * It is possible for some of the defaulted arguments to be polymorphic; - * therefore we can't assume that the default expressions have the correct - * data types already. We have to re-resolve polymorphics and do coercion - * just like the parser did. + * This is used only when the argument list was positional to begin with, + * and so we know we just need to add defaults at the end. */ static List * add_function_defaults(List *args, Oid result_type, HeapTuple func_tuple, @@ -3300,41 +3425,96 @@ add_function_defaults(List *args, Oid result_type, HeapTuple func_tuple, { Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple); int nargsprovided = list_length(args); - Datum proargdefaults; - bool isnull; - char *str; List *defaults; int ndelete; - int nargs; - Oid actual_arg_types[FUNC_MAX_ARGS]; - Oid declared_arg_types[FUNC_MAX_ARGS]; - Oid rettype; ListCell *lc; - /* The error cases here shouldn't happen, but check anyway */ - proargdefaults = SysCacheGetAttr(PROCOID, func_tuple, - Anum_pg_proc_proargdefaults, - &isnull); - if (isnull) - elog(ERROR, "not enough default arguments"); - str = TextDatumGetCString(proargdefaults); - defaults = (List *) stringToNode(str); - Assert(IsA(defaults, List)); - pfree(str); + /* Get all the default expressions from the pg_proc tuple */ + defaults = fetch_function_defaults(func_tuple); + /* Delete any unused defaults from the list */ ndelete = nargsprovided + list_length(defaults) - funcform->pronargs; if (ndelete < 0) elog(ERROR, "not enough default arguments"); while (ndelete-- > 0) defaults = list_delete_first(defaults); + /* And form the combined argument list */ args = list_concat(args, defaults); - Assert(list_length(args) == funcform->pronargs); + + /* Recheck argument types and add casts if needed */ + recheck_cast_function_args(args, result_type, func_tuple); /* - * The next part should be a no-op if there are no polymorphic arguments, - * but we do it anyway to be sure. + * Lastly, we have to recursively simplify the defaults we just added + * (but don't recurse on the args passed in, as we already did those). + * This isn't merely an optimization, it's *necessary* since there could + * be functions with named or defaulted arguments down in there. + * + * Note that we do this last in hopes of simplifying any typecasts that + * were added by recheck_cast_function_args --- there shouldn't be any new + * casts added to the explicit arguments, but casts on the defaults are + * possible. */ + foreach(lc, args) + { + if (nargsprovided-- > 0) + continue; /* skip original arg positions */ + lfirst(lc) = eval_const_expressions_mutator((Node *) lfirst(lc), + context); + } + + return args; +} + +/* + * fetch_function_defaults: get function's default arguments as expression list + */ +static List * +fetch_function_defaults(HeapTuple func_tuple) +{ + List *defaults; + Datum proargdefaults; + bool isnull; + char *str; + + /* The error cases here shouldn't happen, but check anyway */ + proargdefaults = SysCacheGetAttr(PROCOID, func_tuple, + Anum_pg_proc_proargdefaults, + &isnull); + if (isnull) + elog(ERROR, "not enough default arguments"); + str = TextDatumGetCString(proargdefaults); + defaults = (List *) stringToNode(str); + Assert(IsA(defaults, List)); + pfree(str); + return defaults; +} + +/* + * recheck_cast_function_args: recheck function args and typecast as needed + * after adding defaults. + * + * It is possible for some of the defaulted arguments to be polymorphic; + * therefore we can't assume that the default expressions have the correct + * data types already. We have to re-resolve polymorphics and do coercion + * just like the parser did. + * + * This should be a no-op if there are no polymorphic arguments, + * but we do it anyway to be sure. + * + * Note: if any casts are needed, the args list is modified in-place. + */ +static void +recheck_cast_function_args(List *args, Oid result_type, HeapTuple func_tuple) +{ + Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple); + int nargs; + Oid actual_arg_types[FUNC_MAX_ARGS]; + Oid declared_arg_types[FUNC_MAX_ARGS]; + Oid rettype; + ListCell *lc; + if (list_length(args) > FUNC_MAX_ARGS) elog(ERROR, "too many function arguments"); nargs = 0; @@ -3342,6 +3522,7 @@ add_function_defaults(List *args, Oid result_type, HeapTuple func_tuple, { actual_arg_types[nargs++] = exprType((Node *) lfirst(lc)); } + Assert(nargs == funcform->pronargs); memcpy(declared_arg_types, funcform->proargtypes.values, funcform->pronargs * sizeof(Oid)); rettype = enforce_generic_type_consistency(actual_arg_types, @@ -3355,22 +3536,6 @@ add_function_defaults(List *args, Oid result_type, HeapTuple func_tuple, /* perform any necessary typecasting of arguments */ make_fn_arguments(NULL, args, actual_arg_types, declared_arg_types); - - /* - * Lastly, we have to recursively simplify the arguments we just added - * (but don't recurse on the ones passed in, as we already did those). - * This isn't merely an optimization, it's *necessary* since there could - * be functions with defaulted arguments down in there. - */ - foreach(lc, args) - { - if (nargsprovided-- > 0) - continue; /* skip original arg positions */ - lfirst(lc) = eval_const_expressions_mutator((Node *) lfirst(lc), - context); - } - - return args; } /* @@ -3916,6 +4081,7 @@ Query * inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) { FuncExpr *fexpr; + Oid func_oid; HeapTuple func_tuple; Form_pg_proc funcform; Oid *argtypes; @@ -3944,6 +4110,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) fexpr = (FuncExpr *) rte->funcexpr; if (fexpr == NULL || !IsA(fexpr, FuncExpr)) return NULL; + func_oid = fexpr->funcid; /* * The function must be declared to return a set, else inlining would @@ -3967,17 +4134,17 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) return NULL; /* Check permission to call function (fail later, if not) */ - if (pg_proc_aclcheck(fexpr->funcid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK) + if (pg_proc_aclcheck(func_oid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK) return NULL; /* * OK, let's take a look at the function's pg_proc entry. */ func_tuple = SearchSysCache(PROCOID, - ObjectIdGetDatum(fexpr->funcid), + ObjectIdGetDatum(func_oid), 0, 0, 0); if (!HeapTupleIsValid(func_tuple)) - elog(ERROR, "cache lookup failed for function %u", fexpr->funcid); + elog(ERROR, "cache lookup failed for function %u", func_oid); funcform = (Form_pg_proc) GETSTRUCT(func_tuple); /* @@ -3985,16 +4152,15 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) * properties. In particular it mustn't be declared STRICT, since we * couldn't enforce that. It also mustn't be VOLATILE, because that is * supposed to cause it to be executed with its own snapshot, rather than - * sharing the snapshot of the calling query. (The nargs check is just - * paranoia, ditto rechecking proretset.) + * sharing the snapshot of the calling query. (Rechecking proretset is + * just paranoia.) */ if (funcform->prolang != SQLlanguageId || funcform->proisstrict || funcform->provolatile == PROVOLATILE_VOLATILE || funcform->prosecdef || !funcform->proretset || - !heap_attisnull(func_tuple, Anum_pg_proc_proconfig) || - funcform->pronargs != list_length(fexpr->args)) + !heap_attisnull(func_tuple, Anum_pg_proc_proconfig)) { ReleaseSysCache(func_tuple); return NULL; @@ -4020,6 +4186,24 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) ALLOCSET_DEFAULT_MAXSIZE); oldcxt = MemoryContextSwitchTo(mycxt); + /* + * Run eval_const_expressions on the function call. This is necessary + * to ensure that named-argument notation is converted to positional + * notation and any default arguments are inserted. It's a bit of + * overkill for the arguments, since they'll get processed again later, + * but no harm will be done. + */ + fexpr = (FuncExpr *) eval_const_expressions(root, (Node *) fexpr); + + /* It should still be a call of the same function, but let's check */ + if (!IsA(fexpr, FuncExpr) || + fexpr->funcid != func_oid) + goto fail; + + /* Arg list length should now match the function */ + if (list_length(fexpr->args) != funcform->pronargs) + goto fail; + /* Check for polymorphic arguments, and substitute actual arg types */ argtypes = (Oid *) palloc(funcform->pronargs * sizeof(Oid)); memcpy(argtypes, funcform->proargtypes.values, @@ -4038,7 +4222,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) Anum_pg_proc_prosrc, &isNull); if (isNull) - elog(ERROR, "null prosrc for function %u", fexpr->funcid); + elog(ERROR, "null prosrc for function %u", func_oid); src = TextDatumGetCString(tmp); /* @@ -4076,7 +4260,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) * shows it's returning a whole tuple result; otherwise what it's * returning is a single composite column which is not what we need. */ - if (!check_sql_fn_retval(fexpr->funcid, fexpr->funcresulttype, + if (!check_sql_fn_retval(func_oid, fexpr->funcresulttype, querytree_list, true, NULL) && (get_typtype(fexpr->funcresulttype) == TYPTYPE_COMPOSITE || @@ -4116,7 +4300,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) * Since there is now no trace of the function in the plan tree, we must * explicitly record the plan's dependency on the function. */ - record_plan_function_dependency(root->glob, fexpr->funcid); + record_plan_function_dependency(root->glob, func_oid); return querytree; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index ed265f516b2..b86df8a870f 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.681 2009/10/07 22:14:21 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.682 2009/10/08 02:39:22 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -354,6 +354,8 @@ static TypeName *TableFuncTypeName(List *columns); %type <node> def_arg columnElem where_clause where_or_current_clause a_expr b_expr c_expr func_expr AexprConst indirection_el columnref in_expr having_clause func_table array_expr +%type <list> func_arg_list +%type <node> func_arg_expr %type <list> row type_list array_expr_list %type <node> case_expr case_arg when_clause case_default %type <list> when_clause_list @@ -9055,7 +9057,7 @@ func_expr: func_name '(' ')' over_clause n->location = @1; $$ = (Node *)n; } - | func_name '(' expr_list ')' over_clause + | func_name '(' func_arg_list ')' over_clause { FuncCall *n = makeNode(FuncCall); n->funcname = $1; @@ -9067,7 +9069,7 @@ func_expr: func_name '(' ')' over_clause n->location = @1; $$ = (Node *)n; } - | func_name '(' VARIADIC a_expr ')' over_clause + | func_name '(' VARIADIC func_arg_expr ')' over_clause { FuncCall *n = makeNode(FuncCall); n->funcname = $1; @@ -9079,7 +9081,7 @@ func_expr: func_name '(' ')' over_clause n->location = @1; $$ = (Node *)n; } - | func_name '(' expr_list ',' VARIADIC a_expr ')' over_clause + | func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' over_clause { FuncCall *n = makeNode(FuncCall); n->funcname = $1; @@ -9091,7 +9093,7 @@ func_expr: func_name '(' ')' over_clause n->location = @1; $$ = (Node *)n; } - | func_name '(' ALL expr_list ')' over_clause + | func_name '(' ALL func_arg_list ')' over_clause { FuncCall *n = makeNode(FuncCall); n->funcname = $1; @@ -9107,7 +9109,7 @@ func_expr: func_name '(' ')' over_clause n->location = @1; $$ = (Node *)n; } - | func_name '(' DISTINCT expr_list ')' over_clause + | func_name '(' DISTINCT func_arg_list ')' over_clause { FuncCall *n = makeNode(FuncCall); n->funcname = $1; @@ -9830,6 +9832,32 @@ expr_list: a_expr } ; +/* function arguments can have names */ +func_arg_list: func_arg_expr + { + $$ = list_make1($1); + } + | func_arg_list ',' func_arg_expr + { + $$ = lappend($1, $3); + } + ; + +func_arg_expr: a_expr + { + $$ = $1; + } + | a_expr AS param_name + { + NamedArgExpr *na = makeNode(NamedArgExpr); + na->arg = (Expr *) $1; + na->name = $3; + na->argnumber = -1; /* until determined */ + na->location = @3; + $$ = (Node *) na; + } + ; + type_list: Typename { $$ = list_make1($1); } | type_list ',' Typename { $$ = lappend($1, $3); } ; @@ -10296,10 +10324,27 @@ AexprConst: Iconst t->location = @1; $$ = makeStringConstCast($2, @2, t); } - | func_name '(' expr_list ')' Sconst + | func_name '(' func_arg_list ')' Sconst { /* generic syntax with a type modifier */ TypeName *t = makeTypeNameFromNameList($1); + ListCell *lc; + + /* + * We must use func_arg_list in the production to avoid + * reduce/reduce conflicts, but we don't actually wish + * to allow NamedArgExpr in this context. + */ + foreach(lc, $3) + { + NamedArgExpr *arg = (NamedArgExpr *) lfirst(lc); + + if (IsA(arg, NamedArgExpr)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("type modifier cannot have AS name"), + parser_errposition(arg->location))); + } t->typmods = $3; t->location = @1; $$ = makeStringConstCast($5, @5, t); diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 39d66dcc12f..3fccba1ab5a 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.243 2009/09/09 03:32:52 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.244 2009/10/08 02:39:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -228,6 +228,15 @@ transformExpr(ParseState *pstate, Node *expr) result = transformFuncCall(pstate, (FuncCall *) expr); break; + case T_NamedArgExpr: + { + NamedArgExpr *na = (NamedArgExpr *) expr; + + na->arg = (Expr *) transformExpr(pstate, (Node *) na->arg); + result = expr; + break; + } + case T_SubLink: result = transformSubLink(pstate, (SubLink *) expr); break; diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index fd0706e9608..e752dd8d1ec 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.216 2009/06/11 14:49:00 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.217 2009/10/08 02:39:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -70,6 +70,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, int nargsplusdefs; Oid actual_arg_types[FUNC_MAX_ARGS]; Oid *declared_arg_types; + List *argnames; List *argdefaults; Node *retval; bool retset; @@ -117,6 +118,46 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, actual_arg_types[nargs++] = argtype; } + /* + * Check for named arguments; if there are any, build a list of names. + * + * We allow mixed notation (some named and some not), but only with all + * the named parameters after all the unnamed ones. So the name list + * corresponds to the last N actual parameters and we don't need any + * extra bookkeeping to match things up. + */ + argnames = NIL; + foreach(l, fargs) + { + Node *arg = lfirst(l); + + if (IsA(arg, NamedArgExpr)) + { + NamedArgExpr *na = (NamedArgExpr *) arg; + ListCell *lc; + + /* Reject duplicate arg names */ + foreach(lc, argnames) + { + if (strcmp(na->name, (char *) lfirst(lc)) == 0) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("argument name \"%s\" used more than once", + na->name), + parser_errposition(pstate, na->location))); + } + argnames = lappend(argnames, na->name); + } + else + { + if (argnames != NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("positional argument cannot follow named argument"), + parser_errposition(pstate, exprLocation(arg)))); + } + } + if (fargs) { first_arg = linitial(fargs); @@ -127,10 +168,10 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, * Check for column projection: if function has one argument, and that * argument is of complex type, and function name is not qualified, then * the "function call" could be a projection. We also check that there - * wasn't any aggregate or variadic decoration. + * wasn't any aggregate or variadic decoration, nor an argument name. */ if (nargs == 1 && !agg_star && !agg_distinct && over == NULL && - !func_variadic && list_length(funcname) == 1) + !func_variadic && argnames == NIL && list_length(funcname) == 1) { Oid argtype = actual_arg_types[0]; @@ -156,12 +197,17 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, * disambiguation for polymorphic functions, handles inheritance, and * returns the funcid and type and set or singleton status of the * function's return value. It also returns the true argument types to - * the function. In the case of a variadic function call, the reported - * "true" types aren't really what is in pg_proc: the variadic argument is - * replaced by a suitable number of copies of its element type. We'll fix - * it up below. We may also have to deal with default arguments. + * the function. + * + * Note: for a named-notation or variadic function call, the reported + * "true" types aren't really what is in pg_proc: the types are reordered + * to match the given argument order of named arguments, and a variadic + * argument is replaced by a suitable number of copies of its element + * type. We'll fix up the variadic case below. We may also have to deal + * with default arguments. */ - fdresult = func_get_detail(funcname, fargs, nargs, actual_arg_types, + fdresult = func_get_detail(funcname, fargs, argnames, nargs, + actual_arg_types, !func_variadic, true, &funcid, &rettype, &retset, &nvargs, &declared_arg_types, &argdefaults); @@ -225,7 +271,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, ereport(ERROR, (errcode(ERRCODE_AMBIGUOUS_FUNCTION), errmsg("function %s is not unique", - func_signature_string(funcname, nargs, + func_signature_string(funcname, nargs, argnames, actual_arg_types)), errhint("Could not choose a best candidate function. " "You might need to add explicit type casts."), @@ -234,7 +280,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function %s does not exist", - func_signature_string(funcname, nargs, + func_signature_string(funcname, nargs, argnames, actual_arg_types)), errhint("No function matches the given name and argument types. " "You might need to add explicit type casts."), @@ -353,6 +399,18 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, errmsg("aggregates cannot return sets"), parser_errposition(pstate, location))); + /* + * Currently it's not possible to define an aggregate with named + * arguments, so this case should be impossible. Check anyway + * because the planner and executor wouldn't cope with NamedArgExprs + * in an Aggref node. + */ + if (argnames != NIL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("aggregates cannot use named arguments"), + parser_errposition(pstate, location))); + /* parse_agg.c does additional aggregate-specific processing */ transformAggregateCall(pstate, aggref); @@ -406,6 +464,17 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, errmsg("window functions cannot return sets"), parser_errposition(pstate, location))); + /* + * We might want to support this later, but for now reject it + * because the planner and executor wouldn't cope with NamedArgExprs + * in a WindowFunc node. + */ + if (argnames != NIL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("window functions cannot use named arguments"), + parser_errposition(pstate, location))); + /* parse_agg.c does additional window-func-specific processing */ transformWindowFuncCall(pstate, wfunc, over); @@ -801,14 +870,29 @@ func_select_candidate(int nargs, * 1) check for possible interpretation as a type coercion request * 2) apply the ambiguous-function resolution rules * - * Note: we rely primarily on nargs/argtypes as the argument description. + * Return values *funcid through *true_typeids receive info about the function. + * If argdefaults isn't NULL, *argdefaults receives a list of any default + * argument expressions that need to be added to the given arguments. + * + * When processing a named- or mixed-notation call (ie, fargnames isn't NIL), + * the returned true_typeids and argdefaults are ordered according to the + * call's argument ordering: first any positional arguments, then the named + * arguments, then defaulted arguments (if needed and allowed by + * expand_defaults). Some care is needed if this information is to be compared + * to the function's pg_proc entry, but in practice the caller can usually + * just work with the call's argument ordering. + * + * We rely primarily on fargnames/nargs/argtypes as the argument description. * The actual expression node list is passed in fargs so that we can check - * for type coercion of a constant. Some callers pass fargs == NIL - * indicating they don't want that check made. + * for type coercion of a constant. Some callers pass fargs == NIL indicating + * they don't need that check made. Note also that when fargnames isn't NIL, + * the fargs list must be passed if the caller wants actual argument position + * information to be returned into the NamedArgExpr nodes. */ FuncDetailCode func_get_detail(List *funcname, List *fargs, + List *fargnames, int nargs, Oid *argtypes, bool expand_variadic, @@ -833,7 +917,7 @@ func_get_detail(List *funcname, *argdefaults = NIL; /* Get list of possible candidates from namespace search */ - raw_candidates = FuncnameGetCandidates(funcname, nargs, + raw_candidates = FuncnameGetCandidates(funcname, nargs, fargnames, expand_variadic, expand_defaults); /* @@ -884,7 +968,7 @@ func_get_detail(List *funcname, * coerce_type can't handle, we'll cause infinite recursion between * this module and coerce_type! */ - if (nargs == 1 && fargs != NIL) + if (nargs == 1 && fargs != NIL && fargnames == NIL) { Oid targetType = FuncNameAsType(funcname); @@ -967,17 +1051,47 @@ func_get_detail(List *funcname, FuncDetailCode result; /* - * If expanding variadics or defaults, the "best candidate" might - * represent multiple equivalently good functions; treat this case as - * ambiguous. + * If processing named args or expanding variadics or defaults, the + * "best candidate" might represent multiple equivalently good + * functions; treat this case as ambiguous. */ if (!OidIsValid(best_candidate->oid)) return FUNCDETAIL_MULTIPLE; + /* + * We disallow VARIADIC with named arguments unless the last + * argument (the one with VARIADIC attached) actually matched the + * variadic parameter. This is mere pedantry, really, but some + * folks insisted. + */ + if (fargnames != NIL && !expand_variadic && nargs > 0 && + best_candidate->argnumbers[nargs - 1] != nargs - 1) + return FUNCDETAIL_NOTFOUND; + *funcid = best_candidate->oid; *nvargs = best_candidate->nvargs; *true_typeids = best_candidate->args; + /* + * If processing named args, return actual argument positions into + * NamedArgExpr nodes in the fargs list. This is a bit ugly but not + * worth the extra notation needed to do it differently. + */ + if (best_candidate->argnumbers != NULL) + { + int i = 0; + ListCell *lc; + + foreach(lc, fargs) + { + NamedArgExpr *na = (NamedArgExpr *) lfirst(lc); + + if (IsA(na, NamedArgExpr)) + na->argnumber = best_candidate->argnumbers[i]; + i++; + } + } + ftup = SearchSysCache(PROCOID, ObjectIdGetDatum(best_candidate->oid), 0, 0, 0); @@ -988,36 +1102,73 @@ func_get_detail(List *funcname, *rettype = pform->prorettype; *retset = pform->proretset; /* fetch default args if caller wants 'em */ - if (argdefaults) + if (argdefaults && best_candidate->ndargs > 0) { - if (best_candidate->ndargs > 0) + Datum proargdefaults; + bool isnull; + char *str; + List *defaults; + + /* shouldn't happen, FuncnameGetCandidates messed up */ + if (best_candidate->ndargs > pform->pronargdefaults) + elog(ERROR, "not enough default arguments"); + + proargdefaults = SysCacheGetAttr(PROCOID, ftup, + Anum_pg_proc_proargdefaults, + &isnull); + Assert(!isnull); + str = TextDatumGetCString(proargdefaults); + defaults = (List *) stringToNode(str); + Assert(IsA(defaults, List)); + pfree(str); + + /* Delete any unused defaults from the returned list */ + if (best_candidate->argnumbers != NULL) + { + /* + * This is a bit tricky in named notation, since the supplied + * arguments could replace any subset of the defaults. We + * work by making a bitmapset of the argnumbers of defaulted + * arguments, then scanning the defaults list and selecting + * the needed items. (This assumes that defaulted arguments + * should be supplied in their positional order.) + */ + Bitmapset *defargnumbers; + int *firstdefarg; + List *newdefaults; + ListCell *lc; + int i; + + defargnumbers = NULL; + firstdefarg = &best_candidate->argnumbers[best_candidate->nargs - best_candidate->ndargs]; + for (i = 0; i < best_candidate->ndargs; i++) + defargnumbers = bms_add_member(defargnumbers, + firstdefarg[i]); + newdefaults = NIL; + i = pform->pronargs - pform->pronargdefaults; + foreach(lc, defaults) + { + if (bms_is_member(i, defargnumbers)) + newdefaults = lappend(newdefaults, lfirst(lc)); + i++; + } + Assert(list_length(newdefaults) == best_candidate->ndargs); + bms_free(defargnumbers); + *argdefaults = newdefaults; + } + else { - Datum proargdefaults; - bool isnull; - char *str; - List *defaults; + /* + * Defaults for positional notation are lots easier; + * just remove any unwanted ones from the front. + */ int ndelete; - /* shouldn't happen, FuncnameGetCandidates messed up */ - if (best_candidate->ndargs > pform->pronargdefaults) - elog(ERROR, "not enough default arguments"); - - proargdefaults = SysCacheGetAttr(PROCOID, ftup, - Anum_pg_proc_proargdefaults, - &isnull); - Assert(!isnull); - str = TextDatumGetCString(proargdefaults); - defaults = (List *) stringToNode(str); - Assert(IsA(defaults, List)); - pfree(str); - /* Delete any unused defaults from the returned list */ ndelete = list_length(defaults) - best_candidate->ndargs; while (ndelete-- > 0) defaults = list_delete_first(defaults); *argdefaults = defaults; } - else - *argdefaults = NIL; } if (pform->proisagg) result = FUNCDETAIL_AGGREGATE; @@ -1060,13 +1211,36 @@ make_fn_arguments(ParseState *pstate, /* types don't match? then force coercion using a function call... */ if (actual_arg_types[i] != declared_arg_types[i]) { - lfirst(current_fargs) = coerce_type(pstate, - lfirst(current_fargs), - actual_arg_types[i], - declared_arg_types[i], -1, - COERCION_IMPLICIT, - COERCE_IMPLICIT_CAST, - -1); + Node *node = (Node *) lfirst(current_fargs); + + /* + * If arg is a NamedArgExpr, coerce its input expr instead --- + * we want the NamedArgExpr to stay at the top level of the list. + */ + if (IsA(node, NamedArgExpr)) + { + NamedArgExpr *na = (NamedArgExpr *) node; + + node = coerce_type(pstate, + (Node *) na->arg, + actual_arg_types[i], + declared_arg_types[i], -1, + COERCION_IMPLICIT, + COERCE_IMPLICIT_CAST, + -1); + na->arg = (Expr *) node; + } + else + { + node = coerce_type(pstate, + node, + actual_arg_types[i], + declared_arg_types[i], -1, + COERCION_IMPLICIT, + COERCE_IMPLICIT_CAST, + -1); + lfirst(current_fargs) = node; + } } i++; } @@ -1223,25 +1397,39 @@ unknown_attribute(ParseState *pstate, Node *relref, char *attname, * Build a string representing a function name, including arg types. * The result is something like "foo(integer)". * + * If argnames isn't NIL, it is a list of C strings representing the actual + * arg names for the last N arguments. This must be considered part of the + * function signature too, when dealing with named-notation function calls. + * * This is typically used in the construction of function-not-found error * messages. */ const char * -funcname_signature_string(const char *funcname, - int nargs, const Oid *argtypes) +funcname_signature_string(const char *funcname, int nargs, + List *argnames, const Oid *argtypes) { StringInfoData argbuf; + int numposargs; + ListCell *lc; int i; initStringInfo(&argbuf); appendStringInfo(&argbuf, "%s(", funcname); + numposargs = nargs - list_length(argnames); + lc = list_head(argnames); + for (i = 0; i < nargs; i++) { if (i) appendStringInfoString(&argbuf, ", "); appendStringInfoString(&argbuf, format_type_be(argtypes[i])); + if (i >= numposargs) + { + appendStringInfo(&argbuf, " AS %s", (char *) lfirst(lc)); + lc = lnext(lc); + } } appendStringInfoChar(&argbuf, ')'); @@ -1254,10 +1442,11 @@ funcname_signature_string(const char *funcname, * As above, but function name is passed as a qualified name list. */ const char * -func_signature_string(List *funcname, int nargs, const Oid *argtypes) +func_signature_string(List *funcname, int nargs, + List *argnames, const Oid *argtypes) { return funcname_signature_string(NameListToString(funcname), - nargs, argtypes); + nargs, argnames, argtypes); } /* @@ -1276,7 +1465,7 @@ LookupFuncName(List *funcname, int nargs, const Oid *argtypes, bool noError) { FuncCandidateList clist; - clist = FuncnameGetCandidates(funcname, nargs, false, false); + clist = FuncnameGetCandidates(funcname, nargs, NIL, false, false); while (clist) { @@ -1289,7 +1478,8 @@ LookupFuncName(List *funcname, int nargs, const Oid *argtypes, bool noError) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function %s does not exist", - func_signature_string(funcname, nargs, argtypes)))); + func_signature_string(funcname, nargs, + NIL, argtypes)))); return InvalidOid; } @@ -1401,8 +1591,8 @@ LookupAggNameTypeNames(List *aggname, List *argtypes, bool noError) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("aggregate %s does not exist", - func_signature_string(aggname, - argcount, argoids)))); + func_signature_string(aggname, argcount, + NIL, argoids)))); } /* Make sure it's an aggregate */ @@ -1422,8 +1612,8 @@ LookupAggNameTypeNames(List *aggname, List *argtypes, bool noError) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("function %s is not an aggregate", - func_signature_string(aggname, - argcount, argoids)))); + func_signature_string(aggname, argcount, + NIL, argoids)))); } ReleaseSysCache(ftup); diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c index fd90a290220..dd2a53f5a5f 100644 --- a/src/backend/utils/adt/regproc.c +++ b/src/backend/utils/adt/regproc.c @@ -13,7 +13,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/regproc.c,v 1.110 2009/01/01 17:23:49 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/regproc.c,v 1.111 2009/10/08 02:39:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -131,7 +131,7 @@ regprocin(PG_FUNCTION_ARGS) * pg_proc entries in the current search path. */ names = stringToQualifiedNameList(pro_name_or_oid); - clist = FuncnameGetCandidates(names, -1, false, false); + clist = FuncnameGetCandidates(names, -1, NIL, false, false); if (clist == NULL) ereport(ERROR, @@ -190,7 +190,7 @@ regprocout(PG_FUNCTION_ARGS) * qualify it. */ clist = FuncnameGetCandidates(list_make1(makeString(proname)), - -1, false, false); + -1, NIL, false, false); if (clist != NULL && clist->next == NULL && clist->oid == proid) nspname = NULL; @@ -277,7 +277,7 @@ regprocedurein(PG_FUNCTION_ARGS) */ parseNameAndArgTypes(pro_name_or_oid, false, &names, &nargs, argtypes); - clist = FuncnameGetCandidates(names, nargs, false, false); + clist = FuncnameGetCandidates(names, nargs, NIL, false, false); for (; clist; clist = clist->next) { diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 38057a0bfdc..4c04bafd7c6 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.306 2009/08/01 19:59:41 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.307 2009/10/08 02:39:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -218,8 +218,8 @@ static Node *processIndirection(Node *node, deparse_context *context, bool printit); static void printSubscripts(ArrayRef *aref, deparse_context *context); static char *generate_relation_name(Oid relid, List *namespaces); -static char *generate_function_name(Oid funcid, int nargs, Oid *argtypes, - bool *is_variadic); +static char *generate_function_name(Oid funcid, int nargs, List *argnames, + Oid *argtypes, bool *is_variadic); static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2); static text *string_to_text(char *str); static char *flatten_reloptions(Oid relid); @@ -558,7 +558,8 @@ pg_get_triggerdef(PG_FUNCTION_ARGS) appendStringInfo(&buf, "FOR EACH STATEMENT "); appendStringInfo(&buf, "EXECUTE PROCEDURE %s(", - generate_function_name(trigrec->tgfoid, 0, NULL, NULL)); + generate_function_name(trigrec->tgfoid, 0, + NIL, NULL, NULL)); if (trigrec->tgnargs > 0) { @@ -4324,6 +4325,15 @@ get_rule_expr(Node *node, deparse_context *context, get_func_expr((FuncExpr *) node, context, showimplicit); break; + case T_NamedArgExpr: + { + NamedArgExpr *na = (NamedArgExpr *) node; + + get_rule_expr((Node *) na->arg, context, showimplicit); + appendStringInfo(buf, " AS %s", quote_identifier(na->name)); + } + break; + case T_OpExpr: get_oper_expr((OpExpr *) node, context); break; @@ -5187,6 +5197,7 @@ get_func_expr(FuncExpr *expr, deparse_context *context, Oid funcoid = expr->funcid; Oid argtypes[FUNC_MAX_ARGS]; int nargs; + List *argnames; bool is_variadic; ListCell *l; @@ -5231,14 +5242,20 @@ get_func_expr(FuncExpr *expr, deparse_context *context, (errcode(ERRCODE_TOO_MANY_ARGUMENTS), errmsg("too many arguments"))); nargs = 0; + argnames = NIL; foreach(l, expr->args) { - argtypes[nargs] = exprType((Node *) lfirst(l)); + Node *arg = (Node *) lfirst(l); + + if (IsA(arg, NamedArgExpr)) + argnames = lappend(argnames, ((NamedArgExpr *) arg)->name); + argtypes[nargs] = exprType(arg); nargs++; } appendStringInfo(buf, "%s(", - generate_function_name(funcoid, nargs, argtypes, + generate_function_name(funcoid, nargs, + argnames, argtypes, &is_variadic)); nargs = 0; foreach(l, expr->args) @@ -5270,13 +5287,16 @@ get_agg_expr(Aggref *aggref, deparse_context *context) nargs = 0; foreach(l, aggref->args) { - argtypes[nargs] = exprType((Node *) lfirst(l)); + Node *arg = (Node *) lfirst(l); + + Assert(!IsA(arg, NamedArgExpr)); + argtypes[nargs] = exprType(arg); nargs++; } appendStringInfo(buf, "%s(%s", - generate_function_name(aggref->aggfnoid, - nargs, argtypes, NULL), + generate_function_name(aggref->aggfnoid, nargs, + NIL, argtypes, NULL), aggref->aggdistinct ? "DISTINCT " : ""); /* aggstar can be set only in zero-argument aggregates */ if (aggref->aggstar) @@ -5304,13 +5324,16 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) nargs = 0; foreach(l, wfunc->args) { - argtypes[nargs] = exprType((Node *) lfirst(l)); + Node *arg = (Node *) lfirst(l); + + Assert(!IsA(arg, NamedArgExpr)); + argtypes[nargs] = exprType(arg); nargs++; } - appendStringInfo(buf, "%s(%s", - generate_function_name(wfunc->winfnoid, - nargs, argtypes, NULL), ""); + appendStringInfo(buf, "%s(", + generate_function_name(wfunc->winfnoid, nargs, + NIL, argtypes, NULL)); /* winstar can be set only in zero-argument aggregates */ if (wfunc->winstar) appendStringInfoChar(buf, '*'); @@ -6338,15 +6361,15 @@ generate_relation_name(Oid relid, List *namespaces) /* * generate_function_name * Compute the name to display for a function specified by OID, - * given that it is being called with the specified actual arg types. - * (Arg types matter because of ambiguous-function resolution rules.) + * given that it is being called with the specified actual arg names and + * types. (Those matter because of ambiguous-function resolution rules.) * * The result includes all necessary quoting and schema-prefixing. We can * also pass back an indication of whether the function is variadic. */ static char * -generate_function_name(Oid funcid, int nargs, Oid *argtypes, - bool *is_variadic) +generate_function_name(Oid funcid, int nargs, List *argnames, + Oid *argtypes, bool *is_variadic) { HeapTuple proctup; Form_pg_proc procform; @@ -6371,10 +6394,12 @@ generate_function_name(Oid funcid, int nargs, Oid *argtypes, /* * The idea here is to schema-qualify only if the parser would fail to * resolve the correct function given the unqualified func name with the - * specified argtypes. + * specified argtypes. If the function is variadic, we should presume + * that VARIADIC will be included in the call. */ p_result = func_get_detail(list_make1(makeString(proname)), - NIL, nargs, argtypes, false, true, + NIL, argnames, nargs, argtypes, + !OidIsValid(procform->provariadic), true, &p_funcid, &p_rettype, &p_retset, &p_nvargs, &p_true_typeids, NULL); if ((p_result == FUNCDETAIL_NORMAL || diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index 7ecd7812206..313b777d04b 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -7,7 +7,7 @@ * Copyright (c) 2002-2009, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.45 2009/06/11 14:49:05 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.46 2009/10/08 02:39:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -767,6 +767,92 @@ get_func_arg_info(HeapTuple procTup, /* + * get_func_input_arg_names + * + * Extract the names of input arguments only, given a function's + * proargnames and proargmodes entries in Datum form. + * + * Returns the number of input arguments, which is the length of the + * palloc'd array returned to *arg_names. Entries for unnamed args + * are set to NULL. You don't get anything if proargnames is NULL. + */ +int +get_func_input_arg_names(Datum proargnames, Datum proargmodes, + char ***arg_names) +{ + ArrayType *arr; + int numargs; + Datum *argnames; + char *argmodes; + char **inargnames; + int numinargs; + int i; + + /* Do nothing if null proargnames */ + if (proargnames == PointerGetDatum(NULL)) + { + *arg_names = NULL; + return 0; + } + + /* + * We expect the arrays to be 1-D arrays of the right types; verify that. + * For proargmodes, we don't need to use deconstruct_array() + * since the array data is just going to look like a C array of values. + */ + arr = DatumGetArrayTypeP(proargnames); /* ensure not toasted */ + if (ARR_NDIM(arr) != 1 || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != TEXTOID) + elog(ERROR, "proargnames is not a 1-D text array"); + deconstruct_array(arr, TEXTOID, -1, false, 'i', + &argnames, NULL, &numargs); + if (proargmodes != PointerGetDatum(NULL)) + { + arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */ + if (ARR_NDIM(arr) != 1 || + ARR_DIMS(arr)[0] != numargs || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != CHAROID) + elog(ERROR, "proargmodes is not a 1-D char array"); + argmodes = (char *) ARR_DATA_PTR(arr); + } + else + argmodes = NULL; + + /* zero elements probably shouldn't happen, but handle it gracefully */ + if (numargs <= 0) + { + *arg_names = NULL; + return 0; + } + + /* extract input-argument names */ + inargnames = (char **) palloc(numargs * sizeof(char *)); + numinargs = 0; + for (i = 0; i < numargs; i++) + { + if (argmodes == NULL || + argmodes[i] == PROARGMODE_IN || + argmodes[i] == PROARGMODE_INOUT || + argmodes[i] == PROARGMODE_VARIADIC) + { + char *pname = TextDatumGetCString(argnames[i]); + + if (pname[0] != '\0') + inargnames[numinargs] = pname; + else + inargnames[numinargs] = NULL; + numinargs++; + } + } + + *arg_names = inargnames; + return numinargs; +} + + +/* * get_func_result_name * * If the function has exactly one output parameter, and that parameter diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 20eac6aa1c7..8959997ea96 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.542 2009/10/07 22:14:24 alvherre Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.543 2009/10/08 02:39:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200910071 +#define CATALOG_VERSION_NO 200910072 #endif diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h index ed9218c03a4..2c2b88951a3 100644 --- a/src/include/catalog/namespace.h +++ b/src/include/catalog/namespace.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/namespace.h,v 1.59 2009/06/11 14:49:09 momjian Exp $ + * $PostgreSQL: pgsql/src/include/catalog/namespace.h,v 1.60 2009/10/08 02:39:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -32,6 +32,7 @@ typedef struct _FuncCandidateList int nargs; /* number of arg types returned */ int nvargs; /* number of args to become variadic array */ int ndargs; /* number of defaulted args */ + int *argnumbers; /* args' positional indexes, if named call */ Oid args[1]; /* arg types --- VARIABLE LENGTH ARRAY */ } *FuncCandidateList; /* VARIABLE LENGTH STRUCT */ @@ -54,7 +55,8 @@ extern bool RelationIsVisible(Oid relid); extern Oid TypenameGetTypid(const char *typname); extern bool TypeIsVisible(Oid typid); -extern FuncCandidateList FuncnameGetCandidates(List *names, int nargs, +extern FuncCandidateList FuncnameGetCandidates(List *names, + int nargs, List *argnames, bool expand_variadic, bool expand_defaults); extern bool FunctionIsVisible(Oid funcid); diff --git a/src/include/funcapi.h b/src/include/funcapi.h index 1373e4ad245..b4fe22c492b 100644 --- a/src/include/funcapi.h +++ b/src/include/funcapi.h @@ -9,7 +9,7 @@ * * Copyright (c) 2002-2009, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/include/funcapi.h,v 1.29 2009/06/11 14:49:08 momjian Exp $ + * $PostgreSQL: pgsql/src/include/funcapi.h,v 1.30 2009/10/08 02:39:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -173,6 +173,9 @@ extern int get_func_arg_info(HeapTuple procTup, Oid **p_argtypes, char ***p_argnames, char **p_argmodes); +extern int get_func_input_arg_names(Datum proargnames, Datum proargmodes, + char ***arg_names); + extern char *get_func_result_name(Oid functionId); extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes, diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 5fd046e95b8..2a4468799f9 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.227 2009/10/05 19:24:48 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.228 2009/10/08 02:39:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -123,6 +123,7 @@ typedef enum NodeTag T_WindowFunc, T_ArrayRef, T_FuncExpr, + T_NamedArgExpr, T_OpExpr, T_DistinctExpr, T_ScalarArrayOpExpr, diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 5f5d4125c65..0320e231553 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -10,7 +10,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.150 2009/07/16 06:33:45 petere Exp $ + * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.151 2009/10/08 02:39:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -314,6 +314,29 @@ typedef struct FuncExpr } FuncExpr; /* + * NamedArgExpr - a named argument of a function + * + * This node type can only appear in the args list of a FuncCall or FuncExpr + * node. We support pure positional call notation (no named arguments), + * named notation (all arguments are named), and mixed notation (unnamed + * arguments followed by named ones). + * + * Parse analysis sets argnumber to the positional index of the argument, + * but doesn't rearrange the argument list. + * + * The planner will convert argument lists to pure positional notation + * during expression preprocessing, so execution never sees a NamedArgExpr. + */ +typedef struct NamedArgExpr +{ + Expr xpr; + Expr *arg; /* the argument expression */ + char *name; /* the name */ + int argnumber; /* argument's number in positional notation */ + int location; /* argument name location, or -1 if unknown */ +} NamedArgExpr; + +/* * OpExpr - expression node for an operator invocation * * Semantically, this is essentially the same as a function call. diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h index 7905f96e86c..0a38f740b1c 100644 --- a/src/include/parser/parse_func.h +++ b/src/include/parser/parse_func.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/parser/parse_func.h,v 1.65 2009/05/12 00:56:05 tgl Exp $ + * $PostgreSQL: pgsql/src/include/parser/parse_func.h,v 1.66 2009/10/08 02:39:25 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -47,7 +47,8 @@ extern Node *ParseFuncOrColumn(ParseState *pstate, bool agg_star, bool agg_distinct, bool func_variadic, WindowDef *over, bool is_column, int location); -extern FuncDetailCode func_get_detail(List *funcname, List *fargs, +extern FuncDetailCode func_get_detail(List *funcname, + List *fargs, List *fargnames, int nargs, Oid *argtypes, bool expand_variadic, bool expand_defaults, Oid *funcid, Oid *rettype, @@ -68,10 +69,10 @@ extern void make_fn_arguments(ParseState *pstate, Oid *actual_arg_types, Oid *declared_arg_types); -extern const char *funcname_signature_string(const char *funcname, - int nargs, const Oid *argtypes); -extern const char *func_signature_string(List *funcname, - int nargs, const Oid *argtypes); +extern const char *funcname_signature_string(const char *funcname, int nargs, + List *argnames, const Oid *argtypes); +extern const char *func_signature_string(List *funcname, int nargs, + List *argnames, const Oid *argtypes); extern Oid LookupFuncName(List *funcname, int nargs, const Oid *argtypes, bool noError); diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out index 77f693c2b14..36b31f09f43 100644 --- a/src/test/regress/expected/polymorphism.out +++ b/src/test/regress/expected/polymorphism.out @@ -1,5 +1,6 @@ -- Currently this tests polymorphic aggregates and indirectly does some -- testing of polymorphic SQL functions. It ought to be extended. +-- Tests for other features related to function-calling have snuck in, too. -- Legend: ----------- -- A = type is ANY @@ -19,7 +20,7 @@ -- !> = not allowed -- E = exists -- NE = not-exists --- +-- -- Possible states: -- ---------------- -- B = (A || P || N) @@ -60,7 +61,7 @@ CREATE FUNCTION ffp(anyarray) RETURNS anyarray AS CREATE FUNCTION ffnp(int[]) returns int[] as 'select $1' LANGUAGE SQL; -- Try to cover all the possible states: --- +-- -- Note: in Cases 1 & 2, we are trying to return P. Therefore, if the transfn -- is stfnp, tfnp, or tf2p, we must use ffp as finalfn, because stfnp, tfnp, -- and tf2p do not return P. Conversely, in Cases 3 & 4, we are trying to @@ -837,7 +838,7 @@ select dfunc(); -- verify it lists properly \df dfunc - List of functions + List of functions Schema | Name | Result data type | Argument data types | Type --------+-------+------------------+-----------------------------------------------------------+-------- public | dfunc | integer | a integer DEFAULT 1, OUT sum integer, b integer DEFAULT 2 | normal @@ -1005,7 +1006,7 @@ $$ select array_upper($1, 1) $$ language sql; ERROR: cannot remove parameter defaults from existing function HINT: Use DROP FUNCTION first. \df dfunc - List of functions + List of functions Schema | Name | Result data type | Argument data types | Type --------+-------+------------------+-------------------------------------------------+-------- public | dfunc | integer | VARIADIC a integer[] DEFAULT ARRAY[]::integer[] | normal @@ -1038,3 +1039,328 @@ select dfunc('Hi'); drop function dfunc(int, int, int); drop function dfunc(int, int); drop function dfunc(text); +-- +-- Tests for named- and mixed-notation function calling +-- +create function dfunc(a int, b int, c int = 0, d int = 0) + returns table (a int, b int, c int, d int) as $$ + select $1, $2, $3, $4; +$$ language sql; +select (dfunc(10,20,30)).*; + a | b | c | d +----+----+----+--- + 10 | 20 | 30 | 0 +(1 row) + +select (dfunc(10 as a, 20 as b, 30 as c)).*; + a | b | c | d +----+----+----+--- + 10 | 20 | 30 | 0 +(1 row) + +select * from dfunc(10 as a, 20 as b); + a | b | c | d +----+----+---+--- + 10 | 20 | 0 | 0 +(1 row) + +select * from dfunc(10 as b, 20 as a); + a | b | c | d +----+----+---+--- + 20 | 10 | 0 | 0 +(1 row) + +select * from dfunc(0); -- fail +ERROR: function dfunc(integer) does not exist +LINE 1: select * from dfunc(0); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +select * from dfunc(1,2); + a | b | c | d +---+---+---+--- + 1 | 2 | 0 | 0 +(1 row) + +select * from dfunc(1,2,3 as c); + a | b | c | d +---+---+---+--- + 1 | 2 | 3 | 0 +(1 row) + +select * from dfunc(1,2,3 as d); + a | b | c | d +---+---+---+--- + 1 | 2 | 0 | 3 +(1 row) + +select * from dfunc(10 as x, 20 as b, 30 as x); -- fail, duplicate name +ERROR: argument name "x" used more than once +LINE 1: select * from dfunc(10 as x, 20 as b, 30 as x); + ^ +select * from dfunc(10, 20 as b, 30); -- fail, named args must be last +ERROR: positional argument cannot follow named argument +LINE 1: select * from dfunc(10, 20 as b, 30); + ^ +select * from dfunc(10 as x, 20 as b, 30 as c); -- fail, unknown param +ERROR: function dfunc(integer AS x, integer AS b, integer AS c) does not exist +LINE 1: select * from dfunc(10 as x, 20 as b, 30 as c); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +select * from dfunc(10, 10, 20 as a); -- fail, a overlaps positional parameter +ERROR: function dfunc(integer, integer, integer AS a) does not exist +LINE 1: select * from dfunc(10, 10, 20 as a); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +select * from dfunc(1,2 as c,3 as d); -- fail, no value for b +ERROR: function dfunc(integer, integer AS c, integer AS d) does not exist +LINE 1: select * from dfunc(1,2 as c,3 as d); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +drop function dfunc(int, int, int, int); +-- test with different parameter types +create function dfunc(a varchar, b numeric, c date = current_date) + returns table (a varchar, b numeric, c date) as $$ + select $1, $2, $3; +$$ language sql; +select (dfunc('Hello World', 20, '2009-07-25'::date)).*; + a | b | c +-------------+----+------------ + Hello World | 20 | 07-25-2009 +(1 row) + +select * from dfunc('Hello World', 20, '2009-07-25'::date); + a | b | c +-------------+----+------------ + Hello World | 20 | 07-25-2009 +(1 row) + +select * from dfunc('2009-07-25'::date as c, 'Hello World' as a, 20 as b); + a | b | c +-------------+----+------------ + Hello World | 20 | 07-25-2009 +(1 row) + +select * from dfunc('Hello World', 20 as b, '2009-07-25'::date as c); + a | b | c +-------------+----+------------ + Hello World | 20 | 07-25-2009 +(1 row) + +select * from dfunc('Hello World', '2009-07-25'::date as c, 20 as b); + a | b | c +-------------+----+------------ + Hello World | 20 | 07-25-2009 +(1 row) + +select * from dfunc('Hello World', 20 as c, '2009-07-25'::date as b); -- fail +ERROR: function dfunc(unknown, integer AS c, date AS b) does not exist +LINE 1: select * from dfunc('Hello World', 20 as c, '2009-07-25'::da... + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +drop function dfunc(varchar, numeric, date); +-- test out parameters with named params +create function dfunc(a varchar = 'def a', out _a varchar, c numeric = NULL, out _c numeric) +returns record as $$ + select $1, $2; +$$ language sql; +select (dfunc()).*; + _a | _c +-------+---- + def a | +(1 row) + +select * from dfunc(); + _a | _c +-------+---- + def a | +(1 row) + +select * from dfunc('Hello', 100); + _a | _c +-------+----- + Hello | 100 +(1 row) + +select * from dfunc('Hello' as a, 100 as c); + _a | _c +-------+----- + Hello | 100 +(1 row) + +select * from dfunc(100 as c, 'Hello' as a); + _a | _c +-------+----- + Hello | 100 +(1 row) + +select * from dfunc('Hello'); + _a | _c +-------+---- + Hello | +(1 row) + +select * from dfunc('Hello', 100 as c); + _a | _c +-------+----- + Hello | 100 +(1 row) + +select * from dfunc(100 as c); + _a | _c +-------+----- + def a | 100 +(1 row) + +-- fail, can no longer change an input parameter's name +create or replace function dfunc(a varchar = 'def a', out _a varchar, x numeric = NULL, out _c numeric) +returns record as $$ + select $1, $2; +$$ language sql; +ERROR: cannot change name of input parameter "c" +HINT: Use DROP FUNCTION first. +create or replace function dfunc(a varchar = 'def a', out _a varchar, numeric = NULL, out _c numeric) +returns record as $$ + select $1, $2; +$$ language sql; +ERROR: cannot change name of input parameter "c" +HINT: Use DROP FUNCTION first. +drop function dfunc(varchar, numeric); +--fail, named parameters are not unique +create function testfoo(a int, a int) returns int as $$ select 1;$$ language sql; +ERROR: parameter name "a" used more than once +create function testfoo(int, out a int, out a int) returns int as $$ select 1;$$ language sql; +ERROR: parameter name "a" used more than once +create function testfoo(out a int, inout a int) returns int as $$ select 1;$$ language sql; +ERROR: parameter name "a" used more than once +create function testfoo(a int, inout a int) returns int as $$ select 1;$$ language sql; +ERROR: parameter name "a" used more than once +-- valid +create function testfoo(a int, out a int) returns int as $$ select $1;$$ language sql; +select testfoo(37); + testfoo +--------- + 37 +(1 row) + +drop function testfoo(int); +create function testfoo(a int) returns table(a int) as $$ select $1;$$ language sql; +select * from testfoo(37); + a +---- + 37 +(1 row) + +drop function testfoo(int); +-- test polymorphic params and defaults +create function dfunc(a anyelement, b anyelement = null, flag bool = true) +returns anyelement as $$ + select case when $3 then $1 else $2 end; +$$ language sql; +select dfunc(1,2); + dfunc +------- + 1 +(1 row) + +select dfunc('a'::text, 'b'); -- positional notation with default + dfunc +------- + a +(1 row) + +select dfunc(1 as a, 2 as b); + dfunc +------- + 1 +(1 row) + +select dfunc('a'::text as a, 'b' as b); + dfunc +------- + a +(1 row) + +select dfunc('a'::text as a, 'b' as b, false as flag); -- named notation + dfunc +------- + b +(1 row) + +select dfunc('b'::text as b, 'a' as a); -- named notation with default + dfunc +------- + a +(1 row) + +select dfunc('a'::text as a, true as flag); -- named notation with default + dfunc +------- + a +(1 row) + +select dfunc('a'::text as a, false as flag); -- named notation with default + dfunc +------- + +(1 row) + +select dfunc('b'::text as b, 'a' as a, true as flag); -- named notation + dfunc +------- + a +(1 row) + +select dfunc('a'::text, 'b', false); -- full positional notation + dfunc +------- + b +(1 row) + +select dfunc('a'::text, 'b', false as flag); -- mixed notation + dfunc +------- + b +(1 row) + +select dfunc('a'::text, 'b', true); -- full positional notation + dfunc +------- + a +(1 row) + +select dfunc('a'::text, 'b', true as flag); -- mixed notation + dfunc +------- + a +(1 row) + +-- check reverse-listing of named-arg calls +CREATE VIEW dfview AS + SELECT q1, q2, + dfunc(q1,q2, q1>q2 as flag) as c3, + dfunc(q1, q1<q2 as flag, q2 AS b) as c4 + FROM int8_tbl; +select * from dfview; + q1 | q2 | c3 | c4 +------------------+-------------------+------------------+------------------- + 123 | 456 | 456 | 123 + 123 | 4567890123456789 | 4567890123456789 | 123 + 4567890123456789 | 123 | 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 | 4567890123456789 | -4567890123456789 +(5 rows) + +\d dfview + View "public.dfview" + Column | Type | Modifiers +--------+--------+----------- + q1 | bigint | + q2 | bigint | + c3 | bigint | + c4 | bigint | +View definition: + SELECT int8_tbl.q1, int8_tbl.q2, dfunc(int8_tbl.q1, int8_tbl.q2, int8_tbl.q1 > int8_tbl.q2 AS flag) AS c3, dfunc(int8_tbl.q1, int8_tbl.q1 < int8_tbl.q2 AS flag, int8_tbl.q2 AS b) AS c4 + FROM int8_tbl; + +drop view dfview; +drop function dfunc(anyelement, anyelement, bool); diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out index 486dd3f3fe0..843bc53e4e7 100644 --- a/src/test/regress/expected/rangefuncs.out +++ b/src/test/regress/expected/rangefuncs.out @@ -159,7 +159,7 @@ SELECT * FROM getfoo(1) AS t1(fooid int, foosubid int, fooname text); 1 | 1 | Joe (1 row) -CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) AS +CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) AS (fooid int, foosubid int, fooname text); SELECT * FROM vw_getfoo; fooid | foosubid | fooname @@ -515,7 +515,13 @@ SELECT * FROM dup('xyz'::text); xyz | {xyz,xyz} (1 row) --- equivalent specification +-- fails, as we are attempting to rename first argument +CREATE OR REPLACE FUNCTION dup (inout f2 anyelement, out f3 anyarray) +AS 'select $1, array[$1,$1]' LANGUAGE sql; +ERROR: cannot change name of input parameter "f1" +HINT: Use DROP FUNCTION first. +DROP FUNCTION dup(anyelement); +-- equivalent behavior, though different name exposed for input arg CREATE OR REPLACE FUNCTION dup (inout f2 anyelement, out f3 anyarray) AS 'select $1, array[$1,$1]' LANGUAGE sql; SELECT dup(22); diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql index c01871de007..2071ce63da7 100644 --- a/src/test/regress/sql/polymorphism.sql +++ b/src/test/regress/sql/polymorphism.sql @@ -1,5 +1,6 @@ -- Currently this tests polymorphic aggregates and indirectly does some -- testing of polymorphic SQL functions. It ought to be extended. +-- Tests for other features related to function-calling have snuck in, too. -- Legend: @@ -21,7 +22,7 @@ -- !> = not allowed -- E = exists -- NE = not-exists --- +-- -- Possible states: -- ---------------- -- B = (A || P || N) @@ -69,7 +70,7 @@ CREATE FUNCTION ffnp(int[]) returns int[] as 'select $1' LANGUAGE SQL; -- Try to cover all the possible states: --- +-- -- Note: in Cases 1 & 2, we are trying to return P. Therefore, if the transfn -- is stfnp, tfnp, or tf2p, we must use ffp as finalfn, because stfnp, tfnp, -- and tf2p do not return P. Conversely, in Cases 3 & 4, we are trying to @@ -624,3 +625,123 @@ select dfunc('Hi'); drop function dfunc(int, int, int); drop function dfunc(int, int); drop function dfunc(text); + +-- +-- Tests for named- and mixed-notation function calling +-- + +create function dfunc(a int, b int, c int = 0, d int = 0) + returns table (a int, b int, c int, d int) as $$ + select $1, $2, $3, $4; +$$ language sql; + +select (dfunc(10,20,30)).*; +select (dfunc(10 as a, 20 as b, 30 as c)).*; +select * from dfunc(10 as a, 20 as b); +select * from dfunc(10 as b, 20 as a); +select * from dfunc(0); -- fail +select * from dfunc(1,2); +select * from dfunc(1,2,3 as c); +select * from dfunc(1,2,3 as d); + +select * from dfunc(10 as x, 20 as b, 30 as x); -- fail, duplicate name +select * from dfunc(10, 20 as b, 30); -- fail, named args must be last +select * from dfunc(10 as x, 20 as b, 30 as c); -- fail, unknown param +select * from dfunc(10, 10, 20 as a); -- fail, a overlaps positional parameter +select * from dfunc(1,2 as c,3 as d); -- fail, no value for b + +drop function dfunc(int, int, int, int); + +-- test with different parameter types +create function dfunc(a varchar, b numeric, c date = current_date) + returns table (a varchar, b numeric, c date) as $$ + select $1, $2, $3; +$$ language sql; + +select (dfunc('Hello World', 20, '2009-07-25'::date)).*; +select * from dfunc('Hello World', 20, '2009-07-25'::date); +select * from dfunc('2009-07-25'::date as c, 'Hello World' as a, 20 as b); +select * from dfunc('Hello World', 20 as b, '2009-07-25'::date as c); +select * from dfunc('Hello World', '2009-07-25'::date as c, 20 as b); +select * from dfunc('Hello World', 20 as c, '2009-07-25'::date as b); -- fail + +drop function dfunc(varchar, numeric, date); + +-- test out parameters with named params +create function dfunc(a varchar = 'def a', out _a varchar, c numeric = NULL, out _c numeric) +returns record as $$ + select $1, $2; +$$ language sql; + +select (dfunc()).*; +select * from dfunc(); +select * from dfunc('Hello', 100); +select * from dfunc('Hello' as a, 100 as c); +select * from dfunc(100 as c, 'Hello' as a); +select * from dfunc('Hello'); +select * from dfunc('Hello', 100 as c); +select * from dfunc(100 as c); + +-- fail, can no longer change an input parameter's name +create or replace function dfunc(a varchar = 'def a', out _a varchar, x numeric = NULL, out _c numeric) +returns record as $$ + select $1, $2; +$$ language sql; + +create or replace function dfunc(a varchar = 'def a', out _a varchar, numeric = NULL, out _c numeric) +returns record as $$ + select $1, $2; +$$ language sql; + +drop function dfunc(varchar, numeric); + +--fail, named parameters are not unique +create function testfoo(a int, a int) returns int as $$ select 1;$$ language sql; +create function testfoo(int, out a int, out a int) returns int as $$ select 1;$$ language sql; +create function testfoo(out a int, inout a int) returns int as $$ select 1;$$ language sql; +create function testfoo(a int, inout a int) returns int as $$ select 1;$$ language sql; + +-- valid +create function testfoo(a int, out a int) returns int as $$ select $1;$$ language sql; +select testfoo(37); +drop function testfoo(int); +create function testfoo(a int) returns table(a int) as $$ select $1;$$ language sql; +select * from testfoo(37); +drop function testfoo(int); + +-- test polymorphic params and defaults +create function dfunc(a anyelement, b anyelement = null, flag bool = true) +returns anyelement as $$ + select case when $3 then $1 else $2 end; +$$ language sql; + +select dfunc(1,2); +select dfunc('a'::text, 'b'); -- positional notation with default + +select dfunc(1 as a, 2 as b); +select dfunc('a'::text as a, 'b' as b); +select dfunc('a'::text as a, 'b' as b, false as flag); -- named notation + +select dfunc('b'::text as b, 'a' as a); -- named notation with default +select dfunc('a'::text as a, true as flag); -- named notation with default +select dfunc('a'::text as a, false as flag); -- named notation with default +select dfunc('b'::text as b, 'a' as a, true as flag); -- named notation + +select dfunc('a'::text, 'b', false); -- full positional notation +select dfunc('a'::text, 'b', false as flag); -- mixed notation +select dfunc('a'::text, 'b', true); -- full positional notation +select dfunc('a'::text, 'b', true as flag); -- mixed notation + +-- check reverse-listing of named-arg calls +CREATE VIEW dfview AS + SELECT q1, q2, + dfunc(q1,q2, q1>q2 as flag) as c3, + dfunc(q1, q1<q2 as flag, q2 AS b) as c4 + FROM int8_tbl; + +select * from dfview; + +\d dfview + +drop view dfview; +drop function dfunc(anyelement, anyelement, bool); diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql index 3727a36aaff..172bbc73a9e 100644 --- a/src/test/regress/sql/rangefuncs.sql +++ b/src/test/regress/sql/rangefuncs.sql @@ -70,7 +70,7 @@ DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); CREATE FUNCTION getfoo(int) RETURNS RECORD AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; SELECT * FROM getfoo(1) AS t1(fooid int, foosubid int, fooname text); -CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) AS +CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) AS (fooid int, foosubid int, fooname text); SELECT * FROM vw_getfoo; @@ -251,7 +251,13 @@ SELECT dup('xyz'); -- fails SELECT dup('xyz'::text); SELECT * FROM dup('xyz'::text); --- equivalent specification +-- fails, as we are attempting to rename first argument +CREATE OR REPLACE FUNCTION dup (inout f2 anyelement, out f3 anyarray) +AS 'select $1, array[$1,$1]' LANGUAGE sql; + +DROP FUNCTION dup(anyelement); + +-- equivalent behavior, though different name exposed for input arg CREATE OR REPLACE FUNCTION dup (inout f2 anyelement, out f3 anyarray) AS 'select $1, array[$1,$1]' LANGUAGE sql; SELECT dup(22); |