* If pattern is NULL, it's a wild card that matches any word.
* If pattern begins with '!', the result is negated, ie we check that 'word'
* does *not* match any alternative appearing in the rest of 'pattern'.
- * Any alternative can end with '*' which is a wild card, i.e., it means
- * match any word that matches the characters so far. (We do not currently
- * support '*' elsewhere than the end of an alternative.)
+ * Any alternative can contain '*' which is a wild card, i.e., it can match
+ * any substring; however, we allow at most one '*' per alternative.
*
* For readability, callers should use the macros MatchAny and MatchAnyExcept
* to invoke those two special cases for 'pattern'. (But '|' and '*' must
const char *word,
bool case_sensitive)
{
- size_t wordlen,
- patternlen;
+ size_t wordlen;
+
+#define cimatch(s1, s2, n) \
+ (case_sensitive ? strncmp(s1, s2, n) == 0 : pg_strncasecmp(s1, s2, n) == 0)
/* NULL pattern matches anything. */
if (pattern == NULL)
wordlen = strlen(word);
for (;;)
{
+ const char *star = NULL;
const char *c;
- /* Find end of current alternative. */
+ /* Find end of current alternative, and locate any wild card. */
c = pattern;
while (*c != '\0' && *c != '|')
+ {
+ if (*c == '*')
+ star = c;
c++;
- /* Was there a wild card? (Assumes first alternative is not empty) */
- if (c[-1] == '*')
+ }
+ /* Was there a wild card? */
+ if (star)
{
/* Yes, wildcard match? */
- patternlen = c - pattern - 1;
- if (wordlen >= patternlen &&
- (case_sensitive ?
- strncmp(word, pattern, patternlen) == 0 :
- pg_strncasecmp(word, pattern, patternlen) == 0))
+ size_t beforelen = star - pattern,
+ afterlen = c - star - 1;
+
+ if (wordlen >= (beforelen + afterlen) &&
+ cimatch(word, pattern, beforelen) &&
+ cimatch(word + wordlen - afterlen, star + 1, afterlen))
return true;
}
else
{
/* No, plain match? */
- patternlen = c - pattern;
- if (wordlen == patternlen &&
- (case_sensitive ?
- strncmp(word, pattern, wordlen) == 0 :
- pg_strncasecmp(word, pattern, wordlen) == 0))
+ if (wordlen == (c - pattern) &&
+ cimatch(word, pattern, wordlen))
return true;
}
/* Out of alternatives? */
else if (Matches5("ALTER", "TYPE", MatchAny, "RENAME", "VALUE"))
COMPLETE_WITH_ENUM_VALUE(prev3_wd);
+/*
+ * ANALYZE [ ( option [, ...] ) ] [ table_and_columns [, ...] ]
+ * ANALYZE [ VERBOSE ] [ table_and_columns [, ...] ]
+ *
+ * Currently the only allowed option is VERBOSE, so we can be skimpier on
+ * the option processing than VACUUM has to be.
+ */
+ else if (Matches1("ANALYZE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_analyzables,
+ " UNION SELECT 'VERBOSE'");
+ else if (Matches2("ANALYZE", "("))
+ COMPLETE_WITH_CONST("VERBOSE)");
+ else if (HeadMatches1("ANALYZE") && TailMatches1("("))
+ /* "ANALYZE (" should be caught above, so assume we want columns */
+ COMPLETE_WITH_ATTR(prev2_wd, "");
+ else if (HeadMatches1("ANALYZE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_analyzables, NULL);
+
/* BEGIN */
else if (Matches1("BEGIN"))
COMPLETE_WITH_LIST6("WORK", "TRANSACTION", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
else if (Matches1("EXECUTE"))
COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
-/* EXPLAIN */
-
- /*
- * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
- */
+/*
+ * EXPLAIN [ ( option [, ...] ) ] statement
+ * EXPLAIN [ ANALYZE ] [ VERBOSE ] statement
+ */
else if (Matches1("EXPLAIN"))
COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
"ANALYZE", "VERBOSE");
+ else if (HeadMatches2("EXPLAIN", "(*") &&
+ !HeadMatches2("EXPLAIN", "(*)"))
+ {
+ /*
+ * This fires if we're in an unfinished parenthesized option list.
+ * get_previous_words treats a completed parenthesized option list as
+ * one word, so the above test is correct.
+ */
+ if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
+ COMPLETE_WITH_LIST7("ANALYZE", "VERBOSE", "COSTS", "BUFFERS",
+ "TIMING", "SUMMARY", "FORMAT");
+ else if (TailMatches1("ANALYZE|VERBOSE|COSTS|BUFFERS|TIMING|SUMMARY"))
+ COMPLETE_WITH_LIST2("ON", "OFF");
+ else if (TailMatches1("FORMAT"))
+ COMPLETE_WITH_LIST4("TEXT", "XML", "JSON", "YAML");
+ }
else if (Matches2("EXPLAIN", "ANALYZE"))
COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
"VERBOSE");
- else if (Matches2("EXPLAIN", "VERBOSE") ||
+ else if (Matches2("EXPLAIN", "(*)") ||
+ Matches2("EXPLAIN", "VERBOSE") ||
Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
COMPLETE_WITH_CONST("OPTIONS");
/*
- * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
- * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
+ * VACUUM [ ( option [, ...] ) ] [ table_and_columns [, ...] ]
+ * VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ table_and_columns [, ...] ]
*/
else if (Matches1("VACUUM"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables,
" UNION SELECT 'FREEZE'"
" UNION SELECT 'ANALYZE'"
" UNION SELECT 'VERBOSE'");
- else if (Matches2("VACUUM", "FULL|FREEZE"))
+ else if (Matches2("VACUUM", "FULL"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables,
+ " UNION SELECT 'FREEZE'"
" UNION SELECT 'ANALYZE'"
" UNION SELECT 'VERBOSE'");
- else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
- COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables,
- " UNION SELECT 'VERBOSE'");
- else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
+ else if (Matches2("VACUUM", "FREEZE") ||
+ Matches3("VACUUM", "FULL", "FREEZE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables,
+ " UNION SELECT 'VERBOSE'"
" UNION SELECT 'ANALYZE'");
- else if (Matches2("VACUUM", "VERBOSE"))
+ else if (Matches2("VACUUM", "VERBOSE") ||
+ Matches3("VACUUM", "FULL|FREEZE", "VERBOSE") ||
+ Matches4("VACUUM", "FULL", "FREEZE", "VERBOSE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables,
" UNION SELECT 'ANALYZE'");
- else if (Matches2("VACUUM", "ANALYZE"))
- COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables,
- " UNION SELECT 'VERBOSE'");
+ else if (HeadMatches2("VACUUM", "(*") &&
+ !HeadMatches2("VACUUM", "(*)"))
+ {
+ /*
+ * This fires if we're in an unfinished parenthesized option list.
+ * get_previous_words treats a completed parenthesized option list as
+ * one word, so the above test is correct.
+ */
+ if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
+ COMPLETE_WITH_LIST5("FULL", "FREEZE", "ANALYZE", "VERBOSE",
+ "DISABLE_PAGE_SKIPPING");
+ }
+ else if (HeadMatches1("VACUUM") && TailMatches1("("))
+ /* "VACUUM (" should be caught above, so assume we want columns */
+ COMPLETE_WITH_ATTR(prev2_wd, "");
else if (HeadMatches1("VACUUM"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables, NULL);
else if (Matches1("WITH"))
COMPLETE_WITH_CONST("RECURSIVE");
-/* ANALYZE */
- /* Complete with list of appropriate relations */
- else if (Matches1("ANALYZE"))
- COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_analyzables, NULL);
-
/* WHERE */
/* Simple case of the word before the where being the table name */
else if (TailMatches2(MatchAny, "WHERE"))