1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-16 15:02:33 +03:00

Provide more-specific error details/hints for function lookup failures.

Up to now we've contented ourselves with a one-size-fits-all error
hint when we fail to find any match to a function or procedure call.
That was mostly okay in the beginning, but it was never great, and
since the introduction of named arguments it's really not adequate.
We at least ought to distinguish "function name doesn't exist" from
"function name exists, but not with those argument names".  And the
rules for named-argument matching are arcane enough that some more
detail seems warranted if we match the argument names but the call
still doesn't work.

This patch creates a framework for dealing with these problems:
FuncnameGetCandidates and related code will now pass back a bitmask of
flags showing how far the match succeeded.  This allows a considerable
amount of granularity in the reports.  The set-bits-in-a-bitmask
approach means that when there are multiple candidate functions, the
report will reflect the match(es) that got the furthest, which seems
correct.  Also, we can avoid mentioning "maybe add casts" unless
failure to match argument types is actually the issue.

Extend the same return-a-bitmask approach to OpernameGetCandidates.
The issues around argument names don't apply to operator syntax,
but it still seems worth distinguishing between "there is no
operator of that name" and "we couldn't match the argument types".

While at it, adjust these messages and related ones to more strictly
separate "detail" from "hint", following our message style guidelines'
distinction between those.

Reported-by: Dominique Devienne <ddevienne@gmail.com>
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Robert Haas <robertmhaas@gmail.com>
Discussion: https://postgr.es/m/1756041.1754616558@sss.pgh.pa.us
This commit is contained in:
Tom Lane
2025-09-16 12:17:02 -04:00
parent c7b0cb367d
commit 83a5641945
44 changed files with 573 additions and 151 deletions

View File

