mirror of
https://github.com/postgres/postgres.git
synced 2025-11-10 17:42:29 +03:00
Adjust assorted hint messages that list all valid options.
Instead of listing all valid options, we now try to provide one that looks similar. Since this may be useful elsewhere, this change introduces a new set of functions that can be reused for similar purposes. Author: Nathan Bossart <nathandbossart@gmail.com> Discussion: https://www.postgresql.org/message-id/flat/b1f9f399-3a1a-b554-283f-4ae7f34608e2@enterprisedb.com
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
#include "utils/memutils.h"
|
||||
#include "utils/rel.h"
|
||||
#include "utils/syscache.h"
|
||||
#include "utils/varlena.h"
|
||||
|
||||
|
||||
/*
|
||||
@@ -621,25 +622,32 @@ postgresql_fdw_validator(PG_FUNCTION_ARGS)
|
||||
if (!is_conninfo_option(def->defname, catalog))
|
||||
{
|
||||
const struct ConnectionOption *opt;
|
||||
StringInfoData buf;
|
||||
const char *closest_match;
|
||||
ClosestMatchState match_state;
|
||||
bool has_valid_options = false;
|
||||
|
||||
/*
|
||||
* Unknown option specified, complain about it. Provide a hint
|
||||
* with list of valid options for the object.
|
||||
* with a valid option that looks similar, if there is one.
|
||||
*/
|
||||
initStringInfo(&buf);
|
||||
initClosestMatch(&match_state, def->defname, 4);
|
||||
for (opt = libpq_conninfo_options; opt->optname; opt++)
|
||||
{
|
||||
if (catalog == opt->optcontext)
|
||||
appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "",
|
||||
opt->optname);
|
||||
{
|
||||
has_valid_options = true;
|
||||
updateClosestMatch(&match_state, opt->optname);
|
||||
}
|
||||
}
|
||||
|
||||
closest_match = getClosestMatch(&match_state);
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("invalid option \"%s\"", def->defname),
|
||||
buf.len > 0
|
||||
? errhint("Valid options in this context are: %s",
|
||||
buf.data)
|
||||
: errhint("There are no valid options in this context.")));
|
||||
has_valid_options ? closest_match ?
|
||||
errhint("Perhaps you meant the option \"%s\".",
|
||||
closest_match) : 0 :
|
||||
errhint("There are no valid options in this context.")));
|
||||
|
||||
PG_RETURN_BOOL(false);
|
||||
}
|
||||
|
||||
@@ -6197,6 +6197,88 @@ rest_of_char_same(const char *s1, const char *s2, int len)
|
||||
#include "levenshtein.c"
|
||||
|
||||
|
||||
/*
|
||||
* The following *ClosestMatch() functions can be used to determine whether a
|
||||
* user-provided string resembles any known valid values, which is useful for
|
||||
* providing hints in log messages, among other things. Use these functions
|
||||
* like so:
|
||||
*
|
||||
* initClosestMatch(&state, source_string, max_distance);
|
||||
*
|
||||
* for (int i = 0; i < num_valid_strings; i++)
|
||||
* updateClosestMatch(&state, valid_strings[i]);
|
||||
*
|
||||
* closestMatch = getClosestMatch(&state);
|
||||
*/
|
||||
|
||||
/*
|
||||
* Initialize the given state with the source string and maximum Levenshtein
|
||||
* distance to consider.
|
||||
*/
|
||||
void
|
||||
initClosestMatch(ClosestMatchState *state, const char *source, int max_d)
|
||||
{
|
||||
Assert(state);
|
||||
Assert(max_d >= 0);
|
||||
|
||||
state->source = source;
|
||||
state->min_d = -1;
|
||||
state->max_d = max_d;
|
||||
state->match = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the candidate string is a closer match than the current one saved (or
|
||||
* there is no match saved), save it as the closest match.
|
||||
*
|
||||
* If the source or candidate string is NULL, empty, or too long, this function
|
||||
* takes no action. Likewise, if the Levenshtein distance exceeds the maximum
|
||||
* allowed or more than half the characters are different, no action is taken.
|
||||
*/
|
||||
void
|
||||
updateClosestMatch(ClosestMatchState *state, const char *candidate)
|
||||
{
|
||||
int dist;
|
||||
|
||||
Assert(state);
|
||||
|
||||
if (state->source == NULL || state->source[0] == '\0' ||
|
||||
candidate == NULL || candidate[0] == '\0')
|
||||
return;
|
||||
|
||||
/*
|
||||
* To avoid ERROR-ing, we check the lengths here instead of setting
|
||||
* 'trusted' to false in the call to varstr_levenshtein_less_equal().
|
||||
*/
|
||||
if (strlen(state->source) > MAX_LEVENSHTEIN_STRLEN ||
|
||||
strlen(candidate) > MAX_LEVENSHTEIN_STRLEN)
|
||||
return;
|
||||
|
||||
dist = varstr_levenshtein_less_equal(state->source, strlen(state->source),
|
||||
candidate, strlen(candidate), 1, 1, 1,
|
||||
state->max_d, true);
|
||||
if (dist <= state->max_d &&
|
||||
dist <= strlen(state->source) / 2 &&
|
||||
(state->min_d == -1 || dist < state->min_d))
|
||||
{
|
||||
state->min_d = dist;
|
||||
state->match = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the closest match. If no suitable candidates were provided via
|
||||
* updateClosestMatch(), return NULL.
|
||||
*/
|
||||
const char *
|
||||
getClosestMatch(ClosestMatchState *state)
|
||||
{
|
||||
Assert(state);
|
||||
|
||||
return state->match;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Unicode support
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user