diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml index 0843a17563..6d7eb84d8f 100644 --- a/doc/src/sgml/ref/create_function.sgml +++ b/doc/src/sgml/ref/create_function.sgml @@ -1,5 +1,5 @@ @@ -65,7 +65,7 @@ CREATE [ OR REPLACE ] FUNCTION Also, CREATE OR REPLACE FUNCTION will not let you change the return type of an existing function. To do that, you must drop and recreate the function. (When using OUT - parameters, that means you cannot change the names or types of any + parameters, that means you cannot change the types of any OUT parameters except by dropping the function.) @@ -121,8 +121,11 @@ CREATE [ OR REPLACE ] FUNCTION The name of an argument. Some languages (currently only PL/pgSQL) let you use the name in the function body. For other languages the - name of an input argument is just extra documentation. But the name - of an output argument is significant, since it defines the column + name of an input argument is just extra documentation, so far as + the function itself is concerned; but you can use input argument names + when calling a function to improve readability (see ). In any case, the name + of an output argument is significant, because it defines the column name in the result row type. (If you omit the name for an output argument, the system will choose a default column name.) @@ -570,6 +573,18 @@ CREATE FUNCTION foo(int, int default 42) ... to replace it (this includes being a member of the owning role). + + When replacing an existing function with CREATE OR REPLACE + FUNCTION, there are restrictions on changing parameter names. + You cannot change the name already assigned to any input parameter + (although you can add names to parameters that had none before). + If there is more than one output parameter, you cannot change the + names of the output parameters, because that would change the + column names of the anonymous composite type that describes the + function's result. These restrictions are made to ensure that + existing calls of the function do not stop working when it is replaced. + + diff --git a/doc/src/sgml/sources.sgml b/doc/src/sgml/sources.sgml index 0872eacee7..342f8e4ef7 100644 --- a/doc/src/sgml/sources.sgml +++ b/doc/src/sgml/sources.sgml @@ -1,4 +1,4 @@ - + PostgreSQL Coding Conventions @@ -125,7 +125,7 @@ ereport(ERROR, (errcode(ERRCODE_AMBIGUOUS_FUNCTION), errmsg("function %s is not unique", func_signature_string(funcname, nargs, - actual_arg_types)), + NIL, actual_arg_types)), errhint("Unable to choose a best candidate function. " "You might need to add explicit typecasts."))); diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml index 73db3235bd..20f7085a8d 100644 --- a/doc/src/sgml/syntax.sgml +++ b/doc/src/sgml/syntax.sgml @@ -1,4 +1,4 @@ - + SQL Syntax @@ -1505,6 +1505,11 @@ sqrt(2) The list of built-in functions is in . Other functions can be added by the user. + + + The arguments can optionally have names attached. + See for details. + @@ -2123,4 +2128,168 @@ SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END; + + Calling Functions + + + notation + functions + + + + PostgreSQL allows functions that have named + parameters to be called using either positional or + named notation. Named notation is especially + useful for functions that have a large number of parameters, since it + makes the associations between parameters and actual arguments more + explicit and reliable. + In positional notation, a function call is written with + its argument values in the same order as they are defined in the function + declaration. In named notation, the arguments are matched to the + function parameters by name and can be written in any order. + + + + In either notation, parameters that have default values given in the + function declaration need not be written in the call at all. But this + is particularly useful in named notation, since any combination of + parameters can be omitted; while in positional notation parameters can + only be omitted from right to left. + + + + PostgreSQL also supports + mixed notation, which combines positional and + named notation. In this case, positional parameters are written first + and named parameters appear after them. + + + + The following examples will illustrate the usage of all three + notations, using the following function definition: + +CREATE FUNCTION concat_lower_or_upper(a text, b text, uppercase boolean DEFAULT false) +RETURNS text +AS +$$ + SELECT CASE + WHEN $3 THEN UPPER($1 || ' ' || $2) + ELSE LOWER($1 || ' ' || $2) + END; +$$ +LANGUAGE SQL IMMUTABLE STRICT; + + Function concat_lower_or_upper has two mandatory + parameters, a and b. Additionally + there is one optional parameter uppercase which defaults + to false. The a and + b inputs will be concatenated, and forced to either + upper or lower case depending on the uppercase + parameter. The remaining details of this function + definition are not important here (see for + more information). + + + + Using positional notation + + + function + positional notation + + + + Positional notation is the traditional mechanism for passing arguments + to functions in PostgreSQL. An example is: + +SELECT concat_lower_or_upper('Hello', 'World', true); + concat_lower_or_upper +----------------------- + HELLO WORLD +(1 row) + + All arguments are specified in order. The result is upper case since + uppercase is specified as true. + Another example is: + +SELECT concat_lower_or_upper('Hello', 'World'); + concat_lower_or_upper +----------------------- + hello world +(1 row) + + Here, the uppercase parameter is omitted, so it + receives its default value of false, resulting in + lower case output. In positional notation, arguments can be omitted + from right to left so long as they have defaults. + + + + + Using named notation + + + function + named notation + + + + In named notation, each argument's name is specified using the + AS keyword. For example: + +SELECT concat_lower_or_upper('Hello' AS a, 'World' AS b); + concat_lower_or_upper +----------------------- + hello world +(1 row) + + Again, the argument uppercase was omitted + so it is set to false implicitly. One advantage of + using named notation is that the arguments may be specified in any + order, for example: + +SELECT concat_lower_or_upper('Hello' AS a, 'World' AS b, true AS uppercase); + concat_lower_or_upper +----------------------- + HELLO WORLD +(1 row) + +SELECT concat_lower_or_upper('Hello' AS a, true AS uppercase, 'World' AS b); + concat_lower_or_upper +----------------------- + HELLO WORLD +(1 row) + + + + + + Using mixed notation + + + function + mixed notation + + + + The mixed notation combines positional and named notation. However, as + already mentioned, named arguments cannot precede positional arguments. + For example: + +SELECT concat_lower_or_upper('Hello', 'World', true AS uppercase); + concat_lower_or_upper +----------------------- + HELLO WORLD +(1 row) + + In the above query, the arguments a and + b are specified positionally, while + uppercase is specified by name. In this example, + that adds little except documentation. With a more complex function + having numerous parameters that have default values, named or mixed + notation can save a great deal of writing and reduce chances for error. + + + + diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index 6d85d2d262..1c20f15226 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -1,4 +1,4 @@ - + User-Defined Functions @@ -517,6 +517,39 @@ SELECT getname(new_emp()); + + <acronym>SQL</> Functions with Parameter Names + + + function + named parameter + + + + It is possible to attach names to a function's parameters, for example + + +CREATE FUNCTION tf1 (acct_no integer, debit numeric) RETURNS numeric AS $$ + UPDATE bank + SET balance = balance - $2 + WHERE accountno = $1 + RETURNING balance; +$$ LANGUAGE SQL; + + + Here the first parameter has been given the name acct_no, + and the second parameter the name debit. + So far as the SQL function itself is concerned, these names are just + decoration; you must still refer to the parameters as $1, + $2, etc within the function body. (Some procedural + languages let you use the parameter names instead.) However, + attaching names to the parameters is useful for documentation purposes. + When a function has many parameters, it is also useful to use the names + while calling the function, as described in + . + + + <acronym>SQL</> Functions with Output Parameters @@ -571,7 +604,10 @@ LANGUAGE SQL; but not having to bother with the separate composite type definition - is often handy. + is often handy. Notice that the names attached to the output parameters + are not just decoration, but determine the column names of the anonymous + composite type. (If you omit a name for an output parameter, the + system will choose a name on its own.) @@ -621,7 +657,7 @@ DROP FUNCTION sum_n_product (int, int); must be declared as being of an array type. For example: -CREATE FUNCTION mleast(VARIADIC numeric[]) RETURNS numeric AS $$ +CREATE FUNCTION mleast(VARIADIC arr numeric[]) RETURNS numeric AS $$ SELECT min($1[i]) FROM generate_subscripts($1, 1) g(i); $$ LANGUAGE SQL; @@ -661,6 +697,25 @@ SELECT mleast(VARIADIC ARRAY[10, -1, 5, 4.4]); normally. VARIADIC can only be attached to the last actual argument of a function call. + + + The array element parameters generated from a variadic parameter are + treated as not having any names of their own. This means it is not + possible to call a variadic function using named arguments (), except when you specify + VARIADIC. For example, this will work: + + +SELECT mleast(VARIADIC ARRAY[10, -1, 5, 4.4] AS arr); + + + but not these: + + +SELECT mleast(10 AS arr); +SELECT mleast(ARRAY[10, -1, 5, 4.4] AS arr); + + @@ -677,7 +732,9 @@ SELECT mleast(VARIADIC ARRAY[10, -1, 5, 4.4]); called with insufficiently many actual arguments. Since arguments can only be omitted from the end of the actual argument list, all parameters after a parameter with a default value have to have - default values as well. + default values as well. (Although the use of named argument notation + could allow this restriction to be relaxed, it's still enforced so that + positional argument notation works sensibly.) @@ -712,7 +769,7 @@ SELECT foo(); -- fails since there is no default for the first argument ERROR: function foo() does not exist The = sign can also be used in place of the - key word DEFAULT, + key word DEFAULT. diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 12cb1720cd..2c327d45f1 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; @@ -885,6 +966,125 @@ FuncnameGetCandidates(List *names, int nargs, return resultList; } +/* + * 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 @@ -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 8c9f8bce7a..7fae4b66b4 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 db69c127ad..c7ee17a57b 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, @@ -393,6 +395,49 @@ ProcedureCreate(const char *procedureName, errhint("Use DROP FUNCTION first."))); } + /* + * 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 @@ -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 461f81005c..bafc285660 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 cf206b3f09..40097a80c7 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 5339e1783c..8696ea8573 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 5fa2ac6bc9..4f997a0dce 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 9319aa84c5..27c231701d 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 $ * *------------------------------------------------------------------------- */ @@ -1019,6 +1019,22 @@ _copyFuncExpr(FuncExpr *from) return newnode; } +/* + * _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 */ @@ -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 6a61112b99..5766f37c14 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 $ * *------------------------------------------------------------------------- */ @@ -241,6 +241,17 @@ _equalFuncExpr(FuncExpr *a, FuncExpr *b) return true; } +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) { @@ -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 61c0e21db0..04a39f1752 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 6751cb1a34..79665ed12a 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* @@ -875,6 +875,17 @@ _outFuncExpr(StringInfo str, FuncExpr *node) WRITE_LOCATION_FIELD(location); } +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) { @@ -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 8f5264d357..205b6da4cd 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 @@ -525,6 +525,22 @@ _readFuncExpr(void) READ_DONE(); } +/* + * _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 */ @@ -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 3f344b3a14..64f77a5c71 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 f2038c73af..dcfc731a17 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, @@ -3286,13 +3311,113 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod, return newexpr; } +/* + * 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,16 +3425,58 @@ 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); + List *defaults; + int ndelete; + ListCell *lc; + + /* 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); + + /* 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. + */ + 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; - 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, @@ -3321,20 +3488,33 @@ add_function_defaults(List *args, Oid result_type, HeapTuple func_tuple, defaults = (List *) stringToNode(str); Assert(IsA(defaults, List)); pfree(str); - /* 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); + 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; - /* - * The next part should be a no-op if there are no polymorphic arguments, - * but we do it anyway to be sure. - */ 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 ed265f516b..b86df8a870 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 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 func_arg_list +%type func_arg_expr %type row type_list array_expr_list %type case_expr case_arg when_clause case_default %type 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 39d66dcc12..3fccba1ab5 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 fd0706e960..e752dd8d1e 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) { - Datum proargdefaults; - bool isnull; - char *str; - List *defaults; + /* + * 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 + { + /* + * 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 fd90a29022..dd2a53f5a5 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 38057a0bfd..4c04bafd7c 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 7ecd781220..313b777d04 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 $ * *------------------------------------------------------------------------- */ @@ -766,6 +766,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 * diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 20eac6aa1c..8959997ea9 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 ed9218c03a..2c2b88951a 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 1373e4ad24..b4fe22c492 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 5fd046e95b..2a4468799f 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 5f5d4125c6..0320e23155 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 $ * *------------------------------------------------------------------------- */ @@ -313,6 +313,29 @@ typedef struct FuncExpr int location; /* token location, or -1 if unknown */ } 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 * diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h index 7905f96e86..0a38f740b1 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 77f693c2b1..36b31f09f4 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 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 486dd3f3fe..843bc53e4e 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 c01871de00..2071ce63da 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