@@ -232,7 +232,7 @@ static void RemoveTempRelationsCallback(int code, Datum arg);
static void InvalidationCallback(Datum arg, int cacheid, uint32 hashvalue);
static bool MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
bool include_out_arguments, int pronargs,
int **argnumbers);
int **argnumbers, int *fgc_flags);
/*
* Recomputing the namespace path can be costly when done frequently, such as
@@ -1117,15 +1117,15 @@ TypeIsVisibleExt(Oid typid, bool *is_missing)
/*
* FuncnameGetCandidates
* Given a possibly-qualified function name and argument count,
* Given a possibly-qualified routine name, argument count, and arg names,
* retrieve a list of the possible matches.
*
* If nargs is -1, we return all functions matching the given name,
* If nargs is -1, we return all routines matching the given name,
* 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.
* and only routines 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.
@@ -1183,14 +1183,21 @@ TypeIsVisibleExt(Oid typid, bool *is_missing)
* 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.
*
* If missing_ok is true, an empty list (NULL) is returned if the name was
* schema-qualified with a schema that does not exist. Likewise if no
* candidate is found for other reasons.
* We return an empty list (NULL) if no suitable matches can be found.
* If the function name was schema-qualified with a schema that does not
* exist, then we return an empty list if missing_ok is true and otherwise
* throw an error. (missing_ok does not affect the behavior otherwise.)
*
* The output argument *fgc_flags is filled with a bitmask indicating how
* far we were able to match the supplied information. This is not of much
* interest if any candidates were found, but if not, it can help callers
* produce an on-point error message.
*/
FuncCandidateList
FuncnameGetCandidates(List *names, int nargs, List *argnames,
bool expand_variadic, bool expand_defaults,
bool include_out_arguments, bool missing_ok)
bool include_out_arguments, bool missing_ok,
int *fgc_flags)
{
FuncCandidateList resultList = NULL;
bool any_special = false;
@@ -1203,15 +1210,20 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
/* check for caller error */
Assert(nargs >= 0 || !(expand_variadic | expand_defaults));
/* initialize output fgc_flags to empty */
*fgc_flags = 0;
/* deconstruct the name list */
DeconstructQualifiedName(names, &schemaname, &funcname);
if (schemaname)
{
/* use exact schema given */
*fgc_flags |= FGC_SCHEMA_GIVEN; /* report that a schema is given */
namespaceId = LookupExplicitNamespace(schemaname, missing_ok);
if (!OidIsValid(namespaceId))
return NULL;
*fgc_flags |= FGC_SCHEMA_EXISTS; /* report that the schema exists */
}
else
{
@@ -1237,6 +1249,8 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
int *argnumbers = NULL;
FuncCandidateList newResult;
*fgc_flags |= FGC_NAME_EXISTS; /* the name is present in pg_proc */
if (OidIsValid(namespaceId))
{
/* Consider only procs in specified namespace */
@@ -1262,6 +1276,8 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
continue; /* proc is not in search path */
}
*fgc_flags |= FGC_NAME_VISIBLE; /* routine is in the right schema */
/*
* If we are asked to match to OUT arguments, then use the
* proallargtypes array (which includes those); otherwise use
@@ -1296,16 +1312,6 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
/*
* 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 */
@@ -1324,12 +1330,33 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
if (pronargs != nargs && !use_defaults)
continue;
/* We found a routine with a suitable number of arguments */
*fgc_flags |= FGC_ARGCOUNT_MATCH;
/* Check for argument name match, generate positional mapping */
if (!MatchNamedCall(proctup, nargs, argnames,
include_out_arguments, pronargs,
&argnumbers))
&argnumbers, fgc_flags))
continue;
/*
* 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.
* However, we postpone the check until here because we want to
* perform argument name matching anyway (using the variadic array
* argument's name). This allows us to give an on-point error
* message if the user forgets to say VARIADIC in what would have
* been a valid call with it.
*/
if (OidIsValid(procform->provariadic) && expand_variadic)
continue;
va_elem_type = InvalidOid;
variadic = false;
/* We found a fully-valid call using argument names */
*fgc_flags |= FGC_ARGNAMES_VALID;
/* Named argument matching is always "special" */
any_special = true;
}
@@ -1371,6 +1398,9 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
/* Ignore if it doesn't match requested argument count */
if (nargs >= 0 && pronargs != nargs && !variadic && !use_defaults)
continue;
/* We found a routine with a suitable number of arguments */
*fgc_flags |= FGC_ARGCOUNT_MATCH;
}
/*
@@ -1579,11 +1609,13 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
* 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.
*
* We also add flag bits to *fgc_flags reporting on how far the match got.
*/
static bool
MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
bool include_out_arguments, int pronargs,
int **argnumbers)
int **argnumbers, int *fgc_flags)
{
Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
int numposargs = nargs - list_length(argnames);
@@ -1592,6 +1624,7 @@ MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
char **p_argnames;
char *p_argmodes;
bool arggiven[FUNC_MAX_ARGS];
bool arg_filled_twice = false;
bool isnull;
int ap; /* call args position */
int pp; /* proargs position */
@@ -1645,9 +1678,9 @@ MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
continue;
if (p_argnames[i] && strcmp(p_argnames[i], argname) == 0)
{
/* fail if argname matches a positional argument */
/* note if argname matches a positional argument */
if (arggiven[pp])
return false;
arg_filled_twice = true;
arggiven[pp] = true;
(*argnumbers)[ap] = pp;
found = true;
@@ -1664,6 +1697,16 @@ MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
Assert(ap == nargs); /* processed all actual parameters */
/* If we get here, the function did match all the supplied argnames */
*fgc_flags |= FGC_ARGNAMES_MATCH;
/* ... however, some of them might have been placed wrong */
if (arg_filled_twice)
return false; /* some argname matched a positional argument */
/* If we get here, the call doesn't have invalid mixed notation */
*fgc_flags |= FGC_ARGNAMES_NONDUP;
/* Check for default arguments */
if (nargs < pronargs)
{
@@ -1682,6 +1725,9 @@ MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
Assert(ap == pronargs); /* processed all function parameters */
/* If we get here, the call supplies all the required arguments */
*fgc_flags |= FGC_ARGNAMES_ALL;
return true;
}
@@ -1745,11 +1791,13 @@ FunctionIsVisibleExt(Oid funcid, bool *is_missing)
char *proname = NameStr(procform->proname);
int nargs = procform->pronargs;
FuncCandidateList clist;
int fgc_flags;
visible = false;
clist = FuncnameGetCandidates(list_make1(makeString(proname)),
nargs, NIL, false, false, false, false);
nargs, NIL, false, false, false, false,
&fgc_flags);
for (; clist; clist = clist->next)
{
@@ -1882,9 +1930,20 @@ OpernameGetOprid(List *names, Oid oprleft, Oid oprright)
*
* The returned items always have two args[] entries --- the first will be
* InvalidOid for a prefix oprkind. nargs is always 2, too.
*
* We return an empty list (NULL) if no suitable matches can be found. If the
* operator name was schema-qualified with a schema that does not exist, then
* we return an empty list if missing_schema_ok is true and otherwise throw an
* error. (missing_schema_ok does not affect the behavior otherwise.)
*
* The output argument *fgc_flags is filled with a bitmask indicating how
* far we were able to match the supplied information. This is not of much
* interest if any candidates were found, but if not, it can help callers
* produce an on-point error message.
*/
FuncCandidateList
OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok)
OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok,
int *fgc_flags)
{
FuncCandidateList resultList = NULL;
char *resultSpace = NULL;
@@ -1895,15 +1954,20 @@ OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok)
CatCList *catlist;
int i;
/* initialize output fgc_flags to empty */
*fgc_flags = 0;
/* deconstruct the name list */
DeconstructQualifiedName(names, &schemaname, &opername);
if (schemaname)
{
/* use exact schema given */
*fgc_flags |= FGC_SCHEMA_GIVEN; /* report that a schema is given */
namespaceId = LookupExplicitNamespace(schemaname, missing_schema_ok);
if (missing_schema_ok && !OidIsValid(namespaceId))
if (!OidIsValid(namespaceId))
return NULL;
*fgc_flags |= FGC_SCHEMA_EXISTS; /* report that the schema exists */
}
else
{
@@ -1941,6 +2005,8 @@ OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok)
if (oprkind && operform->oprkind != oprkind)
continue;
*fgc_flags |= FGC_NAME_EXISTS; /* the name is present in pg_operator */
if (OidIsValid(namespaceId))
{
/* Consider only opers in specified namespace */
@@ -2014,6 +2080,8 @@ OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok)
}
}
*fgc_flags |= FGC_NAME_VISIBLE; /* operator is in the right schema */
/*
* Okay to add it to result list
*/

View File

@@ -836,6 +836,7 @@ lookup_agg_function(List *fnName,
Oid vatype;
Oid *true_oid_array;
FuncDetailCode fdresult;
int fgc_flags;
AclResult aclresult;
int i;
@@ -848,6 +849,7 @@ lookup_agg_function(List *fnName,
*/
fdresult = func_get_detail(fnName, NIL, NIL,
nargs, input_types, false, false, false,
&fgc_flags,
&fnOid, rettype, &retset,
&nvargs, &vatype,
&true_oid_array, NULL);

View File

@@ -42,6 +42,8 @@ typedef enum
FUNCLOOKUP_AMBIGUOUS,
} FuncLookupError;
static int func_lookup_failure_details(int fgc_flags, List *argnames,
bool proc_call);
static void unify_hypothetical_args(ParseState *pstate,
List *fargs, int numAggregatedArgs,
Oid *actual_arg_types, Oid *declared_arg_types);
@@ -115,6 +117,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
int nvargs;
Oid vatype;
FuncDetailCode fdresult;
int fgc_flags;
char aggkind = 0;
ParseCallbackState pcbstate;
@@ -266,6 +269,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
fdresult = func_get_detail(funcname, fargs, argnames, nargs,
actual_arg_types,
!func_variadic, true, proc_call,
&fgc_flags,
&funcid, &rettype, &retset,
&nvargs, &vatype,
&declared_arg_types, &argdefaults);
@@ -563,8 +567,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
errmsg("procedure %s is not unique",
func_signature_string(funcname, nargs, argnames,
actual_arg_types)),
errhint("Could not choose a best candidate procedure. "
"You might need to add explicit type casts."),
errdetail("Could not choose a best candidate procedure."),
errhint("You might need to add explicit type casts."),
parser_errposition(pstate, location)));
else
ereport(ERROR,
@@ -572,8 +576,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
errmsg("function %s is not unique",
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."),
errdetail("Could not choose a best candidate function."),
errhint("You might need to add explicit type casts."),
parser_errposition(pstate, location)));
}
else
@@ -601,7 +605,9 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
/*
* No function, and no column either. Since we're dealing with
* function notation, report "function does not exist".
* function notation, report "function/procedure does not exist".
* Depending on what was returned in fgc_flags, we can add some color
* to that with detail or hint messages.
*/
if (list_length(agg_order) > 1 && !agg_within_group)
{
@@ -611,8 +617,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
errmsg("function %s does not exist",
func_signature_string(funcname, nargs, argnames,
actual_arg_types)),
errhint("No aggregate function matches the given name and argument types. "
"Perhaps you misplaced ORDER BY; ORDER BY must appear "
errdetail("No aggregate function matches the given name and argument types."),
errhint("Perhaps you misplaced ORDER BY; ORDER BY must appear "
"after all regular arguments of the aggregate."),
parser_errposition(pstate, location)));
}
@@ -622,8 +628,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
errmsg("procedure %s does not exist",
func_signature_string(funcname, nargs, argnames,
actual_arg_types)),
errhint("No procedure matches the given name and argument types. "
"You might need to add explicit type casts."),
func_lookup_failure_details(fgc_flags, argnames,
proc_call),
parser_errposition(pstate, location)));
else
ereport(ERROR,
@@ -631,8 +637,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
errmsg("function %s does not exist",
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."),
func_lookup_failure_details(fgc_flags, argnames,
proc_call),
parser_errposition(pstate, location)));
}
@@ -905,6 +911,104 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
return retval;
}
/*
* Interpret the fgc_flags and issue a suitable detail or hint message.
*
* Helper function to reduce code duplication while throwing a
* function-not-found error.
*/
static int
func_lookup_failure_details(int fgc_flags, List *argnames, bool proc_call)
{
/*
* If not FGC_NAME_VISIBLE, we shouldn't raise the question of whether the
* arguments are wrong. If the function name was not schema-qualified,
* it's helpful to distinguish between doesn't-exist-anywhere and
* not-in-search-path; but if it was, there's really nothing to add to the
* basic "function/procedure %s does not exist" message.
*
* Note: we passed missing_ok = false to FuncnameGetCandidates, so there's
* no need to consider FGC_SCHEMA_EXISTS here: we'd have already thrown an
* error if an explicitly-given schema doesn't exist.
*/
if (!(fgc_flags & FGC_NAME_VISIBLE))
{
if (fgc_flags & FGC_SCHEMA_GIVEN)
return 0; /* schema-qualified name */
else if (!(fgc_flags & FGC_NAME_EXISTS))
{
if (proc_call)
return errdetail("There is no procedure of that name.");
else
return errdetail("There is no function of that name.");
}
else
{
if (proc_call)
return errdetail("A procedure of that name exists, but it is not in the search_path.");
else
return errdetail("A function of that name exists, but it is not in the search_path.");
}
}
/*
* Next, complain if nothing had the right number of arguments. (This
* takes precedence over wrong-argnames cases because we won't even look
* at the argnames unless there's a workable number of arguments.)
*/
if (!(fgc_flags & FGC_ARGCOUNT_MATCH))
{
if (proc_call)
return errdetail("No procedure of that name accepts the given number of arguments.");
else
return errdetail("No function of that name accepts the given number of arguments.");
}
/*
* If there are argnames, and we failed to match them, again we should
* mention that and not bring up the argument types.
*/
if (argnames != NIL && !(fgc_flags & FGC_ARGNAMES_MATCH))
{
if (proc_call)
return errdetail("No procedure of that name accepts the given argument names.");
else
return errdetail("No function of that name accepts the given argument names.");
}
/*
* We could have matched all the given argnames and still not have had a
* valid call, either because of improper use of mixed notation, or
* because of missing arguments, or because the user misused VARIADIC. The
* rules about named-argument matching are finicky enough that it's worth
* trying to be specific about the problem. (The messages here are chosen
* with full knowledge of the steps that namespace.c uses while checking a
* potential match.)
*/
if (argnames != NIL && !(fgc_flags & FGC_ARGNAMES_NONDUP))
return errdetail("In the closest available match, "
"an argument was specified both positionally and by name.");
if (argnames != NIL && !(fgc_flags & FGC_ARGNAMES_ALL))
return errdetail("In the closest available match, "
"not all required arguments were supplied.");
if (argnames != NIL && !(fgc_flags & FGC_ARGNAMES_VALID))
return errhint("This call would be correct if the variadic array were labeled VARIADIC and placed last.");
if (fgc_flags & FGC_VARIADIC_FAIL)
return errhint("The VARIADIC parameter must be placed last, even when using argument names.");
/*
* Otherwise, the problem must be incorrect argument types.
*/
if (proc_call)
(void) errdetail("No procedure of that name accepts the given argument types.");
else
(void) errdetail("No function of that name accepts the given argument types.");
return errhint("You might need to add explicit type casts.");
}
/* func_match_argtypes()
*
@@ -1372,9 +1476,14 @@ func_select_candidate(int nargs,
* 1) check for possible interpretation as a type coercion request
* 2) apply the ambiguous-function resolution rules
*
* 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.
* If there is no match at all, we return FUNCDETAIL_NOTFOUND, and *fgc_flags
* is filled with some flags that may be useful for issuing an on-point error
* message (see FuncnameGetCandidates).
*
* On success, 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
@@ -1400,6 +1509,7 @@ func_get_detail(List *funcname,
bool expand_variadic,
bool expand_defaults,
bool include_out_arguments,
int *fgc_flags, /* return value */
Oid *funcid, /* return value */
Oid *rettype, /* return value */
bool *retset, /* return value */
@@ -1424,7 +1534,8 @@ func_get_detail(List *funcname,
/* Get list of possible candidates from namespace search */
raw_candidates = FuncnameGetCandidates(funcname, nargs, fargnames,
expand_variadic, expand_defaults,
include_out_arguments, false);
include_out_arguments, false,
fgc_flags);
/*
* Quickly check if there is an exact match to the input datatypes (there
@@ -1594,7 +1705,10 @@ func_get_detail(List *funcname,
*/
if (fargnames != NIL && !expand_variadic && nargs > 0 &&
best_candidate->argnumbers[nargs - 1] != nargs - 1)
{
*fgc_flags |= FGC_VARIADIC_FAIL;
return FUNCDETAIL_NOTFOUND;
}
*funcid = best_candidate->oid;
*nvargs = best_candidate->nvargs;
@@ -2053,6 +2167,7 @@ LookupFuncNameInternal(ObjectType objtype, List *funcname,
{
Oid result = InvalidOid;
FuncCandidateList clist;
int fgc_flags;
/* NULL argtypes allowed for nullary functions only */
Assert(argtypes != NULL || nargs == 0);
@@ -2062,7 +2177,8 @@ LookupFuncNameInternal(ObjectType objtype, List *funcname,
/* Get list of candidate objects */
clist = FuncnameGetCandidates(funcname, nargs, NIL, false, false,
include_out_arguments, missing_ok);
include_out_arguments, missing_ok,
&fgc_flags);
/* Scan list for a match to the arg types (if specified) and the objtype */
for (; clist != NULL; clist = clist->next)

View File

@@ -72,7 +72,8 @@ static FuncDetailCode oper_select_candidate(int nargs,
Oid *operOid);
static void op_error(ParseState *pstate, List *op,
Oid arg1, Oid arg2,
FuncDetailCode fdresult, int location);
FuncDetailCode fdresult, int fgc_flags, int location);
static int oper_lookup_failure_details(int fgc_flags, bool is_unary_op);
static bool make_oper_cache_key(ParseState *pstate, OprCacheKey *key,
List *opname, Oid ltypeId, Oid rtypeId,
int location);
@@ -373,6 +374,7 @@ oper(ParseState *pstate, List *opname, Oid ltypeId, Oid rtypeId,
Oid operOid;
OprCacheKey key;
bool key_ok;
int fgc_flags = 0;
FuncDetailCode fdresult = FUNCDETAIL_NOTFOUND;
HeapTuple tup = NULL;
@@ -404,7 +406,7 @@ oper(ParseState *pstate, List *opname, Oid ltypeId, Oid rtypeId,
FuncCandidateList clist;
/* Get binary operators of given name */
clist = OpernameGetCandidates(opname, 'b', false);
clist = OpernameGetCandidates(opname, 'b', false, &fgc_flags);
/* No operators found? Then fail... */
if (clist != NULL)
@@ -434,7 +436,8 @@ oper(ParseState *pstate, List *opname, Oid ltypeId, Oid rtypeId,
make_oper_cache_entry(&key, operOid);
}
else if (!noError)
op_error(pstate, opname, ltypeId, rtypeId, fdresult, location);
op_error(pstate, opname, ltypeId, rtypeId,
fdresult, fgc_flags, location);
return (Operator) tup;
}
@@ -520,6 +523,7 @@ left_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location)
Oid operOid;
OprCacheKey key;
bool key_ok;
int fgc_flags = 0;
FuncDetailCode fdresult = FUNCDETAIL_NOTFOUND;
HeapTuple tup = NULL;
@@ -551,7 +555,7 @@ left_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location)
FuncCandidateList clist;
/* Get prefix operators of given name */
clist = OpernameGetCandidates(op, 'l', false);
clist = OpernameGetCandidates(op, 'l', false, &fgc_flags);
/* No operators found? Then fail... */
if (clist != NULL)
@@ -585,7 +589,8 @@ left_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location)
make_oper_cache_entry(&key, operOid);
}
else if (!noError)
op_error(pstate, op, InvalidOid, arg, fdresult, location);
op_error(pstate, op, InvalidOid, arg,
fdresult, fgc_flags, location);
return (Operator) tup;
}
@@ -621,29 +626,67 @@ op_signature_string(List *op, Oid arg1, Oid arg2)
static void
op_error(ParseState *pstate, List *op,
Oid arg1, Oid arg2,
FuncDetailCode fdresult, int location)
FuncDetailCode fdresult, int fgc_flags, int location)
{
if (fdresult == FUNCDETAIL_MULTIPLE)
ereport(ERROR,
(errcode(ERRCODE_AMBIGUOUS_FUNCTION),
errmsg("operator is not unique: %s",
op_signature_string(op, arg1, arg2)),
errhint("Could not choose a best candidate operator. "
"You might need to add explicit type casts."),
errdetail("Could not choose a best candidate operator."),
errhint("You might need to add explicit type casts."),
parser_errposition(pstate, location)));
else
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("operator does not exist: %s",
op_signature_string(op, arg1, arg2)),
(!arg1 || !arg2) ?
errhint("No operator matches the given name and argument type. "
"You might need to add an explicit type cast.") :
errhint("No operator matches the given name and argument types. "
"You might need to add explicit type casts."),
oper_lookup_failure_details(fgc_flags, (!arg1 || !arg2)),
parser_errposition(pstate, location)));
}
/*
* Interpret the fgc_flags and issue a suitable detail or hint message.
*/
static int
oper_lookup_failure_details(int fgc_flags, bool is_unary_op)
{
/*
* If not FGC_NAME_VISIBLE, we shouldn't raise the question of whether the
* arguments are wrong. If the operator name was not schema-qualified,
* it's helpful to distinguish between doesn't-exist-anywhere and
* not-in-search-path; but if it was, there's really nothing to add to the
* basic "operator does not exist" message.
*
* Note: we passed missing_ok = false to OpernameGetCandidates, so there's
* no need to consider FGC_SCHEMA_EXISTS here: we'd have already thrown an
* error if an explicitly-given schema doesn't exist.
*/
if (!(fgc_flags & FGC_NAME_VISIBLE))
{
if (fgc_flags & FGC_SCHEMA_GIVEN)
return 0; /* schema-qualified name */
else if (!(fgc_flags & FGC_NAME_EXISTS))
return errdetail("There is no operator of that name.");
else
return errdetail("An operator of that name exists, but it is not in the search_path.");
}
/*
* Otherwise, the problem must be incorrect argument type(s).
*/
if (is_unary_op)
{
(void) errdetail("No operator of that name accepts the given argument type.");
return errhint("You might need to add an explicit type cast.");
}
else
{
(void) errdetail("No operator of that name accepts the given argument types.");
return errhint("You might need to add explicit type casts.");
}
}
/*
* make_op()
* Operator expression construction.

View File

@@ -71,6 +71,7 @@ regprocin(PG_FUNCTION_ARGS)
RegProcedure result;
List *names;
FuncCandidateList clist;
int fgc_flags;
/* Handle "-" or numeric OID */
if (parseDashOrOid(pro_name_or_oid, &result, escontext))
@@ -93,7 +94,8 @@ regprocin(PG_FUNCTION_ARGS)
if (names == NIL)
PG_RETURN_NULL();
clist = FuncnameGetCandidates(names, -1, NIL, false, false, false, true);
clist = FuncnameGetCandidates(names, -1, NIL, false, false, false, true,
&fgc_flags);
if (clist == NULL)
ereturn(escontext, (Datum) 0,
@@ -164,13 +166,15 @@ regprocout(PG_FUNCTION_ARGS)
{
char *nspname;
FuncCandidateList clist;
int fgc_flags;
/*
* Would this proc be found (uniquely!) by regprocin? If not,
* qualify it.
*/
clist = FuncnameGetCandidates(list_make1(makeString(proname)),
-1, NIL, false, false, false, false);
-1, NIL, false, false, false, false,
&fgc_flags);
if (clist != NULL && clist->next == NULL &&
clist->oid == proid)
nspname = NULL;
@@ -231,6 +235,7 @@ regprocedurein(PG_FUNCTION_ARGS)
int nargs;
Oid argtypes[FUNC_MAX_ARGS];
FuncCandidateList clist;
int fgc_flags;
/* Handle "-" or numeric OID */
if (parseDashOrOid(pro_name_or_oid, &result, escontext))
@@ -251,8 +256,8 @@ regprocedurein(PG_FUNCTION_ARGS)
escontext))
PG_RETURN_NULL();
clist = FuncnameGetCandidates(names, nargs, NIL, false, false,
false, true);
clist = FuncnameGetCandidates(names, nargs, NIL, false, false, false, true,
&fgc_flags);
for (; clist; clist = clist->next)
{
@@ -483,6 +488,7 @@ regoperin(PG_FUNCTION_ARGS)
Oid result;
List *names;
FuncCandidateList clist;
int fgc_flags;
/* Handle "0" or numeric OID */
if (parseNumericOid(opr_name_or_oid, &result, escontext))
@@ -502,7 +508,7 @@ regoperin(PG_FUNCTION_ARGS)
if (names == NIL)
PG_RETURN_NULL();
clist = OpernameGetCandidates(names, '\0', true);
clist = OpernameGetCandidates(names, '\0', true, &fgc_flags);
if (clist == NULL)
ereturn(escontext, (Datum) 0,
@@ -572,13 +578,14 @@ regoperout(PG_FUNCTION_ARGS)
else
{
FuncCandidateList clist;
int fgc_flags;
/*
* Would this oper be found (uniquely!) by regoperin? If not,
* qualify it.
*/
clist = OpernameGetCandidates(list_make1(makeString(oprname)),
'\0', false);
'\0', false, &fgc_flags);
if (clist != NULL && clist->next == NULL &&
clist->oid == oprid)
result = pstrdup(oprname);

View File

@@ -13265,6 +13265,7 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
bool use_variadic;
char *nspname;
FuncDetailCode p_result;
int fgc_flags;
Oid p_funcid;
Oid p_rettype;
bool p_retset;
@@ -13323,6 +13324,7 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
p_result = func_get_detail(list_make1(makeString(proname)),
NIL, argnames, nargs, argtypes,
!use_variadic, true, false,
&fgc_flags,
&p_funcid, &p_rettype,
&p_retset, &p_nvargs, &p_vatype,
&p_true_typeids, NULL);