IF (NOT) EXISTS in psql-completion

Started by Kyotaro HORIGUCHIalmost 10 years ago90 messages
#1Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
1 attachment(s)

Hello,

I considered how to make tab-completion robust for syntactical
noises, in other words, optional words in syntax. Typically "IF
(NOT) EXISTS", UNIQUE and TEMPORARY are words that don't affect
further completion. However, the current delimit-matching
mechanism is not so capable (or is complexty-prone) to live with
such noises. I have proposed to use regular expressions or
simplified one for the robustness but it was too complex to be
applied.

This is another answer for the problem. Removal of such words
on-the-fly makes further matching more robust.

Next, currently some CREATE xxx subsyntaxes of CREATE SCHEMA are
matched using TailMatching but it makes difficult the
options-removal operations, which needs forward matching.

So I introduced two things to resolve them by this patch.

1. HEAD_SHIFT macro

It shifts the beginning index in previous_words for *MatchN
macros. When the varialbe is 3 and previous_words is as
following,

{"NOT", "IF", "CONCURRENTLY", "INDEX", "UNIQUE", "CREATE"}

Match3("CONCURRENTLY", "IF", "NOT") reutrns true. HeadMatches
and TailMatches works on the same basis. This allows us to
match "CREATE xxx" subsyntaxes of CREATE SCHEMA
independently. SHIFT_TO_LAST1() macro finds the last appearance
of specified word and HEAD_SHIFT to there if found.

2. MidMatchAndRemoveN() macros

These macros remove specified words starts from specfied number
of words after the current beginning. Having head_shift = 0 and
the following previous_words,

{"i_t1_a", "EXISTS", "IF", "INDEX", "DROP"}

MidMatchAndRemove2(2, "IF", "EXISTS") leaves the following
previous_words.

{"i_t1_a", "INDEX", "DROP"}

Using these things, the patch as whole does the following things.

A. Allows "IF (NOT) EXISTS" at almost everywhere it allowed
syntactically.

The boilerplate is like the following,

| else if (MatchesN(words before IF EXISTS))
| COMPLETE_WITH_XXX(... "UNION SELECT 'IF EXISTS'");
| else if (HeadMatchesN(words before "IF EXISTS") &&
| MidMatchAndRemoveM(N, words to be removed) &&
| MatchesN(words before "IF EXISTS"))
| COMPLETE_WITH_XXXX();

The first "else if" makes suggestion for the 'thing' with "IF
EXISTS".

The MidMatchAndRemoveM in the second "else if" actually
removes "IF EXISTS" if match and the third MatchesN or all
matching macros ever after don't see the removed words. So
they can make a suggestion without seeing such noises.

This looks a bit hackery but works well in the current
framework.

B. Masks the part in CREATE SCHEMA unrelated to the last CREATE
subsyntax currently focused on.

| else if (HeadMatches2("CREATE", "SCHEMA") &&
| SHIFT_TO_LAST1("CREATE") &&
| false) {} /* FALL THROUGH */

The result of this, for the query like this,

CREATE SCHEMA foo bar baz CREATE foe fee CREATE hoge hage

all the following part of psql_completion works as if the
current query is just "CREATE hoge hage".

Does anybody have suggestions, opinions or objections?

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

0001-Suggest-IF-NOT-EXISTS-for-tab-completion-of-psql.patchtext/x-patch; charset=us-asciiDownload
>From 82928996a0887882212d05aca3406a3bd3cf22e5 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 5 Feb 2016 16:50:35 +0900
Subject: [PATCH] Suggest IF (NOT) EXISTS for tab-completion of psql

This patch lets psql to suggest "IF (NOT) EXISTS". Addition to that,
since this patch introduces some mechanism for syntactical robustness,
it allows psql completion to omit some optional part on matching.
---
 src/bin/psql/tab-complete.c | 559 ++++++++++++++++++++++++++++++++++++--------
 1 file changed, 463 insertions(+), 96 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5f27120..d95698a 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -656,6 +656,10 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "   FROM pg_catalog.pg_roles "\
 "  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"
 
+#define Query_for_list_of_rules \
+"SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules "\
+" WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"
+
 #define Query_for_list_of_grant_roles \
 " SELECT pg_catalog.quote_ident(rolname) "\
 "   FROM pg_catalog.pg_roles "\
@@ -763,6 +767,11 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "SELECT pg_catalog.quote_ident(tmplname) FROM pg_catalog.pg_ts_template "\
 " WHERE substring(pg_catalog.quote_ident(tmplname),1,%d)='%s'"
 
+#define Query_for_list_of_triggers \
+"SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger "\
+" WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s' AND "\
+"       NOT tgisinternal"
+
 #define Query_for_list_of_fdws \
 " SELECT pg_catalog.quote_ident(fdwname) "\
 "   FROM pg_catalog.pg_foreign_data_wrapper "\
@@ -906,7 +915,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
 	{"POLICY", NULL, NULL},
 	{"ROLE", Query_for_list_of_roles},
-	{"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"},
+	{"RULE", Query_for_list_of_rules},
 	{"SCHEMA", Query_for_list_of_schemas},
 	{"SEQUENCE", NULL, &Query_for_list_of_sequences},
 	{"SERVER", Query_for_list_of_servers},
@@ -915,7 +924,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"TEMP", NULL, NULL, THING_NO_DROP},		/* for CREATE TEMP TABLE ... */
 	{"TEMPLATE", Query_for_list_of_ts_templates, NULL, THING_NO_SHOW},
 	{"TEXT SEARCH", NULL, NULL},
-	{"TRIGGER", "SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s' AND NOT tgisinternal"},
+	{"TRIGGER", Query_for_list_of_triggers},
 	{"TYPE", NULL, &Query_for_list_of_datatypes},
 	{"UNIQUE", NULL, NULL, THING_NO_DROP},		/* for CREATE UNIQUE INDEX ... */
 	{"UNLOGGED", NULL, NULL, THING_NO_DROP},	/* for CREATE UNLOGGED TABLE
@@ -944,6 +953,7 @@ static char **complete_from_variables(const char *text,
 					const char *prefix, const char *suffix, bool need_value);
 static char *complete_from_files(const char *text, int state);
 
+static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
@@ -952,6 +962,7 @@ static char **get_previous_words(int point, char **buffer, int *nwords);
 
 static char *get_guctype(const char *varname);
 
+static const pgsql_thing_t *find_thing_entry(char *word);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -1107,6 +1118,9 @@ psql_completion(const char *text, int start, int end)
 	/* The number of words found on the input line. */
 	int			previous_words_count;
 
+	/* The number of prefixing words to be ignored */
+	int			head_shift = 0;
+
 	/*
 	 * For compactness, we use these macros to reference previous_words[].
 	 * Caution: do not access a previous_words[] entry without having checked
@@ -1124,31 +1138,72 @@ psql_completion(const char *text, int start, int end)
 #define prev8_wd  (previous_words[7])
 #define prev9_wd  (previous_words[8])
 
+	/* Move the position of the beginning word for matching macros.  */
+#define HEADSHIFT(n) \
+	(head_shift += n, true)
+
+	/* Return the number of stored words counting head shift */
+#define WORD_COUNT() (previous_words_count - head_shift)
+
+	/* Return the true index in previous_words for index from the beginning */
+#define HEAD_INDEX(n) \
+	(previous_words_count - head_shift - (n))
+
+	/*
+	 * remove n words from current shifted position, see MidMatchAndRevmove2
+	 * for the reason for the return value
+	 */
+#define COLLAPSE(n) \
+	(memmove(previous_words + HEAD_INDEX(n), previous_words + HEAD_INDEX(0), \
+			 sizeof(char *) * head_shift),								\
+	 previous_words_count -= (n), false)
+
+	/*
+	 * Find the position the specified word occurs last and shift to there.
+	 * This is used to ignore the words before there.
+	 */
+#define SHIFT_TO_LAST1(p1) \
+	(HEADSHIFT(find_last_index_of(p1, previous_words, previous_words_count)), \
+	 true)
+
+	/*
+	 * Remove the specified words if they match from the sth word in
+	 * previous_words.
+	 */
+#define MidMatchAndRemove1(s, p1) \
+	((HEADSHIFT(s) && HeadMatches1(p1) && COLLAPSE(1)) || HEADSHIFT(-s))
+
+#define MidMatchAndRemove2(s, p1, p2) \
+	((HEADSHIFT(s) && HeadMatches2(p1, p2) && COLLAPSE(2)) || HEADSHIFT(-s))
+
+#define MidMatchAndRemove3(s, p1, p2, p3)									\
+	((HEADSHIFT(s) && HeadMatches3(p1, p2, p3) && COLLAPSE(3)) || HEADSHIFT(-s))
+
 	/* Macros for matching the last N words before point, case-insensitively. */
 #define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches(p1, prev_wd))
 
 #define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd))
 
 #define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
+	(WORD_COUNT() >= 3 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd))
 
 #define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
+	(WORD_COUNT() >= 4 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
 	 word_matches(p4, prev4_wd))
 
 #define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
+	(WORD_COUNT() >= 5 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1156,7 +1211,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p5, prev5_wd))
 
 #define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
+	(WORD_COUNT() >= 6 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1165,7 +1220,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p6, prev6_wd))
 
 #define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
+	(WORD_COUNT() >= 7 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1175,7 +1230,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p7, prev7_wd))
 
 #define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
+	(WORD_COUNT() >= 8 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1186,7 +1241,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p8, prev8_wd))
 
 #define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
+	(WORD_COUNT() >= 9 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1199,43 +1254,43 @@ psql_completion(const char *text, int start, int end)
 
 	/* Macros for matching the last N words before point, case-sensitively. */
 #define TailMatchesCS1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches_cs(p1, prev_wd))
 #define TailMatchesCS2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches_cs(p1, prev_wd) && \
 	 word_matches_cs(p2, prev2_wd))
 
 	/*
-	 * Macros for matching N words beginning at the start of the line,
+	 * Macros for matching N words exactly to the line,
 	 * case-insensitively.
 	 */
 #define Matches1(p1) \
-	(previous_words_count == 1 && \
+	(WORD_COUNT() == 1 && \
 	 TailMatches1(p1))
 #define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
+	(WORD_COUNT() == 2 && \
 	 TailMatches2(p1, p2))
 #define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
+	(WORD_COUNT() == 3 && \
 	 TailMatches3(p1, p2, p3))
 #define Matches4(p1, p2, p3, p4) \
-	(previous_words_count == 4 && \
+	(WORD_COUNT() == 4 && \
 	 TailMatches4(p1, p2, p3, p4))
 #define Matches5(p1, p2, p3, p4, p5) \
-	(previous_words_count == 5 && \
+	(WORD_COUNT() == 5 && \
 	 TailMatches5(p1, p2, p3, p4, p5))
 #define Matches6(p1, p2, p3, p4, p5, p6) \
-	(previous_words_count == 6 && \
+	(WORD_COUNT() == 6 && \
 	 TailMatches6(p1, p2, p3, p4, p5, p6))
 #define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(previous_words_count == 7 && \
+	(WORD_COUNT() == 7 && \
 	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
 #define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(previous_words_count == 8 && \
+	(WORD_COUNT() == 8 && \
 	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
 #define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(previous_words_count == 9 && \
+	(WORD_COUNT() == 9 && \
 	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
 
 	/*
@@ -1243,19 +1298,34 @@ psql_completion(const char *text, int start, int end)
 	 * what is after them, case-insensitively.
 	 */
 #define HeadMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]))
+	(HEAD_INDEX(1) >=0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]))
 
 #define HeadMatches2(p1, p2) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]))
+	(HEAD_INDEX(2) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]))
 
 #define HeadMatches3(p1, p2, p3) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]) && \
-	 word_matches(p3, previous_words[previous_words_count - 3]))
+	(HEAD_INDEX(3) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]))
+
+#define HeadMatches4(p1, p2, p3, p4) \
+	(HEAD_INDEX(4) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]))
+
+#define HeadMatches5(p1, p2, p3, p4, p5) \
+	(HEAD_INDEX(5) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]))
 
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
@@ -1327,9 +1397,16 @@ psql_completion(const char *text, int start, int end)
 	else if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
+	/* 
+	 * If this is in CREATE SCHEMA, seek to the last CREATE and regard it as
+	 * current command to complete.
+	 */
+	else if (HeadMatches2("CREATE", "SCHEMA") &&
+			 SHIFT_TO_LAST1("CREATE") &&
+			 false) {} /* FALL THROUGH */
 /* CREATE */
 	/* complete with something you can create */
-	else if (TailMatches1("CREATE"))
+	else if (Matches1("CREATE"))
 		matches = completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
@@ -1342,7 +1419,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE */
 	else if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
+	/* Try ALTER TABLE after removing optional words IF EXISTS*/
+	else if (HeadMatches2("ALTER", "TABLE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("ALTER", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER something */
 	else if (Matches1("ALTER"))
@@ -1420,6 +1503,18 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
+	/* ALTER FOREIGN TABLE */
+	else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
+								   " UNION SELECT 'IF EXISTS'");
+
+	/* Try ALTER FOREIGN TABLE after removing optinal words IF EXISTS */
+	/* Complete for DROP together  */
+	else if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+
 	/* ALTER FOREIGN TABLE <name> */
 	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
@@ -1431,10 +1526,21 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_FOREIGN_TABLE);
 	}
 
+	/* ALTER FOREIGN TABLE xxx RENAME */
+	else if (Matches5("ALTER", "FOREIGN", "TABLE", MatchAny, "RENAME"))
+		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'TO'");
+
 	/* ALTER INDEX */
 	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
+	/* Try ALTER INDEX after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "INDEX") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("ALTER", "INDEX"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+
 	/* ALTER INDEX <name> */
 	else if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
@@ -1463,8 +1569,15 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER MATERIALIZED VIEW */
 	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
+	/* Try ALTER MATERIALIZED VIEW after removing optional words IF EXISTS */
+	else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("ALTER", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+
 	/* ALTER USER,ROLE <name> */
 	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
@@ -1515,8 +1628,23 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER DOMAIN <sth> DROP */
 	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
-	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	/* ALTER DOMAIN <sth> RENAME|VALIDATE CONSTRAINT */
+	else if (Matches5("ALTER", "DOMAIN", MatchAny, "RENAME|VALIDATE", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+	}
+	/* ALTER DOMAIN <sth> DROP CONSTRAINT */
+	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type
+							"UNION SELECT 'IF EXISTS'");
+	}
+	/* Try the same match after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
@@ -1531,8 +1659,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER DOMAIN <sth> SET */
 	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
-	/* ALTER SEQUENCE <name> */
-	else if (Matches3("ALTER", "SEQUENCE", MatchAny))
+	else if (Matches2("ALTER", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try ALTER SEQUENCE after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "SEQUENCE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1555,6 +1688,14 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER SYSTEM SET|RESET <name> */
 	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
+	/* ALTER VIEW */
+	else if (Matches2("ALTER", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   "UNION SELECT 'IF EXISTS'");
+	/*  Try ALTER VIEW after removing optional worlds IF EXISTS */
+	else if (HeadMatches2("ALTER", "VIEW") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false){} /* FALL THROUGH */
 	/* ALTER VIEW <name> */
 	else if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
@@ -1564,9 +1705,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
-	/* ALTER POLICY <name> */
+	/* ALTER POLICY */
 	else if (Matches2("ALTER", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies
+							"UNION SELECT 'IF EXISTS'");
+	/* Try ALTER POLICY after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "POLICY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	/* ALTER POLICY <name> ON */
 	else if (Matches3("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
@@ -1692,8 +1838,10 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
-	/* ALTER TABLE xxx RENAME yyy */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
+	/* ALTER [FOREIGN] TABLE xxx RENAME yyy */
+	else if ((HeadMatches2("ALTER", "TABLE") ||
+			  HeadMatches3("ALTER", "FOREIGN", "TABLE")) &&
+			 TailMatches2("RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
@@ -1705,13 +1853,36 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
-	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
+	 * If we have ALTER TABLE <sth> ALTER|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME|VALIDATE", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+	}
+	/*
+	 * If we have ALTER TABLE <sth> DROP CONSTRAINT,
+	 * provide list of constraints
+	 */
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table
+							"UNION SELECT 'IF EXISTS'");
+	}
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
@@ -1844,6 +2015,12 @@ psql_completion(const char *text, int start, int end)
 	/* complete ALTER TYPE <foo> ADD with actions */
 	else if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
+	else if (Matches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE"))
+		COMPLETE_WITH_LIST2("IF NOT EXISTS", "");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE") &&
+			 MidMatchAndRemove3(5, "IF", "NOT", "EXISTS") &&
+			 false) {} /* Nothing to do for now */
 	/* ALTER TYPE <foo> RENAME	*/
 	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "TO");
@@ -1990,6 +2167,12 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	else if (Matches2("CREATE", "EXTENSION"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions
+							" UNION SELECT 'IF NOT EXISTS'");
+	/* Try the same after removing optional words IF NOT EXISTS */
+	else if (HeadMatches2("CREATE", "EXTENSION") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
 	else if (Matches3("CREATE", "EXTENSION", MatchAny))
@@ -2005,6 +2188,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
+	/* CREATE FOREIGN TABLE */
+	else if (Matches3("CREATE", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
+			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
@@ -2013,23 +2204,39 @@ psql_completion(const char *text, int start, int end)
 	/* First off we complete CREATE UNIQUE with "INDEX" */
 	else if (TailMatches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
-	/* If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY",
+
+	/* Remove optional word UNIQUE from CREATE UNIQUE INDEX */
+	else if (HeadMatches3("CREATE", MatchAny, "INDEX") &&
+			 MidMatchAndRemove1(1, "UNIQUE") &&
+			 false) {} /* FALL THROUGH */
+
+	/* If we have CREATE INDEX, then add "ON", "CONCURRENTLY" or IF NOT EXISTS,
 	   and existing indexes */
-	else if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	else if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
-								   " UNION SELECT 'CONCURRENTLY'");
-	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	else if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
-			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-	/* Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing indexes */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+								   " UNION SELECT 'CONCURRENTLY'"
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and
+	 * existing indexes */
+	else if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF NOT EXISTS'"
 								   " UNION SELECT 'ON'");
-	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
-			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
+
+	/* Remove optional words "CONCURRENTLY",  "IF NOT EXISTS" */
+	else if (HeadMatches2("CREATE", "INDEX") &&
+			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
+	/* Complete CREATE INDEX [<name>] ON with a list of tables */
+	else if (Matches4("CREATE", "INDEX", MatchAny, "ON") ||
+			 Matches3("CREATE", "INDEX", "ON"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+
+	/* Complete CREATE INDEX <sth> with "ON" */
+	else if (Matches3("CREATE", "INDEX", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/*
@@ -2037,10 +2244,10 @@ psql_completion(const char *text, int start, int end)
 	 * should really be in parens)
 	 */
 	else if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-			 TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+			 TailMatches3("INDEX", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-			 TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
+	else if (Matches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
+			 Matches4("INDEX", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* same if you put in USING */
 	else if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
@@ -2101,27 +2308,50 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-/* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	else if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
-			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
+/* CREATE SEQUENCE, removing optional words TEMPORARY/TEMP */
+	else if (HeadMatches3("CREATE", MatchAny, "SEQUENCE") &&
+			 MidMatchAndRemove1(1, "TEMP|TEMPORARY") &&
+			 Matches3("CREATE", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	else if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
-		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
+	else if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
 	else if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
-/* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE SCHEMA <name> */
+	else if (Matches2("CREATE", "SCHEMA"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+							" UNION SELECT 'IF NOT EXISTS'");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches2("CREATE", "SCHEMA") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH*/
+
+/* CREATE TABLE  */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	else if (Matches2("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	else if (TailMatches2("CREATE", "UNLOGGED"))
+	else if (Matches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
+	/* Remove optional words here */
+	else if (HeadMatches3("CREATE", MatchAny, "TABLE") &&
+			 MidMatchAndRemove1(1, "TEMP|TEMPORARY|UNLOGGED") &&
+			 false) {} /* FALL THROUGH */
+
+	else if (Matches2("CREATE", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+								   " UNION SELECT 'IF NOT EXISTS'");
+
+	/* Remove optional words here */
+	else if (HeadMatches2("CREATE", "TABLE") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
@@ -2135,18 +2365,18 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
-/* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	else if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	else if (Matches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	else if (Matches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	else if (Matches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+	else if (Matches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
 	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
@@ -2203,9 +2433,16 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
-/* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE VIEW  */
+	else if (Matches2("CREATE", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches2("CREATE", "VIEW") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	/* Complete CREATE VIEW <name> with AS */
-	else if (TailMatches3("CREATE", "VIEW", MatchAny))
+	else if (Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
 	else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
@@ -2214,6 +2451,15 @@ psql_completion(const char *text, int start, int end)
 /* CREATE MATERIALIZED VIEW */
 	else if (Matches2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+	else if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Try the same after removing optional words IF NOT EXISTS. VIEW will be
+	 * completed afterwards */
+	else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
+			 Matches3("CREATE", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
 	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
@@ -2272,28 +2518,61 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
+	else if (Matches2("DROP", "AGGREGATE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates,
+								   " UNION SELECT 'IF EXISTS'");
+	else if (Matches2("DROP", "FUNCTION"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
 	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
+	else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 
 	/* DROP INDEX */
 	else if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF EXISTS'"
 								   " UNION SELECT 'CONCURRENTLY'");
 	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches3("DROP", "INDEX", MatchAny))
-		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	else if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */
+	else if (HeadMatches2("DROP", "INDEX") &&
+			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
 	else if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+	else if (Matches2("DROP", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Remove optional words IF EXISTS  */
+	else if (HeadMatches2("DROP", "VIEW") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
 	/* DROP OWNED BY */
@@ -2306,7 +2585,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (Matches3("DROP", "TRIGGER", MatchAny))
+	else if (Matches2("DROP", "TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_triggers
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "TRIGGER") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
@@ -2320,15 +2605,27 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	else if (Matches3("DROP", "EVENT", "TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers
+							" UNION SELECT 'IF EXISTS'");
+	/* Trye the same after removing optional words IF EXISTS */
+	else if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
-	/* DROP POLICY <name>  */
+	/* DROP POLICY */
 	else if (Matches2("DROP", "POLICY"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "POLICY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
-	/* DROP POLICY <name> ON */
+	/* DROP POLICY <name> */
 	else if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	/* DROP POLICY <name> ON <table> */
+	/* DROP POLICY <name> ON */
 	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
@@ -2336,7 +2633,13 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* DROP RULE */
-	else if (Matches3("DROP", "RULE", MatchAny))
+	else if (Matches2("DROP", "RULE"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_rules
+							"UNION SELECT 'IF EXISTS'");
+	/* DROP RULE <name>, after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "RULE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
@@ -2346,6 +2649,46 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
+	/* IF EXISTS processing for DROP everything else */
+	else if (Matches2("DROP",
+					  "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|"
+					  "GROUP|LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|"
+					  "SERVER|TABLE|TABLESPACE|TYPE|USER") ||
+			 Matches4("DROP", "TEXT", "SEARCH",
+					  "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE"))
+
+	{
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
+		char *addition = " UNION SELECT 'IF EXISTS'";
+
+		if (ent)
+		{
+			if (ent->query)
+			{
+				char *buf = pg_malloc(strlen(ent->query) +
+									  strlen(addition) + 1);
+				strcpy(buf, ent->query);
+				strcat(buf, addition);
+				COMPLETE_WITH_QUERY(buf);
+				free(buf);
+			}
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery,
+										   " UNION SELECT 'IF EXISTS'");
+		}
+	}
+	/* Remove optional IF EXISTS from DROP */
+	else if (HeadMatches2("DROP",
+						  "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|GROUP|"
+						  "LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|SERVER|"
+						  "TABLE|TABLESPACE|TYPE|USER") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+	else if (HeadMatches4("DROP", "TEXT", "SEARCH",
+						  "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
 /* EXECUTE */
 	else if (Matches1("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
@@ -2392,8 +2735,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /* FOREIGN TABLE */
-	else if (TailMatches2("FOREIGN", "TABLE") &&
-			 !TailMatches3("CREATE", MatchAny, MatchAny))
+	else if (TailMatches2("FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* FOREIGN SERVER */
@@ -3058,19 +3400,14 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else
 	{
-		int			i;
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
 
-		for (i = 0; words_after_create[i].name; i++)
+		if (ent)
 		{
-			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
-			{
-				if (words_after_create[i].query)
-					COMPLETE_WITH_QUERY(words_after_create[i].query);
-				else if (words_after_create[i].squery)
-					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
-											   NULL);
-				break;
-			}
+			if (ent->query)
+				COMPLETE_WITH_QUERY(ent->query);
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, NULL);
 		}
 	}
 
@@ -3587,6 +3924,18 @@ complete_from_files(const char *text, int state)
 
 /* HELPER FUNCTIONS */
 
+/*
+ * Return the index (reverse to the index of previous_words) of the tailmost
+ * (topmost in the array) appearance of w.
+ */
+static int
+find_last_index_of(char *w, char **previous_words, int len)
+{
+	int i;
+
+	for (i = 0 ; i < len && !word_matches(w, previous_words[i]) ; i++);
+	return i < len ? (len - i - 1) : 0;
+}
 
 /*
  * Make a pg_strdup copy of s and convert the case according to
@@ -3833,6 +4182,24 @@ get_guctype(const char *varname)
 	return guctype;
 }
 
+/*
+ * Finds the entry in words_after_create[] that matches the word.
+ * NULL if not found.
+ */
+static const pgsql_thing_t *
+find_thing_entry(char *word)
+{
+	int			i;
+
+	for (i = 0; words_after_create[i].name; i++)
+	{
+		if (pg_strcasecmp(word, words_after_create[i].name) == 0)
+			return words_after_create + i;
+	}
+
+	return NULL;
+}
+
 #ifdef NOT_USED
 
 /*
-- 
1.8.3.1

#2Artur Zakirov
a.zakirov@postgrespro.ru
In reply to: Kyotaro HORIGUCHI (#1)
Re: IF (NOT) EXISTS in psql-completion

On 05.02.2016 11:09, Kyotaro HORIGUCHI wrote:

Hello,

I considered how to make tab-completion robust for syntactical
noises, in other words, optional words in syntax. Typically "IF
(NOT) EXISTS", UNIQUE and TEMPORARY are words that don't affect
further completion. However, the current delimit-matching
mechanism is not so capable (or is complexty-prone) to live with
such noises. I have proposed to use regular expressions or
simplified one for the robustness but it was too complex to be
applied.

This is another answer for the problem. Removal of such words
on-the-fly makes further matching more robust.

Next, currently some CREATE xxx subsyntaxes of CREATE SCHEMA are
matched using TailMatching but it makes difficult the
options-removal operations, which needs forward matching.

So I introduced two things to resolve them by this patch.

I did some tests with your patch. But I am not confident in tab-complete.c.

And I have some notes:

1 - I execute git apply command and get the following warning:

../0001-Suggest-IF-NOT-EXISTS-for-tab-completion-of-psql.patch:302:
trailing whitespace.
/*
warning: 1 line adds whitespace errors.

This is because of superfluous whitespace I think.

2 - In psql I write "create table if" and press <TAB>. psql adds the
following:

create table IF NOT EXISTS

I think psql should continue with lower case if user wrote query with
loser case text:

create table if not exists

3 - Same with "IF EXISTS". If a write "alter view if" and press <TAB>
psql writes:

alter view IF EXISTS

--
Artur Zakirov
Postgres Professional: http://www.postgrespro.com
Russian Postgres Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Artur Zakirov (#2)
Re: IF (NOT) EXISTS in psql-completion

Hello, thank you for the comments.

At Mon, 15 Feb 2016 15:43:57 +0300, Artur Zakirov <a.zakirov@postgrespro.ru> wrote in <56C1C80D.7020101@postgrespro.ru>

On 05.02.2016 11:09, Kyotaro HORIGUCHI wrote:

Hello,

I considered how to make tab-completion robust for syntactical
noises, in other words, optional words in syntax. Typically "IF
(NOT) EXISTS", UNIQUE and TEMPORARY are words that don't affect
further completion. However, the current delimit-matching
mechanism is not so capable (or is complexty-prone) to live with
such noises. I have proposed to use regular expressions or
simplified one for the robustness but it was too complex to be
applied.

This is another answer for the problem. Removal of such words
on-the-fly makes further matching more robust.

Next, currently some CREATE xxx subsyntaxes of CREATE SCHEMA are
matched using TailMatching but it makes difficult the
options-removal operations, which needs forward matching.

So I introduced two things to resolve them by this patch.

I did some tests with your patch. But I am not confident in
tab-complete.c.

And I have some notes:

1 - I execute git apply command and get the following warning:

../0001-Suggest-IF-NOT-EXISTS-for-tab-completion-of-psql.patch:302:
trailing whitespace.
/*
warning: 1 line adds whitespace errors.

This is because of superfluous whitespace I think.

Oops. I'll remove it.

2 - In psql I write "create table if" and press <TAB>. psql adds the
following:

create table IF NOT EXISTS

I think psql should continue with lower case if user wrote query with
loser case text:

Good catch! Hmm. COMPLETE_WITH_SCHEMA_QUERY() does it. For
example, the following existing completion behaves in the same
way.

"drop index c" =<tab>=> "drop index CONCURRENTLY"

The results of schema queries should be treated in case-sensitive
way so the additional keywords by 'UNION' are also treated
case-sensitively.

COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
" UNION SELECT 'CONCURRENTLY'");

Fixing this is another problem. So I'd like to leave this alone
here.

create table if not exists

3 - Same with "IF EXISTS". If a write "alter view if" and press <TAB>
psql writes:

alter view IF EXISTS

regards,

--
堀口恭太郎

日本電信電話株式会社 NTTオープンソースソフトウェアセンタ
Phone: 03-5860-5115 / Fax: 03-5463-5490

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#3)
1 attachment(s)
Re: IF (NOT) EXISTS in psql-completion

Hello, this is the new version of this patch.

At Fri, 26 Feb 2016 13:17:26 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <20160226.131726.54224488.horiguchi.kyotaro@lab.ntt.co.jp>

Hello, thank you for the comments.

At Mon, 15 Feb 2016 15:43:57 +0300, Artur Zakirov <a.zakirov@postgrespro.ru> wrote in <56C1C80D.7020101@postgrespro.ru>

I did some tests with your patch. But I am not confident in
tab-complete.c.

And I have some notes:

1 - I execute git apply command and get the following warning:

../0001-Suggest-IF-NOT-EXISTS-for-tab-completion-of-psql.patch:302:
trailing whitespace.
/*
warning: 1 line adds whitespace errors.

This is because of superfluous whitespace I think.

Oops. I'll remove it.

2 - In psql I write "create table if" and press <TAB>. psql adds the
following:

create table IF NOT EXISTS

I think psql should continue with lower case if user wrote query with
loser case text:

Good catch! Hmm. COMPLETE_WITH_SCHEMA_QUERY() does it. For
example, the following existing completion behaves in the same
way.

"drop index c" =<tab>=> "drop index CONCURRENTLY"

The results of schema queries should be treated in case-sensitive
way so the additional keywords by 'UNION' are also treated
case-sensitively.

COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
" UNION SELECT 'CONCURRENTLY'");

Fixing this is another problem. So I'd like to leave this alone
here.

create table if not exists

3 - Same with "IF EXISTS". If a write "alter view if" and press <TAB>
psql writes:

alter view IF EXISTS

Finally, the only thing done in this new patch is removing one
dangling space.

reagrds,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

0001-Suggest-IF-NOT-EXISTS-for-tab-completion-of-psql_v2.patchtext/x-patch; charset=us-asciiDownload
From f9610a87d73629ac3fcc25fcf8e667c5ca3e921d Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 5 Feb 2016 16:50:35 +0900
Subject: [PATCH] Suggest IF (NOT) EXISTS for tab-completion of psql

This patch lets psql to suggest "IF (NOT) EXISTS". Addition to that,
since this patch introduces some mechanism for syntactical robustness,
it allows psql completion to omit some optional part on matching.
---
 src/bin/psql/tab-complete.c | 559 ++++++++++++++++++++++++++++++++++++--------
 1 file changed, 463 insertions(+), 96 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5f27120..d3eab8c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -656,6 +656,10 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "   FROM pg_catalog.pg_roles "\
 "  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"
 
+#define Query_for_list_of_rules \
+"SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules "\
+" WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"
+
 #define Query_for_list_of_grant_roles \
 " SELECT pg_catalog.quote_ident(rolname) "\
 "   FROM pg_catalog.pg_roles "\
@@ -763,6 +767,11 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "SELECT pg_catalog.quote_ident(tmplname) FROM pg_catalog.pg_ts_template "\
 " WHERE substring(pg_catalog.quote_ident(tmplname),1,%d)='%s'"
 
+#define Query_for_list_of_triggers \
+"SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger "\
+" WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s' AND "\
+"       NOT tgisinternal"
+
 #define Query_for_list_of_fdws \
 " SELECT pg_catalog.quote_ident(fdwname) "\
 "   FROM pg_catalog.pg_foreign_data_wrapper "\
@@ -906,7 +915,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
 	{"POLICY", NULL, NULL},
 	{"ROLE", Query_for_list_of_roles},
-	{"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"},
+	{"RULE", Query_for_list_of_rules},
 	{"SCHEMA", Query_for_list_of_schemas},
 	{"SEQUENCE", NULL, &Query_for_list_of_sequences},
 	{"SERVER", Query_for_list_of_servers},
@@ -915,7 +924,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"TEMP", NULL, NULL, THING_NO_DROP},		/* for CREATE TEMP TABLE ... */
 	{"TEMPLATE", Query_for_list_of_ts_templates, NULL, THING_NO_SHOW},
 	{"TEXT SEARCH", NULL, NULL},
-	{"TRIGGER", "SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s' AND NOT tgisinternal"},
+	{"TRIGGER", Query_for_list_of_triggers},
 	{"TYPE", NULL, &Query_for_list_of_datatypes},
 	{"UNIQUE", NULL, NULL, THING_NO_DROP},		/* for CREATE UNIQUE INDEX ... */
 	{"UNLOGGED", NULL, NULL, THING_NO_DROP},	/* for CREATE UNLOGGED TABLE
@@ -944,6 +953,7 @@ static char **complete_from_variables(const char *text,
 					const char *prefix, const char *suffix, bool need_value);
 static char *complete_from_files(const char *text, int state);
 
+static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
@@ -952,6 +962,7 @@ static char **get_previous_words(int point, char **buffer, int *nwords);
 
 static char *get_guctype(const char *varname);
 
+static const pgsql_thing_t *find_thing_entry(char *word);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -1107,6 +1118,9 @@ psql_completion(const char *text, int start, int end)
 	/* The number of words found on the input line. */
 	int			previous_words_count;
 
+	/* The number of prefixing words to be ignored */
+	int			head_shift = 0;
+
 	/*
 	 * For compactness, we use these macros to reference previous_words[].
 	 * Caution: do not access a previous_words[] entry without having checked
@@ -1124,31 +1138,72 @@ psql_completion(const char *text, int start, int end)
 #define prev8_wd  (previous_words[7])
 #define prev9_wd  (previous_words[8])
 
+	/* Move the position of the beginning word for matching macros.  */
+#define HEADSHIFT(n) \
+	(head_shift += n, true)
+
+	/* Return the number of stored words counting head shift */
+#define WORD_COUNT() (previous_words_count - head_shift)
+
+	/* Return the true index in previous_words for index from the beginning */
+#define HEAD_INDEX(n) \
+	(previous_words_count - head_shift - (n))
+
+	/*
+	 * remove n words from current shifted position, see MidMatchAndRevmove2
+	 * for the reason for the return value
+	 */
+#define COLLAPSE(n) \
+	(memmove(previous_words + HEAD_INDEX(n), previous_words + HEAD_INDEX(0), \
+			 sizeof(char *) * head_shift),								\
+	 previous_words_count -= (n), false)
+
+	/*
+	 * Find the position the specified word occurs last and shift to there.
+	 * This is used to ignore the words before there.
+	 */
+#define SHIFT_TO_LAST1(p1) \
+	(HEADSHIFT(find_last_index_of(p1, previous_words, previous_words_count)), \
+	 true)
+
+	/*
+	 * Remove the specified words if they match from the sth word in
+	 * previous_words.
+	 */
+#define MidMatchAndRemove1(s, p1) \
+	((HEADSHIFT(s) && HeadMatches1(p1) && COLLAPSE(1)) || HEADSHIFT(-s))
+
+#define MidMatchAndRemove2(s, p1, p2) \
+	((HEADSHIFT(s) && HeadMatches2(p1, p2) && COLLAPSE(2)) || HEADSHIFT(-s))
+
+#define MidMatchAndRemove3(s, p1, p2, p3)									\
+	((HEADSHIFT(s) && HeadMatches3(p1, p2, p3) && COLLAPSE(3)) || HEADSHIFT(-s))
+
 	/* Macros for matching the last N words before point, case-insensitively. */
 #define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches(p1, prev_wd))
 
 #define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd))
 
 #define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
+	(WORD_COUNT() >= 3 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd))
 
 #define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
+	(WORD_COUNT() >= 4 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
 	 word_matches(p4, prev4_wd))
 
 #define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
+	(WORD_COUNT() >= 5 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1156,7 +1211,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p5, prev5_wd))
 
 #define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
+	(WORD_COUNT() >= 6 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1165,7 +1220,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p6, prev6_wd))
 
 #define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
+	(WORD_COUNT() >= 7 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1175,7 +1230,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p7, prev7_wd))
 
 #define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
+	(WORD_COUNT() >= 8 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1186,7 +1241,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p8, prev8_wd))
 
 #define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
+	(WORD_COUNT() >= 9 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1199,43 +1254,43 @@ psql_completion(const char *text, int start, int end)
 
 	/* Macros for matching the last N words before point, case-sensitively. */
 #define TailMatchesCS1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches_cs(p1, prev_wd))
 #define TailMatchesCS2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches_cs(p1, prev_wd) && \
 	 word_matches_cs(p2, prev2_wd))
 
 	/*
-	 * Macros for matching N words beginning at the start of the line,
+	 * Macros for matching N words exactly to the line,
 	 * case-insensitively.
 	 */
 #define Matches1(p1) \
-	(previous_words_count == 1 && \
+	(WORD_COUNT() == 1 && \
 	 TailMatches1(p1))
 #define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
+	(WORD_COUNT() == 2 && \
 	 TailMatches2(p1, p2))
 #define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
+	(WORD_COUNT() == 3 && \
 	 TailMatches3(p1, p2, p3))
 #define Matches4(p1, p2, p3, p4) \
-	(previous_words_count == 4 && \
+	(WORD_COUNT() == 4 && \
 	 TailMatches4(p1, p2, p3, p4))
 #define Matches5(p1, p2, p3, p4, p5) \
-	(previous_words_count == 5 && \
+	(WORD_COUNT() == 5 && \
 	 TailMatches5(p1, p2, p3, p4, p5))
 #define Matches6(p1, p2, p3, p4, p5, p6) \
-	(previous_words_count == 6 && \
+	(WORD_COUNT() == 6 && \
 	 TailMatches6(p1, p2, p3, p4, p5, p6))
 #define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(previous_words_count == 7 && \
+	(WORD_COUNT() == 7 && \
 	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
 #define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(previous_words_count == 8 && \
+	(WORD_COUNT() == 8 && \
 	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
 #define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(previous_words_count == 9 && \
+	(WORD_COUNT() == 9 && \
 	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
 
 	/*
@@ -1243,19 +1298,34 @@ psql_completion(const char *text, int start, int end)
 	 * what is after them, case-insensitively.
 	 */
 #define HeadMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]))
+	(HEAD_INDEX(1) >=0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]))
 
 #define HeadMatches2(p1, p2) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]))
+	(HEAD_INDEX(2) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]))
 
 #define HeadMatches3(p1, p2, p3) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]) && \
-	 word_matches(p3, previous_words[previous_words_count - 3]))
+	(HEAD_INDEX(3) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]))
+
+#define HeadMatches4(p1, p2, p3, p4) \
+	(HEAD_INDEX(4) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]))
+
+#define HeadMatches5(p1, p2, p3, p4, p5) \
+	(HEAD_INDEX(5) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]))
 
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
@@ -1327,9 +1397,16 @@ psql_completion(const char *text, int start, int end)
 	else if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
+	/*
+	 * If this is in CREATE SCHEMA, seek to the last CREATE and regard it as
+	 * current command to complete.
+	 */
+	else if (HeadMatches2("CREATE", "SCHEMA") &&
+			 SHIFT_TO_LAST1("CREATE") &&
+			 false) {} /* FALL THROUGH */
 /* CREATE */
 	/* complete with something you can create */
-	else if (TailMatches1("CREATE"))
+	else if (Matches1("CREATE"))
 		matches = completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
@@ -1342,7 +1419,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE */
 	else if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
+	/* Try ALTER TABLE after removing optional words IF EXISTS*/
+	else if (HeadMatches2("ALTER", "TABLE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("ALTER", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER something */
 	else if (Matches1("ALTER"))
@@ -1420,6 +1503,18 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
+	/* ALTER FOREIGN TABLE */
+	else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
+								   " UNION SELECT 'IF EXISTS'");
+
+	/* Try ALTER FOREIGN TABLE after removing optinal words IF EXISTS */
+	/* Complete for DROP together  */
+	else if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+
 	/* ALTER FOREIGN TABLE <name> */
 	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
@@ -1431,10 +1526,21 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_FOREIGN_TABLE);
 	}
 
+	/* ALTER FOREIGN TABLE xxx RENAME */
+	else if (Matches5("ALTER", "FOREIGN", "TABLE", MatchAny, "RENAME"))
+		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'TO'");
+
 	/* ALTER INDEX */
 	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
+	/* Try ALTER INDEX after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "INDEX") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("ALTER", "INDEX"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+
 	/* ALTER INDEX <name> */
 	else if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
@@ -1463,8 +1569,15 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER MATERIALIZED VIEW */
 	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
+	/* Try ALTER MATERIALIZED VIEW after removing optional words IF EXISTS */
+	else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("ALTER", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+
 	/* ALTER USER,ROLE <name> */
 	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
@@ -1515,8 +1628,23 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER DOMAIN <sth> DROP */
 	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
-	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	/* ALTER DOMAIN <sth> RENAME|VALIDATE CONSTRAINT */
+	else if (Matches5("ALTER", "DOMAIN", MatchAny, "RENAME|VALIDATE", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+	}
+	/* ALTER DOMAIN <sth> DROP CONSTRAINT */
+	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type
+							"UNION SELECT 'IF EXISTS'");
+	}
+	/* Try the same match after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
@@ -1531,8 +1659,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER DOMAIN <sth> SET */
 	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
-	/* ALTER SEQUENCE <name> */
-	else if (Matches3("ALTER", "SEQUENCE", MatchAny))
+	else if (Matches2("ALTER", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try ALTER SEQUENCE after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "SEQUENCE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1555,6 +1688,14 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER SYSTEM SET|RESET <name> */
 	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
+	/* ALTER VIEW */
+	else if (Matches2("ALTER", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   "UNION SELECT 'IF EXISTS'");
+	/*  Try ALTER VIEW after removing optional worlds IF EXISTS */
+	else if (HeadMatches2("ALTER", "VIEW") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false){} /* FALL THROUGH */
 	/* ALTER VIEW <name> */
 	else if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
@@ -1564,9 +1705,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
-	/* ALTER POLICY <name> */
+	/* ALTER POLICY */
 	else if (Matches2("ALTER", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies
+							"UNION SELECT 'IF EXISTS'");
+	/* Try ALTER POLICY after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "POLICY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	/* ALTER POLICY <name> ON */
 	else if (Matches3("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
@@ -1692,8 +1838,10 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
-	/* ALTER TABLE xxx RENAME yyy */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
+	/* ALTER [FOREIGN] TABLE xxx RENAME yyy */
+	else if ((HeadMatches2("ALTER", "TABLE") ||
+			  HeadMatches3("ALTER", "FOREIGN", "TABLE")) &&
+			 TailMatches2("RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
@@ -1705,13 +1853,36 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
-	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
+	 * If we have ALTER TABLE <sth> ALTER|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME|VALIDATE", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+	}
+	/*
+	 * If we have ALTER TABLE <sth> DROP CONSTRAINT,
+	 * provide list of constraints
+	 */
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table
+							"UNION SELECT 'IF EXISTS'");
+	}
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
@@ -1844,6 +2015,12 @@ psql_completion(const char *text, int start, int end)
 	/* complete ALTER TYPE <foo> ADD with actions */
 	else if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
+	else if (Matches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE"))
+		COMPLETE_WITH_LIST2("IF NOT EXISTS", "");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE") &&
+			 MidMatchAndRemove3(5, "IF", "NOT", "EXISTS") &&
+			 false) {} /* Nothing to do for now */
 	/* ALTER TYPE <foo> RENAME	*/
 	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "TO");
@@ -1990,6 +2167,12 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	else if (Matches2("CREATE", "EXTENSION"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions
+							" UNION SELECT 'IF NOT EXISTS'");
+	/* Try the same after removing optional words IF NOT EXISTS */
+	else if (HeadMatches2("CREATE", "EXTENSION") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
 	else if (Matches3("CREATE", "EXTENSION", MatchAny))
@@ -2005,6 +2188,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
+	/* CREATE FOREIGN TABLE */
+	else if (Matches3("CREATE", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
+			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
@@ -2013,23 +2204,39 @@ psql_completion(const char *text, int start, int end)
 	/* First off we complete CREATE UNIQUE with "INDEX" */
 	else if (TailMatches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
-	/* If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY",
+
+	/* Remove optional word UNIQUE from CREATE UNIQUE INDEX */
+	else if (HeadMatches3("CREATE", MatchAny, "INDEX") &&
+			 MidMatchAndRemove1(1, "UNIQUE") &&
+			 false) {} /* FALL THROUGH */
+
+	/* If we have CREATE INDEX, then add "ON", "CONCURRENTLY" or IF NOT EXISTS,
 	   and existing indexes */
-	else if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	else if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
-								   " UNION SELECT 'CONCURRENTLY'");
-	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	else if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
-			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-	/* Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing indexes */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+								   " UNION SELECT 'CONCURRENTLY'"
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and
+	 * existing indexes */
+	else if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF NOT EXISTS'"
 								   " UNION SELECT 'ON'");
-	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
-			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
+
+	/* Remove optional words "CONCURRENTLY",  "IF NOT EXISTS" */
+	else if (HeadMatches2("CREATE", "INDEX") &&
+			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
+	/* Complete CREATE INDEX [<name>] ON with a list of tables */
+	else if (Matches4("CREATE", "INDEX", MatchAny, "ON") ||
+			 Matches3("CREATE", "INDEX", "ON"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+
+	/* Complete CREATE INDEX <sth> with "ON" */
+	else if (Matches3("CREATE", "INDEX", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/*
@@ -2037,10 +2244,10 @@ psql_completion(const char *text, int start, int end)
 	 * should really be in parens)
 	 */
 	else if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-			 TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+			 TailMatches3("INDEX", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-			 TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
+	else if (Matches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
+			 Matches4("INDEX", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* same if you put in USING */
 	else if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
@@ -2101,27 +2308,50 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-/* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	else if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
-			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
+/* CREATE SEQUENCE, removing optional words TEMPORARY/TEMP */
+	else if (HeadMatches3("CREATE", MatchAny, "SEQUENCE") &&
+			 MidMatchAndRemove1(1, "TEMP|TEMPORARY") &&
+			 Matches3("CREATE", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	else if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
-		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
+	else if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
 	else if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
-/* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE SCHEMA <name> */
+	else if (Matches2("CREATE", "SCHEMA"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+							" UNION SELECT 'IF NOT EXISTS'");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches2("CREATE", "SCHEMA") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH*/
+
+/* CREATE TABLE  */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	else if (Matches2("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	else if (TailMatches2("CREATE", "UNLOGGED"))
+	else if (Matches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
+	/* Remove optional words here */
+	else if (HeadMatches3("CREATE", MatchAny, "TABLE") &&
+			 MidMatchAndRemove1(1, "TEMP|TEMPORARY|UNLOGGED") &&
+			 false) {} /* FALL THROUGH */
+
+	else if (Matches2("CREATE", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+								   " UNION SELECT 'IF NOT EXISTS'");
+
+	/* Remove optional words here */
+	else if (HeadMatches2("CREATE", "TABLE") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
@@ -2135,18 +2365,18 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
-/* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	else if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	else if (Matches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	else if (Matches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	else if (Matches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+	else if (Matches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
 	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
@@ -2203,9 +2433,16 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
-/* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE VIEW  */
+	else if (Matches2("CREATE", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches2("CREATE", "VIEW") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	/* Complete CREATE VIEW <name> with AS */
-	else if (TailMatches3("CREATE", "VIEW", MatchAny))
+	else if (Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
 	else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
@@ -2214,6 +2451,15 @@ psql_completion(const char *text, int start, int end)
 /* CREATE MATERIALIZED VIEW */
 	else if (Matches2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+	else if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Try the same after removing optional words IF NOT EXISTS. VIEW will be
+	 * completed afterwards */
+	else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
+			 Matches3("CREATE", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
 	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
@@ -2272,28 +2518,61 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
+	else if (Matches2("DROP", "AGGREGATE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates,
+								   " UNION SELECT 'IF EXISTS'");
+	else if (Matches2("DROP", "FUNCTION"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
 	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
+	else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 
 	/* DROP INDEX */
 	else if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF EXISTS'"
 								   " UNION SELECT 'CONCURRENTLY'");
 	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches3("DROP", "INDEX", MatchAny))
-		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	else if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */
+	else if (HeadMatches2("DROP", "INDEX") &&
+			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
 	else if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+	else if (Matches2("DROP", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Remove optional words IF EXISTS  */
+	else if (HeadMatches2("DROP", "VIEW") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
 	/* DROP OWNED BY */
@@ -2306,7 +2585,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (Matches3("DROP", "TRIGGER", MatchAny))
+	else if (Matches2("DROP", "TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_triggers
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "TRIGGER") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
@@ -2320,15 +2605,27 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	else if (Matches3("DROP", "EVENT", "TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers
+							" UNION SELECT 'IF EXISTS'");
+	/* Trye the same after removing optional words IF EXISTS */
+	else if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
-	/* DROP POLICY <name>  */
+	/* DROP POLICY */
 	else if (Matches2("DROP", "POLICY"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "POLICY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
-	/* DROP POLICY <name> ON */
+	/* DROP POLICY <name> */
 	else if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	/* DROP POLICY <name> ON <table> */
+	/* DROP POLICY <name> ON */
 	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
@@ -2336,7 +2633,13 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* DROP RULE */
-	else if (Matches3("DROP", "RULE", MatchAny))
+	else if (Matches2("DROP", "RULE"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_rules
+							"UNION SELECT 'IF EXISTS'");
+	/* DROP RULE <name>, after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "RULE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
@@ -2346,6 +2649,46 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
+	/* IF EXISTS processing for DROP everything else */
+	else if (Matches2("DROP",
+					  "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|"
+					  "GROUP|LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|"
+					  "SERVER|TABLE|TABLESPACE|TYPE|USER") ||
+			 Matches4("DROP", "TEXT", "SEARCH",
+					  "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE"))
+
+	{
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
+		char *addition = " UNION SELECT 'IF EXISTS'";
+
+		if (ent)
+		{
+			if (ent->query)
+			{
+				char *buf = pg_malloc(strlen(ent->query) +
+									  strlen(addition) + 1);
+				strcpy(buf, ent->query);
+				strcat(buf, addition);
+				COMPLETE_WITH_QUERY(buf);
+				free(buf);
+			}
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery,
+										   " UNION SELECT 'IF EXISTS'");
+		}
+	}
+	/* Remove optional IF EXISTS from DROP */
+	else if (HeadMatches2("DROP",
+						  "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|GROUP|"
+						  "LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|SERVER|"
+						  "TABLE|TABLESPACE|TYPE|USER") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+	else if (HeadMatches4("DROP", "TEXT", "SEARCH",
+						  "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
 /* EXECUTE */
 	else if (Matches1("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
@@ -2392,8 +2735,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /* FOREIGN TABLE */
-	else if (TailMatches2("FOREIGN", "TABLE") &&
-			 !TailMatches3("CREATE", MatchAny, MatchAny))
+	else if (TailMatches2("FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* FOREIGN SERVER */
@@ -3058,19 +3400,14 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else
 	{
-		int			i;
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
 
-		for (i = 0; words_after_create[i].name; i++)
+		if (ent)
 		{
-			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
-			{
-				if (words_after_create[i].query)
-					COMPLETE_WITH_QUERY(words_after_create[i].query);
-				else if (words_after_create[i].squery)
-					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
-											   NULL);
-				break;
-			}
+			if (ent->query)
+				COMPLETE_WITH_QUERY(ent->query);
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, NULL);
 		}
 	}
 
@@ -3587,6 +3924,18 @@ complete_from_files(const char *text, int state)
 
 /* HELPER FUNCTIONS */
 
+/*
+ * Return the index (reverse to the index of previous_words) of the tailmost
+ * (topmost in the array) appearance of w.
+ */
+static int
+find_last_index_of(char *w, char **previous_words, int len)
+{
+	int i;
+
+	for (i = 0 ; i < len && !word_matches(w, previous_words[i]) ; i++);
+	return i < len ? (len - i - 1) : 0;
+}
 
 /*
  * Make a pg_strdup copy of s and convert the case according to
@@ -3833,6 +4182,24 @@ get_guctype(const char *varname)
 	return guctype;
 }
 
+/*
+ * Finds the entry in words_after_create[] that matches the word.
+ * NULL if not found.
+ */
+static const pgsql_thing_t *
+find_thing_entry(char *word)
+{
+	int			i;
+
+	for (i = 0; words_after_create[i].name; i++)
+	{
+		if (pg_strcasecmp(word, words_after_create[i].name) == 0)
+			return words_after_create + i;
+	}
+
+	return NULL;
+}
+
 #ifdef NOT_USED
 
 /*
-- 
1.8.3.1

#5Robert Haas
robertmhaas@gmail.com
In reply to: Kyotaro HORIGUCHI (#4)
Re: IF (NOT) EXISTS in psql-completion

On Fri, Feb 26, 2016 at 2:37 AM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

Hello, this is the new version of this patch.

The CommitFest entry for this patch is messed up. It shows no author,
even though I'm pretty sure that a patch has to have one by
definition.

https://commitfest.postgresql.org/9/518/

Also, this patch was listed as Waiting on Author, but it's been
updated since it was last reviewed, so I switched it back to Needs
Review. Can someone please review it and, if appropriate, mark it
Ready for Committer?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6David Steele
david@pgmasters.net
In reply to: Robert Haas (#5)
Re: IF (NOT) EXISTS in psql-completion

On 3/15/16 1:42 PM, Robert Haas wrote:

On Fri, Feb 26, 2016 at 2:37 AM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

Hello, this is the new version of this patch.

The CommitFest entry for this patch is messed up. It shows no author,
even though I'm pretty sure that a patch has to have one by
definition.

https://commitfest.postgresql.org/9/518/

Also, this patch was listed as Waiting on Author, but it's been
updated since it was last reviewed, so I switched it back to Needs
Review. Can someone please review it and, if appropriate, mark it
Ready for Committer?

Author has been set to Kyotaro Horiguchi.

--
-David
david@pgmasters.net

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Peter Eisentraut
peter_e@gmx.net
In reply to: Kyotaro HORIGUCHI (#1)
Re: IF (NOT) EXISTS in psql-completion

On 2/5/16 3:09 AM, Kyotaro HORIGUCHI wrote:

I considered how to make tab-completion robust for syntactical
noises, in other words, optional words in syntax. Typically "IF
(NOT) EXISTS", UNIQUE and TEMPORARY are words that don't affect
further completion.

To repeat the question I raised in the previous commit fest about tab
completion: Why do you want tab completion for IF NOT EXISTS? When you
tab complete, the completion mechanism will show you whether the item in
question exists. What is the use case?

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Peter Eisentraut (#7)
Re: IF (NOT) EXISTS in psql-completion

Hello,

# It seems that I have been forgotten in the recepient list..

At Tue, 15 Mar 2016 22:09:59 -0400, Peter Eisentraut <peter_e@gmx.net> wrote in <56E8C077.2000903@gmx.net>

On 2/5/16 3:09 AM, Kyotaro HORIGUCHI wrote:

I considered how to make tab-completion robust for syntactical
noises, in other words, optional words in syntax. Typically "IF
(NOT) EXISTS", UNIQUE and TEMPORARY are words that don't affect
further completion.

To repeat the question I raised in the previous commit fest about tab
completion: Why do you want tab completion for IF NOT EXISTS? When you
tab complete, the completion mechanism will show you whether the item in
question exists. What is the use case?

Ah, I think I understand you question. It's not about IF EXISTS,
but only IF NOT EXSTS. It is needed when repeated execution of
the same SQL statement will be done using command line
history. Such stocks of commands in history is often
convenient. And sometimes I rely on psql-completion to write a
SQL script. The completions for such words seemingly useless on
instant-execution will be handy to do that.

Another thing I want to do by this patch is that we can get
completion even after such optional words. I have been annoyed
many times by this. Some of them, such as UNIQUE, TEMPORARY and
CONCURRENTLY are treated but they just doubles the matching
condition expressions.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: David Steele (#6)
Re: IF (NOT) EXISTS in psql-completion

Mmm. Have I broken the entry?

At Tue, 15 Mar 2016 13:55:24 -0400, David Steele <david@pgmasters.net> wrote in <56E84C8C.7060902@pgmasters.net>

On 3/15/16 1:42 PM, Robert Haas wrote:

On Fri, Feb 26, 2016 at 2:37 AM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

Hello, this is the new version of this patch.

The CommitFest entry for this patch is messed up. It shows no author,
even though I'm pretty sure that a patch has to have one by
definition.

https://commitfest.postgresql.org/9/518/

Also, this patch was listed as Waiting on Author, but it's been
updated since it was last reviewed, so I switched it back to Needs
Review. Can someone please review it and, if appropriate, mark it
Ready for Committer?

Author has been set to Kyotaro Horiguchi.

Thank you for repairing it.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Michael Paquier
michael.paquier@gmail.com
In reply to: Robert Haas (#5)
Re: IF (NOT) EXISTS in psql-completion

On Wed, Mar 16, 2016 at 2:42 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Feb 26, 2016 at 2:37 AM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

Hello, this is the new version of this patch.

The CommitFest entry for this patch is messed up. It shows no author,
even though I'm pretty sure that a patch has to have one by
definition.

The CF app does not require an author name when the entry is first
created. The author needs to be added afterwards. A message-id, a
description and a category are the mandatory things.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#8)
Re: IF (NOT) EXISTS in psql-completion

Hi

2016-03-16 5:01 GMT+01:00 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp

:

Hello,

# It seems that I have been forgotten in the recepient list..

At Tue, 15 Mar 2016 22:09:59 -0400, Peter Eisentraut <peter_e@gmx.net>
wrote in <56E8C077.2000903@gmx.net>

On 2/5/16 3:09 AM, Kyotaro HORIGUCHI wrote:

I considered how to make tab-completion robust for syntactical
noises, in other words, optional words in syntax. Typically "IF
(NOT) EXISTS", UNIQUE and TEMPORARY are words that don't affect
further completion.

To repeat the question I raised in the previous commit fest about tab
completion: Why do you want tab completion for IF NOT EXISTS? When you
tab complete, the completion mechanism will show you whether the item in
question exists. What is the use case?

Ah, I think I understand you question. It's not about IF EXISTS,
but only IF NOT EXSTS. It is needed when repeated execution of
the same SQL statement will be done using command line
history. Such stocks of commands in history is often
convenient. And sometimes I rely on psql-completion to write a
SQL script. The completions for such words seemingly useless on
instant-execution will be handy to do that.

Another thing I want to do by this patch is that we can get
completion even after such optional words. I have been annoyed
many times by this. Some of them, such as UNIQUE, TEMPORARY and
CONCURRENTLY are treated but they just doubles the matching
condition expressions.

I am looking this patch. It looks well, but this feature doesn't respect
upper or lower chars. It enforce upper chars. This is not consistent with
any other autocomplete.

Regards

Pavel

Show quoted text

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#11)
Re: IF (NOT) EXISTS in psql-completion

Hi

2016-03-17 21:02 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:

Hi

I am looking this patch. It looks well, but this feature doesn't respect
upper or lower chars. It enforce upper chars. This is not consistent with
any other autocomplete.

I checked it against sql help and these statements doesn't work

alter foreign table hhhh drop column
alter text search configuration jjj drop mapping
alter type hhh drop attribute
drop cast
drop extension
drop operator
drop text search
drop transform -- missing autocomplete completely
drop user mapping
alter table jjj add column
create temp sequence
create sequence

Regards

Pavel

Show quoted text

Regards

Pavel

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#13David Steele
david@pgmasters.net
In reply to: Pavel Stehule (#12)
Re: IF (NOT) EXISTS in psql-completion

Hi Kyotaro,

On 3/18/16 3:22 AM, Pavel Stehule wrote:

I am looking this patch. It looks well, but this feature doesn't
respect upper or lower chars. It enforce upper chars. This is not
consistent with any other autocomplete.

I checked it against sql help and these statements doesn't work

alter foreign table hhhh drop column
alter text search configuration jjj drop mapping
alter type hhh drop attribute
drop cast
drop extension
drop operator
drop text search
drop transform -- missing autocomplete completely
drop user mapping
alter table jjj add column
create temp sequence
create sequence

Do you have an idea of when you will have a new patch ready?

Thanks,
--
-David
david@pgmasters.net

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#14Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: David Steele (#13)
1 attachment(s)
Re: IF (NOT) EXISTS in psql-completion

Thank you Pavel, David.

Thank you for pointing syntaxes to be addressed. Most of the are
addressed in the attached patch.

At Tue, 22 Mar 2016 12:57:27 -0400, David Steele <david@pgmasters.net> wrote in <56F17977.8040503@pgmasters.net>

Hi Kyotaro,

On 3/18/16 3:22 AM, Pavel Stehule wrote:

I am looking this patch. It looks well, but this feature doesn't
respect upper or lower chars. It enforce upper chars. This is not
consistent with any other autocomplete.

As mentioned before, upper-lower problem is an existing
issue. The case of the words in a query result list cannot be
edited since it may contain words that should not be changed,
such as relation names. So we can address it only before issueing
a query but I haven't found simple way to do it.

I checked it against sql help and these statements doesn't work

Thank you very much.

alter foreign table hhhh drop column
drop cast
drop operator
drop transform -- missing autocomplete completely

These are not done. Each of them has issues to be addressed
before adding completion of IF EXISTS.

alter text search configuration jjj drop mapping
alter type hhh drop attribute
drop extension

Done.

drop text search

I don't see the syntax "drop text search [if exists]". drop text
search (configuration|dictionary|parser|template) are already
addressed.

drop user mapping

"drop user" was not completed with "mapping". I added it then
addressed this. (This might be another issue.)

alter table jjj add column

Done if it is mentioning DROP COLUMN. But new two macros
HeadMatches6 and 7 are introduced together.

create temp sequence
create sequence

DROP SEQUENCE is already completed with IF EXISTS. CREATE [TEMP]
SEQUENCE with IF NOT EXISTS is added.

Do you have an idea of when you will have a new patch ready?

Sorry to to have been late. The attached is the revised version.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

0001-Suggest-IF-NOT-EXISTS-for-tab-completion-of-psql_v3.patchtext/x-patch; charset=us-asciiDownload
From 2f46c0aa00fd8fd6357dcb76a26e49e3a66e2572 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 5 Feb 2016 16:50:35 +0900
Subject: [PATCH] Suggest IF (NOT) EXISTS for tab-completion of psql

This patch lets psql to suggest "IF (NOT) EXISTS". Addition to that,
since this patch introduces some mechanism for syntactical robustness,
it allows psql completion to omit some optional part on matching.
---
 src/bin/psql/tab-complete.c | 626 ++++++++++++++++++++++++++++++++++++--------
 1 file changed, 524 insertions(+), 102 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 6a81416..73c5601 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -656,6 +656,10 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "   FROM pg_catalog.pg_roles "\
 "  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"
 
+#define Query_for_list_of_rules \
+"SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules "\
+" WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"
+
 #define Query_for_list_of_grant_roles \
 " SELECT pg_catalog.quote_ident(rolname) "\
 "   FROM pg_catalog.pg_roles "\
@@ -763,6 +767,11 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "SELECT pg_catalog.quote_ident(tmplname) FROM pg_catalog.pg_ts_template "\
 " WHERE substring(pg_catalog.quote_ident(tmplname),1,%d)='%s'"
 
+#define Query_for_list_of_triggers \
+"SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger "\
+" WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s' AND "\
+"       NOT tgisinternal"
+
 #define Query_for_list_of_fdws \
 " SELECT pg_catalog.quote_ident(fdwname) "\
 "   FROM pg_catalog.pg_foreign_data_wrapper "\
@@ -906,7 +915,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
 	{"POLICY", NULL, NULL},
 	{"ROLE", Query_for_list_of_roles},
-	{"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"},
+	{"RULE", Query_for_list_of_rules},
 	{"SCHEMA", Query_for_list_of_schemas},
 	{"SEQUENCE", NULL, &Query_for_list_of_sequences},
 	{"SERVER", Query_for_list_of_servers},
@@ -915,7 +924,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"TEMP", NULL, NULL, THING_NO_DROP},		/* for CREATE TEMP TABLE ... */
 	{"TEMPLATE", Query_for_list_of_ts_templates, NULL, THING_NO_SHOW},
 	{"TEXT SEARCH", NULL, NULL},
-	{"TRIGGER", "SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s' AND NOT tgisinternal"},
+	{"TRIGGER", Query_for_list_of_triggers},
 	{"TYPE", NULL, &Query_for_list_of_datatypes},
 	{"UNIQUE", NULL, NULL, THING_NO_DROP},		/* for CREATE UNIQUE INDEX ... */
 	{"UNLOGGED", NULL, NULL, THING_NO_DROP},	/* for CREATE UNLOGGED TABLE
@@ -944,6 +953,7 @@ static char **complete_from_variables(const char *text,
 					const char *prefix, const char *suffix, bool need_value);
 static char *complete_from_files(const char *text, int state);
 
+static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
@@ -952,6 +962,7 @@ static char **get_previous_words(int point, char **buffer, int *nwords);
 
 static char *get_guctype(const char *varname);
 
+static const pgsql_thing_t *find_thing_entry(char *word);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -1107,6 +1118,9 @@ psql_completion(const char *text, int start, int end)
 	/* The number of words found on the input line. */
 	int			previous_words_count;
 
+	/* The number of prefixing words to be ignored */
+	int			head_shift = 0;
+
 	/*
 	 * For compactness, we use these macros to reference previous_words[].
 	 * Caution: do not access a previous_words[] entry without having checked
@@ -1124,31 +1138,72 @@ psql_completion(const char *text, int start, int end)
 #define prev8_wd  (previous_words[7])
 #define prev9_wd  (previous_words[8])
 
+	/* Move the position of the beginning word for matching macros.  */
+#define HEADSHIFT(n) \
+	(head_shift += n, true)
+
+	/* Return the number of stored words counting head shift */
+#define WORD_COUNT() (previous_words_count - head_shift)
+
+	/* Return the true index in previous_words for index from the beginning */
+#define HEAD_INDEX(n) \
+	(previous_words_count - head_shift - (n))
+
+	/*
+	 * remove n words from current shifted position, see MidMatchAndRevmove2
+	 * for the reason for the return value
+	 */
+#define COLLAPSE(n) \
+	(memmove(previous_words + HEAD_INDEX(n), previous_words + HEAD_INDEX(0), \
+			 sizeof(char *) * head_shift),								\
+	 previous_words_count -= (n), false)
+
+	/*
+	 * Find the position the specified word occurs last and shift to there.
+	 * This is used to ignore the words before there.
+	 */
+#define SHIFT_TO_LAST1(p1) \
+	(HEADSHIFT(find_last_index_of(p1, previous_words, previous_words_count)), \
+	 true)
+
+	/*
+	 * Remove the specified words if they match from the sth word in
+	 * previous_words.
+	 */
+#define MidMatchAndRemove1(s, p1) \
+	((HEADSHIFT(s) && HeadMatches1(p1) && COLLAPSE(1)) || HEADSHIFT(-s))
+
+#define MidMatchAndRemove2(s, p1, p2) \
+	((HEADSHIFT(s) && HeadMatches2(p1, p2) && COLLAPSE(2)) || HEADSHIFT(-s))
+
+#define MidMatchAndRemove3(s, p1, p2, p3)									\
+	((HEADSHIFT(s) && HeadMatches3(p1, p2, p3) && COLLAPSE(3)) || HEADSHIFT(-s))
+
 	/* Macros for matching the last N words before point, case-insensitively. */
 #define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches(p1, prev_wd))
 
 #define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd))
 
 #define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
+	(WORD_COUNT() >= 3 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd))
 
 #define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
+	(WORD_COUNT() >= 4 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
 	 word_matches(p4, prev4_wd))
 
 #define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
+	(WORD_COUNT() >= 5 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1156,7 +1211,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p5, prev5_wd))
 
 #define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
+	(WORD_COUNT() >= 6 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1165,7 +1220,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p6, prev6_wd))
 
 #define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
+	(WORD_COUNT() >= 7 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1175,7 +1230,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p7, prev7_wd))
 
 #define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
+	(WORD_COUNT() >= 8 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1186,7 +1241,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p8, prev8_wd))
 
 #define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
+	(WORD_COUNT() >= 9 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1199,43 +1254,43 @@ psql_completion(const char *text, int start, int end)
 
 	/* Macros for matching the last N words before point, case-sensitively. */
 #define TailMatchesCS1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches_cs(p1, prev_wd))
 #define TailMatchesCS2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches_cs(p1, prev_wd) && \
 	 word_matches_cs(p2, prev2_wd))
 
 	/*
-	 * Macros for matching N words beginning at the start of the line,
+	 * Macros for matching N words exactly to the line,
 	 * case-insensitively.
 	 */
 #define Matches1(p1) \
-	(previous_words_count == 1 && \
+	(WORD_COUNT() == 1 && \
 	 TailMatches1(p1))
 #define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
+	(WORD_COUNT() == 2 && \
 	 TailMatches2(p1, p2))
 #define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
+	(WORD_COUNT() == 3 && \
 	 TailMatches3(p1, p2, p3))
 #define Matches4(p1, p2, p3, p4) \
-	(previous_words_count == 4 && \
+	(WORD_COUNT() == 4 && \
 	 TailMatches4(p1, p2, p3, p4))
 #define Matches5(p1, p2, p3, p4, p5) \
-	(previous_words_count == 5 && \
+	(WORD_COUNT() == 5 && \
 	 TailMatches5(p1, p2, p3, p4, p5))
 #define Matches6(p1, p2, p3, p4, p5, p6) \
-	(previous_words_count == 6 && \
+	(WORD_COUNT() == 6 && \
 	 TailMatches6(p1, p2, p3, p4, p5, p6))
 #define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(previous_words_count == 7 && \
+	(WORD_COUNT() == 7 && \
 	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
 #define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(previous_words_count == 8 && \
+	(WORD_COUNT() == 8 && \
 	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
 #define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(previous_words_count == 9 && \
+	(WORD_COUNT() == 9 && \
 	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
 
 	/*
@@ -1243,19 +1298,53 @@ psql_completion(const char *text, int start, int end)
 	 * what is after them, case-insensitively.
 	 */
 #define HeadMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]))
+	(HEAD_INDEX(1) >=0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]))
 
 #define HeadMatches2(p1, p2) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]))
+	(HEAD_INDEX(2) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]))
 
 #define HeadMatches3(p1, p2, p3) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]) && \
-	 word_matches(p3, previous_words[previous_words_count - 3]))
+	(HEAD_INDEX(3) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]))
+
+#define HeadMatches4(p1, p2, p3, p4) \
+	(HEAD_INDEX(4) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]))
+
+#define HeadMatches5(p1, p2, p3, p4, p5) \
+	(HEAD_INDEX(5) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]))
+
+#define HeadMatches6(p1, p2, p3, p4, p5, p6)		\
+	(HEAD_INDEX(6) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]) && \
+	 word_matches(p6, previous_words[HEAD_INDEX(6)]))
+
+#define HeadMatches7(p1, p2, p3, p4, p5, p6, p7)	\
+	(HEAD_INDEX(7) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]) && \
+	 word_matches(p6, previous_words[HEAD_INDEX(6)]) && \
+	 word_matches(p7, previous_words[HEAD_INDEX(7)]))
 
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
@@ -1327,9 +1416,16 @@ psql_completion(const char *text, int start, int end)
 	else if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
+	/*
+	 * If this is in CREATE SCHEMA, seek to the last CREATE and regard it as
+	 * current command to complete.
+	 */
+	else if (HeadMatches2("CREATE", "SCHEMA") &&
+			 SHIFT_TO_LAST1("CREATE") &&
+			 false) {} /* FALL THROUGH */
 /* CREATE */
 	/* complete with something you can create */
-	else if (TailMatches1("CREATE"))
+	else if (Matches1("CREATE"))
 		matches = completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
@@ -1342,7 +1438,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE */
 	else if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
+	/* Try ALTER TABLE after removing optional words IF EXISTS*/
+	else if (HeadMatches2("ALTER", "TABLE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("ALTER", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER something */
 	else if (Matches1("ALTER"))
@@ -1420,6 +1522,18 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
+	/* ALTER FOREIGN TABLE */
+	else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
+								   " UNION SELECT 'IF EXISTS'");
+
+	/* Try ALTER FOREIGN TABLE after removing optinal words IF EXISTS */
+	/* Complete for DROP together  */
+	else if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+
 	/* ALTER FOREIGN TABLE <name> */
 	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
@@ -1431,10 +1545,21 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_FOREIGN_TABLE);
 	}
 
+	/* ALTER FOREIGN TABLE xxx RENAME */
+	else if (Matches5("ALTER", "FOREIGN", "TABLE", MatchAny, "RENAME"))
+		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'TO'");
+
 	/* ALTER INDEX */
 	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
+	/* Try ALTER INDEX after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "INDEX") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("ALTER", "INDEX"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+
 	/* ALTER INDEX <name> */
 	else if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
@@ -1463,8 +1588,15 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER MATERIALIZED VIEW */
 	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
+	/* Try ALTER MATERIALIZED VIEW after removing optional words IF EXISTS */
+	else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("ALTER", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+
 	/* ALTER USER,ROLE <name> */
 	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
@@ -1515,8 +1647,23 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER DOMAIN <sth> DROP */
 	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
-	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	/* ALTER DOMAIN <sth> RENAME|VALIDATE CONSTRAINT */
+	else if (Matches5("ALTER", "DOMAIN", MatchAny, "RENAME|VALIDATE", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+	}
+	/* ALTER DOMAIN <sth> DROP CONSTRAINT */
+	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type
+							"UNION SELECT 'IF EXISTS'");
+	}
+	/* Try the same match after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
@@ -1531,8 +1678,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER DOMAIN <sth> SET */
 	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
-	/* ALTER SEQUENCE <name> */
-	else if (Matches3("ALTER", "SEQUENCE", MatchAny))
+	else if (Matches2("ALTER", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try ALTER SEQUENCE after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "SEQUENCE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1555,6 +1707,14 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER SYSTEM SET|RESET <name> */
 	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
+	/* ALTER VIEW */
+	else if (Matches2("ALTER", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   "UNION SELECT 'IF EXISTS'");
+	/*  Try ALTER VIEW after removing optional worlds IF EXISTS */
+	else if (HeadMatches2("ALTER", "VIEW") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false){} /* FALL THROUGH */
 	/* ALTER VIEW <name> */
 	else if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
@@ -1564,9 +1724,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
-	/* ALTER POLICY <name> */
+	/* ALTER POLICY */
 	else if (Matches2("ALTER", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies
+							"UNION SELECT 'IF EXISTS'");
+	/* Try ALTER POLICY after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "POLICY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	/* ALTER POLICY <name> ON */
 	else if (Matches3("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
@@ -1692,8 +1857,10 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
-	/* ALTER TABLE xxx RENAME yyy */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
+	/* ALTER [FOREIGN] TABLE xxx RENAME yyy */
+	else if ((HeadMatches2("ALTER", "TABLE") ||
+			  HeadMatches3("ALTER", "FOREIGN", "TABLE")) &&
+			 TailMatches2("RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
@@ -1703,15 +1870,47 @@ psql_completion(const char *text, int start, int end)
 	/* If we have ALTER TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
+	/*  ALTER TABLE DROP COLUMN may take IF EXISTS */
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+	/* Remove optional COLUMN [IF EXISTS] just after ADD/DROP */
+	else if (HeadMatches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP") &&
+			 MidMatchAndRemove1(4, "COLUMN") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 Matches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP"))
+		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
-	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
+	 * If we have ALTER TABLE <sth> ALTER|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME|VALIDATE", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+	}
+	/*
+	 * If we have ALTER TABLE <sth> DROP CONSTRAINT,
+	 * provide list of constraints
+	 */
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table
+							"UNION SELECT 'IF EXISTS'");
+	}
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
@@ -1833,8 +2032,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 	else if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING",
-							"DROP MAPPING FOR",
-							"OWNER TO", "RENAME TO", "SET SCHEMA");
+							"DROP MAPPING",	"OWNER TO", "RENAME TO", "SET SCHEMA");
+	else if (Matches7("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny, "DROP", "MAPPING"))
+		COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR");
+	/* Remove optional words IF EXISTS */
+	else if (HeadMatches7("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny, "DROP", "MAPPING") &&
+			 MidMatchAndRemove2(7, "IF", "EXISTS") &&
+			 false) {} /* Nothing to do for now */
 
 	/* complete ALTER TYPE <foo> with actions */
 	else if (Matches3("ALTER", "TYPE", MatchAny))
@@ -1844,6 +2048,12 @@ psql_completion(const char *text, int start, int end)
 	/* complete ALTER TYPE <foo> ADD with actions */
 	else if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
+	else if (Matches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE"))
+		COMPLETE_WITH_LIST2("IF NOT EXISTS", "");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE") &&
+			 MidMatchAndRemove3(5, "IF", "NOT", "EXISTS") &&
+			 false) {} /* Nothing to do for now */
 	/* ALTER TYPE <foo> RENAME	*/
 	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "TO");
@@ -1855,10 +2065,15 @@ psql_completion(const char *text, int start, int end)
 	 * If we have ALTER TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list
 	 * of attributes
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
+	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
-	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
+	else if (Matches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE"))
+		COMPLETE_WITH_ATTR(prev3_wd, " UNION SELECT 'IF EXISTS'");
+	/* Remove optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+	 /* ALTER TYPE ALTER ATTRIBUTE <foo> */
+			 Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* complete ALTER GROUP <foo> */
 	else if (Matches3("ALTER", "GROUP", MatchAny))
@@ -1990,6 +2205,12 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	else if (Matches2("CREATE", "EXTENSION"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions
+							" UNION SELECT 'IF NOT EXISTS'");
+	/* Try the same after removing optional words IF NOT EXISTS */
+	else if (HeadMatches2("CREATE", "EXTENSION") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
 	else if (Matches3("CREATE", "EXTENSION", MatchAny))
@@ -2005,6 +2226,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
+	/* CREATE FOREIGN TABLE */
+	else if (Matches3("CREATE", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
+			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
@@ -2013,23 +2242,39 @@ psql_completion(const char *text, int start, int end)
 	/* First off we complete CREATE UNIQUE with "INDEX" */
 	else if (TailMatches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
-	/* If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY",
+
+	/* Remove optional word UNIQUE from CREATE UNIQUE INDEX */
+	else if (HeadMatches3("CREATE", MatchAny, "INDEX") &&
+			 MidMatchAndRemove1(1, "UNIQUE") &&
+			 false) {} /* FALL THROUGH */
+
+	/* If we have CREATE INDEX, then add "ON", "CONCURRENTLY" or IF NOT EXISTS,
 	   and existing indexes */
-	else if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	else if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
-								   " UNION SELECT 'CONCURRENTLY'");
-	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	else if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
-			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-	/* Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing indexes */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+								   " UNION SELECT 'CONCURRENTLY'"
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and
+	 * existing indexes */
+	else if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF NOT EXISTS'"
 								   " UNION SELECT 'ON'");
-	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
-			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
+
+	/* Remove optional words "CONCURRENTLY",  "IF NOT EXISTS" */
+	else if (HeadMatches2("CREATE", "INDEX") &&
+			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
+	/* Complete CREATE INDEX [<name>] ON with a list of tables */
+	else if (Matches4("CREATE", "INDEX", MatchAny, "ON") ||
+			 Matches3("CREATE", "INDEX", "ON"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+
+	/* Complete CREATE INDEX <sth> with "ON" */
+	else if (Matches3("CREATE", "INDEX", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/*
@@ -2037,10 +2282,10 @@ psql_completion(const char *text, int start, int end)
 	 * should really be in parens)
 	 */
 	else if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-			 TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+			 TailMatches3("INDEX", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-			 TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
+	else if (Matches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
+			 Matches4("INDEX", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* same if you put in USING */
 	else if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
@@ -2101,27 +2346,56 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-/* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	else if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
-			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
+/* CREATE SEQUENCE, removing optional words TEMPORARY/TEMP */
+	else if (HeadMatches3("CREATE", MatchAny, "SEQUENCE") &&
+			 MidMatchAndRemove1(1, "TEMP|TEMPORARY") &&
+			 false) {} /* FALL THROUGH */
+	else if(Matches2("CREATE", "SEQUENCE"))
+		COMPLETE_WITH_LIST2("IF NOT EXISTS", "");
+	else if(HeadMatches2("CREATE", "SEQUENCE") &&
+			MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			false) {} /* FALL THROUGH */
+	else if (Matches3("CREATE", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	else if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
-		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
+	else if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
 	else if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
-/* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE SCHEMA <name> */
+	else if (Matches2("CREATE", "SCHEMA"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+							" UNION SELECT 'IF NOT EXISTS'");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches2("CREATE", "SCHEMA") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH*/
+
+/* CREATE TABLE  */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	else if (Matches2("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	else if (TailMatches2("CREATE", "UNLOGGED"))
+	else if (Matches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
+	/* Remove optional words here */
+	else if (HeadMatches3("CREATE", MatchAny, "TABLE") &&
+			 MidMatchAndRemove1(1, "TEMP|TEMPORARY|UNLOGGED") &&
+			 false) {} /* FALL THROUGH */
+
+	else if (Matches2("CREATE", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+								   " UNION SELECT 'IF NOT EXISTS'");
+
+	/* Remove optional words here */
+	else if (HeadMatches2("CREATE", "TABLE") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
@@ -2135,18 +2409,18 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
-/* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	else if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	else if (Matches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	else if (Matches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	else if (Matches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+	else if (Matches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
 	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
@@ -2203,9 +2477,16 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
-/* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE VIEW  */
+	else if (Matches2("CREATE", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches2("CREATE", "VIEW") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	/* Complete CREATE VIEW <name> with AS */
-	else if (TailMatches3("CREATE", "VIEW", MatchAny))
+	else if (Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
 	else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
@@ -2214,6 +2495,15 @@ psql_completion(const char *text, int start, int end)
 /* CREATE MATERIALIZED VIEW */
 	else if (Matches2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+	else if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Try the same after removing optional words IF NOT EXISTS. VIEW will be
+	 * completed afterwards */
+	else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
+			 Matches3("CREATE", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
 	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
@@ -2272,28 +2562,61 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
+	else if (Matches2("DROP", "AGGREGATE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates,
+								   " UNION SELECT 'IF EXISTS'");
+	else if (Matches2("DROP", "FUNCTION"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
 	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
+	else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 
 	/* DROP INDEX */
 	else if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF EXISTS'"
 								   " UNION SELECT 'CONCURRENTLY'");
 	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches3("DROP", "INDEX", MatchAny))
-		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	else if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */
+	else if (HeadMatches2("DROP", "INDEX") &&
+			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
 	else if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+	else if (Matches2("DROP", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Remove optional words IF EXISTS  */
+	else if (HeadMatches2("DROP", "VIEW") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
 	/* DROP OWNED BY */
@@ -2306,7 +2629,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (Matches3("DROP", "TRIGGER", MatchAny))
+	else if (Matches2("DROP", "TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_triggers
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "TRIGGER") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
@@ -2320,15 +2649,27 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	else if (Matches3("DROP", "EVENT", "TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers
+							" UNION SELECT 'IF EXISTS'");
+	/* Trye the same after removing optional words IF EXISTS */
+	else if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
-	/* DROP POLICY <name>  */
+	/* DROP POLICY */
 	else if (Matches2("DROP", "POLICY"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "POLICY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
-	/* DROP POLICY <name> ON */
+	/* DROP POLICY <name> */
 	else if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	/* DROP POLICY <name> ON <table> */
+	/* DROP POLICY <name> ON */
 	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
@@ -2336,7 +2677,13 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* DROP RULE */
-	else if (Matches3("DROP", "RULE", MatchAny))
+	else if (Matches2("DROP", "RULE"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_rules
+							"UNION SELECT 'IF EXISTS'");
+	/* DROP RULE <name>, after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "RULE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
@@ -2346,6 +2693,52 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
+	/* IF EXISTS processing for DROP everything else */
+	else if (Matches2("DROP",
+					  "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|"
+					  "GROUP|LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|"
+					  "SERVER|TABLE|TABLESPACE|TYPE|USER") ||
+			 Matches4("DROP", "TEXT", "SEARCH",
+					  "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE"))
+
+	{
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
+		char *addition = " UNION SELECT 'IF EXISTS'";
+
+		if (ent)
+		{
+			/* Completing USER needs special treat */
+			if (pg_strcasecmp(prev_wd, "USER") == 0)
+			{
+				COMPLETE_WITH_QUERY(Query_for_list_of_roles 
+									"UNION SELECT 'MAPPING' UNION SELECT 'IF EXISTS'");
+			}
+			else if (ent->query)
+			{
+				char *buf = pg_malloc(strlen(ent->query) +
+									  strlen(addition) + 1);
+				strcpy(buf, ent->query);
+				strcat(buf, addition);
+				COMPLETE_WITH_QUERY(buf);
+				free(buf);
+			}
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery,
+										   " UNION SELECT 'IF EXISTS'");
+		}
+	}
+	/* Remove optional IF EXISTS from DROP */
+	else if (HeadMatches2("DROP",
+						  "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|GROUP|"
+						  "LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|SERVER|"
+						  "TABLE|TABLESPACE|TYPE|USER") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+	else if (HeadMatches4("DROP", "TEXT", "SEARCH",
+						  "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
 /* EXECUTE */
 	else if (Matches1("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
@@ -2392,8 +2785,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /* FOREIGN TABLE */
-	else if (TailMatches2("FOREIGN", "TABLE") &&
-			 !TailMatches3("CREATE", MatchAny, MatchAny))
+	else if (TailMatches2("FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* FOREIGN SERVER */
@@ -2829,8 +3221,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
+	else if (Matches3("ALTER|CREATE", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
+	else if (Matches3("DROP", "USER", "MAPPING"))
+		COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR");
+	else if (HeadMatches3("DROP", "USER", "MAPPING") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
@@ -3058,19 +3455,14 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else
 	{
-		int			i;
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
 
-		for (i = 0; words_after_create[i].name; i++)
+		if (ent)
 		{
-			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
-			{
-				if (words_after_create[i].query)
-					COMPLETE_WITH_QUERY(words_after_create[i].query);
-				else if (words_after_create[i].squery)
-					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
-											   NULL);
-				break;
-			}
+			if (ent->query)
+				COMPLETE_WITH_QUERY(ent->query);
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, NULL);
 		}
 	}
 
@@ -3595,6 +3987,18 @@ complete_from_files(const char *text, int state)
 
 /* HELPER FUNCTIONS */
 
+/*
+ * Return the index (reverse to the index of previous_words) of the tailmost
+ * (topmost in the array) appearance of w.
+ */
+static int
+find_last_index_of(char *w, char **previous_words, int len)
+{
+	int i;
+
+	for (i = 0 ; i < len && !word_matches(w, previous_words[i]) ; i++);
+	return i < len ? (len - i - 1) : 0;
+}
 
 /*
  * Make a pg_strdup copy of s and convert the case according to
@@ -3841,6 +4245,24 @@ get_guctype(const char *varname)
 	return guctype;
 }
 
+/*
+ * Finds the entry in words_after_create[] that matches the word.
+ * NULL if not found.
+ */
+static const pgsql_thing_t *
+find_thing_entry(char *word)
+{
+	int			i;
+
+	for (i = 0; words_after_create[i].name; i++)
+	{
+		if (pg_strcasecmp(word, words_after_create[i].name) == 0)
+			return words_after_create + i;
+	}
+
+	return NULL;
+}
+
 #ifdef NOT_USED
 
 /*
-- 
1.8.3.1

#15Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#14)
Re: IF (NOT) EXISTS in psql-completion

Hi

2016-03-29 8:43 GMT+02:00 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp

:

Thank you Pavel, David.

Thank you for pointing syntaxes to be addressed. Most of the are
addressed in the attached patch.

At Tue, 22 Mar 2016 12:57:27 -0400, David Steele <david@pgmasters.net>
wrote in <56F17977.8040503@pgmasters.net>

Hi Kyotaro,

On 3/18/16 3:22 AM, Pavel Stehule wrote:

I am looking this patch. It looks well, but this feature doesn't
respect upper or lower chars. It enforce upper chars. This is not
consistent with any other autocomplete.

As mentioned before, upper-lower problem is an existing
issue. The case of the words in a query result list cannot be
edited since it may contain words that should not be changed,
such as relation names. So we can address it only before issueing
a query but I haven't found simple way to do it.

This is unpleasant. I am sorry. I had very uncomfortable feeling from this
behave. I am thinking so it should be solvable - you have to convert only
keyword IF EXISTS or IF NOT EXISTS. Maybe there are not trivial solution,
but this should be fixed.

I checked it against sql help and these statements doesn't work

Thank you very much.

alter foreign table hhhh drop column
drop cast
drop operator
drop transform -- missing autocomplete completely

These are not done. Each of them has issues to be addressed
before adding completion of IF EXISTS.

alter text search configuration jjj drop mapping
alter type hhh drop attribute
drop extension

Done.

drop text search

I don't see the syntax "drop text search [if exists]". drop text
search (configuration|dictionary|parser|template) are already
addressed.

ok, probably my mistake. I am sorry.

drop user mapping

"drop user" was not completed with "mapping". I added it then
addressed this. (This might be another issue.)

alter table jjj add column

Done if it is mentioning DROP COLUMN. But new two macros
HeadMatches6 and 7 are introduced together.

create temp sequence
create sequence

DROP SEQUENCE is already completed with IF EXISTS. CREATE [TEMP]
SEQUENCE with IF NOT EXISTS is added.

Do you have an idea of when you will have a new patch ready?

Sorry to to have been late. The attached is the revised version.

I'll check it today.

Regards

Pavel

Show quoted text

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#16Artur Zakirov
a.zakirov@postgrespro.ru
In reply to: Pavel Stehule (#15)
1 attachment(s)
Re: IF (NOT) EXISTS in psql-completion

On 29.03.2016 10:59, Pavel Stehule wrote:

Hi

2016-03-29 8:43 GMT+02:00 Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp <mailto:horiguchi.kyotaro@lab.ntt.co.jp>>:

Thank you Pavel, David.

Thank you for pointing syntaxes to be addressed. Most of the are
addressed in the attached patch.

At Tue, 22 Mar 2016 12:57:27 -0400, David Steele
<david@pgmasters.net <mailto:david@pgmasters.net>> wrote in
<56F17977.8040503@pgmasters.net <mailto:56F17977.8040503@pgmasters.net>>

Hi Kyotaro,

On 3/18/16 3:22 AM, Pavel Stehule wrote:

I am looking this patch. It looks well, but this feature doesn't
respect upper or lower chars. It enforce upper chars. This is not
consistent with any other autocomplete.

As mentioned before, upper-lower problem is an existing
issue. The case of the words in a query result list cannot be
edited since it may contain words that should not be changed,
such as relation names. So we can address it only before issueing
a query but I haven't found simple way to do it.

This is unpleasant. I am sorry. I had very uncomfortable feeling from
this behave. I am thinking so it should be solvable - you have to
convert only keyword IF EXISTS or IF NOT EXISTS. Maybe there are not
trivial solution, but this should be fixed.

Hello,

Can we do something like in the patch? This patch should be applied
after the patch
"0001-Suggest-IF-NOT-EXISTS-for-tab-completion-of-psql_v3.patch".

--
Artur Zakirov
Postgres Professional: http://www.postgrespro.com
Russian Postgres Company

Attachments:

tab-complete.patchtext/x-patch; name=tab-complete.patchDownload
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 73c5601..ed4ff09 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -153,6 +153,7 @@ do { \
 do { \
 	completion_squery = &(query); \
 	completion_charp = addon; \
+	completion_case_sensitive = false; \
 	matches = completion_matches(text, complete_from_schema_query); \
 } while (0)
 
@@ -3754,7 +3755,17 @@ _complete_from_query(int is_schema_query, const char *text, int state)
 		while (list_index < PQntuples(result) &&
 			   (item = PQgetvalue(result, list_index++, 0)))
 			if (pg_strncasecmp(text, item, byte_length) == 0)
-				return pg_strdup(item);
+			{
+				if (completion_case_sensitive)
+					return pg_strdup(item);
+				else
+
+					/*
+					 * If case insensitive matching was requested initially,
+					 * adjust the case according to setting.
+					 */
+					return pg_strdup_keyword_case(item, text);
+			}
 	}
 
 	/* If nothing matches, free the db structure and return null */
#17Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Pavel Stehule (#15)
1 attachment(s)
Re: IF (NOT) EXISTS in psql-completion

Hi, Pavel.

At Tue, 29 Mar 2016 09:59:01 +0200, Pavel Stehule <pavel.stehule@gmail.com> wrote in <CAFj8pRDEPgGyMz2aXgTL33PuD7X+xieaO++wa+V9nQPQiYDMGQ@mail.gmail.com>

On 3/18/16 3:22 AM, Pavel Stehule wrote:

I am looking this patch. It looks well, but this feature doesn't
respect upper or lower chars. It enforce upper chars. This is not
consistent with any other autocomplete.

As mentioned before, upper-lower problem is an existing
issue. The case of the words in a query result list cannot be
edited since it may contain words that should not be changed,
such as relation names. So we can address it only before issueing
a query but I haven't found simple way to do it.

This is unpleasant. I am sorry. I had very uncomfortable feeling from this
behave. I am thinking so it should be solvable - you have to convert only
keyword IF EXISTS or IF NOT EXISTS. Maybe there are not trivial solution,
but this should be fixed.

I understand that and feel the same. But I don't want to put
puzzling code. Defining a macro enable this by writing as the
following.

else if (Matches2("ALTER", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(
Query_for_list_of_tables,
ADDLIST("",
"('IF EXISTS'), ('ALL IN TABLESPACE')",
"('if exists'), ('all in tablespace')"));

...

else if (Matches2("ALTER", "POLICY"))
COMPLETE_WITH_QUERY(
ADDLIST(Query_for_list_of_policies,
"('IF EXISTS')", "('if exists')"));

This will work as you expects but it looks quite redundant, but
avoiding dynamic string (and/or malloc) operation would lead to
the similar results. Integrating the ADDLIST into
COMPLETE... might make the situation better.

The attached patch does it only for "ALTER TABLE" and "ALTER
POLICY".

I checked it against sql help and these statements doesn't work

Thank you very much.

alter foreign table hhhh drop column
drop cast
drop operator
drop transform -- missing autocomplete completely

These are not done. Each of them has issues to be addressed
before adding completion of IF EXISTS.

alter text search configuration jjj drop mapping
alter type hhh drop attribute
drop extension

Done.

drop text search

I don't see the syntax "drop text search [if exists]". drop text
search (configuration|dictionary|parser|template) are already
addressed.

ok, probably my mistake. I am sorry.

drop user mapping

"drop user" was not completed with "mapping". I added it then
addressed this. (This might be another issue.)

alter table jjj add column

Done if it is mentioning DROP COLUMN. But new two macros
HeadMatches6 and 7 are introduced together.

create temp sequence
create sequence

DROP SEQUENCE is already completed with IF EXISTS. CREATE [TEMP]
SEQUENCE with IF NOT EXISTS is added.

Do you have an idea of when you will have a new patch ready?

Sorry to to have been late. The attached is the revised version.

I'll check it today.

Thanks.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

0001-Sample-implement-of-case-sensitive-additional-list.patchtext/x-patch; charset=us-asciiDownload
From c2e2469a72cbbd078a2fa3f300716f6e4b2ebc81 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Tue, 29 Mar 2016 19:01:13 +0900
Subject: [PATCH] Sample implement of case-sensitive additional list.

---
 src/bin/psql/tab-complete.c | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 73c5601..5d102d7 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -312,6 +312,10 @@ do { \
 	COMPLETE_WITH_LIST_CS(list); \
 } while (0)
 
+#define CHOOSE_CASE(b, e, u, l) (l && islower(text[0]) ? b l e : b u e)
+#define ADDLIST(q, u, l) \
+	CHOOSE_CASE(q "UNION SELECT * FROM (VALUES ", ") as x", u, l)
+
 /*
  * Assembly instructions for schema queries
  */
@@ -1437,9 +1441,12 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER TABLE */
 	else if (Matches2("ALTER", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   "UNION SELECT 'IF EXISTS'"
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+		COMPLETE_WITH_SCHEMA_QUERY(
+			Query_for_list_of_tables,
+			ADDLIST("",
+					"('IF EXISTS'), ('ALL IN TABLESPACE')",
+					"('if exists'), ('all in tablespace')"));
+
 	/* Try ALTER TABLE after removing optional words IF EXISTS*/
 	else if (HeadMatches2("ALTER", "TABLE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1726,8 +1733,9 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER POLICY */
 	else if (Matches2("ALTER", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST(Query_for_list_of_policies,
+					"('IF EXISTS')", "('if exists')"));
 	/* Try ALTER POLICY after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-- 
1.8.3.1

#18Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Artur Zakirov (#16)
Re: IF (NOT) EXISTS in psql-completion

Hi, thank you for the suggestion.

At Tue, 29 Mar 2016 12:54:01 +0300, Artur Zakirov <a.zakirov@postgrespro.ru> wrote in <56FA50B9.7000107@postgrespro.ru>

On 29.03.2016 10:59, Pavel Stehule wrote:

I am looking this patch. It looks well, but this feature doesn't
respect upper or lower chars. It enforce upper chars. This is
not
consistent with any other autocomplete.

As mentioned before, upper-lower problem is an existing
issue. The case of the words in a query result list cannot be
edited since it may contain words that should not be changed,
such as relation names. So we can address it only before issueing
a query but I haven't found simple way to do it.

This is unpleasant. I am sorry. I had very uncomfortable feeling from
this behave. I am thinking so it should be solvable - you have to
convert only keyword IF EXISTS or IF NOT EXISTS. Maybe there are not
trivial solution, but this should be fixed.

Hello,

Can we do something like in the patch? This patch should be applied
after the patch
"0001-Suggest-IF-NOT-EXISTS-for-tab-completion-of-psql_v3.patch".

Unfortunately, all completions are shown in upper-case with it
for some cases (since COMP_KEYWORD_CASE is upper as the default
on my environment). This prevents names, that are not SQL
keywords, from getting further completion.

=# alter table <tab>
ALL IN TABLESPACE PG_CATALOG. PUBLIC.
ALPHA. PG_TEMP_1. X
IF EXISTS PG_TOAST.
INFORMATION_SCHEMA. PG_TOAST_TEMP_1.

pg_strdup_keyword_case follows COMP_KEYWORD_CASE pset variable
which instructs how *SQL keywords* should be handled. It is not
for this usage. And as mentioned before, SQL keywords and
identifiers cannot be treated in the same way, so the place is
not suitable to do this.

By the way, memory blocks that readline sees are freed by it but
blocks allocated by the additional pstrdup seems abandoned
without being freed. Actually, the address of newly allocated
blocks seems increasing time by time slowly *even without this
patch*..

Of course, no one seems to be able to rush into out of memory by
this leak, though..

Anyway, using malloc is one reason of additional complexity and
it is the main cause that I avoided dynamic memory allocation.

Thoughts?

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#19Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#17)
Re: IF (NOT) EXISTS in psql-completion

2016-03-29 12:08 GMT+02:00 Kyotaro HORIGUCHI <
horiguchi.kyotaro@lab.ntt.co.jp>:

Hi, Pavel.

At Tue, 29 Mar 2016 09:59:01 +0200, Pavel Stehule <pavel.stehule@gmail.com>
wrote in <
CAFj8pRDEPgGyMz2aXgTL33PuD7X+xieaO++wa+V9nQPQiYDMGQ@mail.gmail.com>

On 3/18/16 3:22 AM, Pavel Stehule wrote:

I am looking this patch. It looks well, but this feature

doesn't

respect upper or lower chars. It enforce upper chars. This is

not

consistent with any other autocomplete.

As mentioned before, upper-lower problem is an existing
issue. The case of the words in a query result list cannot be
edited since it may contain words that should not be changed,
such as relation names. So we can address it only before issueing
a query but I haven't found simple way to do it.

This is unpleasant. I am sorry. I had very uncomfortable feeling from

this

behave. I am thinking so it should be solvable - you have to convert only
keyword IF EXISTS or IF NOT EXISTS. Maybe there are not trivial solution,
but this should be fixed.

I understand that and feel the same. But I don't want to put
puzzling code. Defining a macro enable this by writing as the
following.

puzzle is wrong, but nonconsistent behave is not acceptable

Regards

Pavel

Show quoted text

else if (Matches2("ALTER", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(
Query_for_list_of_tables,
ADDLIST("",
"('IF EXISTS'), ('ALL IN TABLESPACE')",
"('if exists'), ('all in tablespace')"));

...

else if (Matches2("ALTER", "POLICY"))
COMPLETE_WITH_QUERY(
ADDLIST(Query_for_list_of_policies,
"('IF EXISTS')", "('if exists')"));

This will work as you expects but it looks quite redundant, but
avoiding dynamic string (and/or malloc) operation would lead to
the similar results. Integrating the ADDLIST into
COMPLETE... might make the situation better.

The attached patch does it only for "ALTER TABLE" and "ALTER
POLICY".

I checked it against sql help and these statements doesn't work

Thank you very much.

alter foreign table hhhh drop column
drop cast
drop operator
drop transform -- missing autocomplete completely

These are not done. Each of them has issues to be addressed
before adding completion of IF EXISTS.

alter text search configuration jjj drop mapping
alter type hhh drop attribute
drop extension

Done.

drop text search

I don't see the syntax "drop text search [if exists]". drop text
search (configuration|dictionary|parser|template) are already
addressed.

ok, probably my mistake. I am sorry.

drop user mapping

"drop user" was not completed with "mapping". I added it then
addressed this. (This might be another issue.)

alter table jjj add column

Done if it is mentioning DROP COLUMN. But new two macros
HeadMatches6 and 7 are introduced together.

create temp sequence
create sequence

DROP SEQUENCE is already completed with IF EXISTS. CREATE [TEMP]
SEQUENCE with IF NOT EXISTS is added.

Do you have an idea of when you will have a new patch ready?

Sorry to to have been late. The attached is the revised version.

I'll check it today.

Thanks.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#20Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Pavel Stehule (#19)
1 attachment(s)
Re: IF (NOT) EXISTS in psql-completion

Hello,

At Tue, 29 Mar 2016 13:12:06 +0200, Pavel Stehule <pavel.stehule@gmail.com> wrote in <CAFj8pRCnrpdSqSozg4Y8__2LFyiNqUCE=KPzFw1+AF_LutmRiQ@mail.gmail.com>

2016-03-29 12:08 GMT+02:00 Kyotaro HORIGUCHI <

As mentioned before, upper-lower problem is an existing
issue. The case of the words in a query result list cannot be
edited since it may contain words that should not be changed,
such as relation names. So we can address it only before issueing
a query but I haven't found simple way to do it.

This is unpleasant. I am sorry. I had very uncomfortable feeling from

this

behave. I am thinking so it should be solvable - you have to convert only
keyword IF EXISTS or IF NOT EXISTS. Maybe there are not trivial solution,
but this should be fixed.

I understand that and feel the same. But I don't want to put
puzzling code. Defining a macro enable this by writing as the
following.

puzzle is wrong, but nonconsistent behave is not acceptable

Mmm. Ok, The attched patch, which applies on top of the
IF(NOT)EXIST patch, does this in rather saner appearance, but
needs to run additional C function at runtime and additional some
macros. The function is called up to once per completion, so it
won't be a performance problem.

else if (Matches2("ALTER", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));

or

else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
{
completion_info_charp = prev3_wd;
COMPLETE_WITH_QUERY(
ADDLIST1(Query_for_constraint_of_table, "IF EXISTS"));
}

I think this syntax is acceptable. Only keywords follows the
setting of COMP_KEYWORD_CASE, as Artur suggested.

=# alter table <tab>
ALL IN TABLESPACE pg_catalog. public.
alpha. pg_temp_1. x
IF EXISTS pg_toast.
information_schema. pg_toast_temp_1.
=# alter table i<tab>
if exists
information_schema.sql_features
...
=# alter table if<tab>
=# alter table if exists
======
=# alter table I<tab>
=# alter table IF EXISTS // "information_schema" doesn't match.

Since this is another problem from IF (NOT) EXISTS, this is
in separate form.

What do you think about this?

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

0001-Make-appended-keywords-of-completion-queries-follow-.patchtext/x-patch; charset=us-asciiDownload
From 282266477b2dfbc6c35a8a4da49544eb9cbfb6fe Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Tue, 29 Mar 2016 19:01:13 +0900
Subject: [PATCH] Make appended keywords of completion queries follow to input.

Currently some keyword shown along with database objects are always in
upper case. This patch changes the behavior so that the case of the
additional keywords follow the setting of COMP_KEYWORD_CASE.
---
 src/bin/psql/tab-complete.c | 231 +++++++++++++++++++++++++-------------------
 1 file changed, 132 insertions(+), 99 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 73c5601..e48bd2f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -312,6 +312,18 @@ do { \
 	COMPLETE_WITH_LIST_CS(list); \
 } while (0)
 
+#define ADDLIST1(p, s1) additional_kw_query(p, text, 1, s1)
+#define ADDLIST2(p, s1, s2) additional_kw_query(p, text, 2, s1, s2)
+#define ADDLIST3(p, s1, s2, s3) additional_kw_query(p, text, 3, s1, s2, s3)
+#define ADDLIST4(p, s1, s2, s3, s4) \
+	additional_kw_query(p, text, 4, s1, s2, s3, s4)
+#define ADDLIST13(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
+	additional_kw_query(p, text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13)
+#define ADDLIST15(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
+	additional_kw_query(p, text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13, s14, s15)
+
 /*
  * Assembly instructions for schema queries
  */
@@ -955,6 +967,7 @@ static char *complete_from_files(const char *text, int state);
 
 static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
+static char *additional_kw_query(char *prefix, const char *ref, int n, ...);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
 
@@ -1438,8 +1451,8 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE */
 	else if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   "UNION SELECT 'IF EXISTS'"
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+			ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
+
 	/* Try ALTER TABLE after removing optional words IF EXISTS*/
 	else if (HeadMatches2("ALTER", "TABLE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1525,7 +1538,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER FOREIGN TABLE */
 	else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 
 	/* Try ALTER FOREIGN TABLE after removing optinal words IF EXISTS */
 	/* Complete for DROP together  */
@@ -1552,8 +1565,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER INDEX */
 	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   "UNION SELECT 'IF EXISTS'"
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+			   ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
 	/* Try ALTER INDEX after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "INDEX") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1588,8 +1600,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER MATERIALIZED VIEW */
 	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   "UNION SELECT 'IF EXISTS'"
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+			   ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
 
 	/* Try ALTER MATERIALIZED VIEW after removing optional words IF EXISTS */
 	else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
@@ -1657,8 +1668,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_constraint_of_type, "IF EXISTS"));
 	}
 	/* Try the same match after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
@@ -1680,7 +1691,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	else if (Matches2("ALTER", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Try ALTER SEQUENCE after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "SEQUENCE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1710,7 +1721,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER VIEW */
 	else if (Matches2("ALTER", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   "UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/*  Try ALTER VIEW after removing optional worlds IF EXISTS */
 	else if (HeadMatches2("ALTER", "VIEW") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1726,8 +1737,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER POLICY */
 	else if (Matches2("ALTER", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_policies, "IF EXISTS"));
 	/* Try ALTER POLICY after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1904,8 +1915,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_constraint_of_table, "IF EXISTS"));
 	}
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
@@ -2099,7 +2110,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
 	else if (Matches1("CLUSTER"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
+								   ADDLIST1("", "VERBOSE"));
 	else if (Matches2("CLUSTER", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 	/* If we have CLUSTER <sth>, then add "USING" */
@@ -2161,7 +2173,7 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION ALL SELECT '('");
+								   ADDLIST1("", "("));
 	/* If we have COPY BINARY, complete with list of tables */
 	else if (Matches2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
@@ -2229,7 +2241,7 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN TABLE */
 	else if (Matches3("CREATE", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
 			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
@@ -2252,15 +2264,12 @@ psql_completion(const char *text, int start, int end)
 	   and existing indexes */
 	else if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'ON'"
-								   " UNION SELECT 'CONCURRENTLY'"
-								   " UNION SELECT 'IF NOT EXISTS'");
+				   ADDLIST3("", "ON", "CONCURRENTLY", "IF NOT EXISTS"));
 	/* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and
 	 * existing indexes */
 	else if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'IF NOT EXISTS'"
-								   " UNION SELECT 'ON'");
+								   ADDLIST2("", "IF NOT EXISTS", "ON"));
 
 	/* Remove optional words "CONCURRENTLY",  "IF NOT EXISTS" */
 	else if (HeadMatches2("CREATE", "INDEX") &&
@@ -2367,8 +2376,8 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE SCHEMA <name> */
 	else if (Matches2("CREATE", "SCHEMA"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas
-							" UNION SELECT 'IF NOT EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_schemas, "IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches2("CREATE", "SCHEMA") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2389,7 +2398,7 @@ psql_completion(const char *text, int start, int end)
 
 	else if (Matches2("CREATE", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 
 	/* Remove optional words here */
 	else if (HeadMatches2("CREATE", "TABLE") &&
@@ -2480,7 +2489,7 @@ psql_completion(const char *text, int start, int end)
 /* CREATE VIEW  */
 	else if (Matches2("CREATE", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches2("CREATE", "VIEW") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2497,7 +2506,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS. VIEW will be
 	 * completed afterwards */
 	else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
@@ -2564,10 +2573,10 @@ psql_completion(const char *text, int start, int end)
 	/* help completing some of the variants */
 	else if (Matches2("DROP", "AGGREGATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	else if (Matches2("DROP", "FUNCTION"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2578,8 +2587,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 	else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_fdws, "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&
 			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
@@ -2588,11 +2597,10 @@ psql_completion(const char *text, int start, int end)
 	/* DROP INDEX */
 	else if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'IF EXISTS'"
-								   " UNION SELECT 'CONCURRENTLY'");
+								   ADDLIST2("", "IF EXISTS","CONCURRENTLY"));
 	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */
 	else if (HeadMatches2("DROP", "INDEX") &&
 			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
@@ -2605,14 +2613,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches2("DROP", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Remove optional words IF EXISTS  */
 	else if (HeadMatches2("DROP", "VIEW") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 false) {} /* FALL THROUGH */
 	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
@@ -2630,8 +2638,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* DROP TRIGGER */
 	else if (Matches2("DROP", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_triggers
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_triggers, "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "TRIGGER") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2649,8 +2657,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	else if (Matches3("DROP", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_event_triggers, "IF EXISTS"));
 	/* Trye the same after removing optional words IF EXISTS */
 	else if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
@@ -2659,8 +2667,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* DROP POLICY */
 	else if (Matches2("DROP", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_policies, "IF EXISTS"));
 	/* Try the same after after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2678,8 +2686,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* DROP RULE */
 	else if (Matches2("DROP", "RULE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_rules
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_rules, "IF EXISTS"));
 	/* DROP RULE <name>, after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "RULE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2710,8 +2718,8 @@ psql_completion(const char *text, int start, int end)
 			/* Completing USER needs special treat */
 			if (pg_strcasecmp(prev_wd, "USER") == 0)
 			{
-				COMPLETE_WITH_QUERY(Query_for_list_of_roles 
-									"UNION SELECT 'MAPPING' UNION SELECT 'IF EXISTS'");
+				COMPLETE_WITH_QUERY(
+					ADDLIST2(Query_for_list_of_roles, "MAPPING", "IF EXISTS"));
 			}
 			else if (ent->query)
 			{
@@ -2724,7 +2732,7 @@ psql_completion(const char *text, int start, int end)
 			}
 			else if (ent->squery)
 				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery,
-										   " UNION SELECT 'IF EXISTS'");
+										   ADDLIST1("", "IF EXISTS"));
 		}
 	}
 	/* Remove optional IF EXISTS from DROP */
@@ -2795,20 +2803,11 @@ psql_completion(const char *text, int start, int end)
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
 	else if (TailMatches1("GRANT|REVOKE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'SELECT'"
-							" UNION SELECT 'INSERT'"
-							" UNION SELECT 'UPDATE'"
-							" UNION SELECT 'DELETE'"
-							" UNION SELECT 'TRUNCATE'"
-							" UNION SELECT 'REFERENCES'"
-							" UNION SELECT 'TRIGGER'"
-							" UNION SELECT 'CREATE'"
-							" UNION SELECT 'CONNECT'"
-							" UNION SELECT 'TEMPORARY'"
-							" UNION SELECT 'EXECUTE'"
-							" UNION SELECT 'USAGE'"
-							" UNION SELECT 'ALL'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST13(Query_for_list_of_roles,
+					  "SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
+					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
+					  "EXECUTE", "USAGE", "ALL"));
 
 	/*
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
@@ -2837,21 +2836,22 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
-								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
-								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
-								   " UNION SELECT 'ALL TABLES IN SCHEMA'"
-								   " UNION SELECT 'DATABASE'"
-								   " UNION SELECT 'DOMAIN'"
-								   " UNION SELECT 'FOREIGN DATA WRAPPER'"
-								   " UNION SELECT 'FOREIGN SERVER'"
-								   " UNION SELECT 'FUNCTION'"
-								   " UNION SELECT 'LANGUAGE'"
-								   " UNION SELECT 'LARGE OBJECT'"
-								   " UNION SELECT 'SCHEMA'"
-								   " UNION SELECT 'SEQUENCE'"
-								   " UNION SELECT 'TABLE'"
-								   " UNION SELECT 'TABLESPACE'"
-								   " UNION SELECT 'TYPE'");
+			   ADDLIST15("",
+						 "ALL FUNCTIONS IN SCHEMA",
+						 "ALL SEQUENCES IN SCHEMA",
+						 "ALL TABLES IN SCHEMA",
+						 "DATABASE",
+						 "DOMAIN",
+						 "FOREIGN DATA WRAPPER",
+						 "FOREIGN SERVER",
+						 "FUNCTION",
+						 "LANGUAGE",
+						 "LARGE OBJECT",
+						 "SCHEMA",
+						 "SEQUENCE",
+						 "TABLE",
+						 "TABLESPACE",
+						 "TYPE"));
 
 	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
@@ -2977,7 +2977,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete LOCK [TABLE] with a list of tables */
 	else if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION SELECT 'TABLE'");
+								   ADDLIST1("", "TABLE"));
 	else if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
@@ -3043,7 +3043,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'CONCURRENTLY'");
+								   ADDLIST1("", "CONCURRENTLY"));
 	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
@@ -3119,7 +3119,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
 	else if (Matches2("SET", "CONSTRAINTS"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema,
+								   ADDLIST1("", "ALL"));
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
 	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
@@ -3131,7 +3132,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
 	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_roles, "DEFAULT"));
 	/* Complete RESET SESSION with AUTHORIZATION */
 	else if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
@@ -3157,10 +3159,11 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST(my_list);
 		}
 		else if (TailMatches2("search_path", "TO|="))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
-								" AND nspname not like 'pg\\_toast%%' "
-								" AND nspname not like 'pg\\_temp%%' "
-								" UNION SELECT 'DEFAULT' ");
+			COMPLETE_WITH_QUERY(
+				ADDLIST1(Query_for_list_of_schemas
+						 " AND nspname not like 'pg\\_toast%%' "
+						 " AND nspname not like 'pg\\_temp%%' ",
+						 "DEFAULT"));
 		else
 		{
 			/* generic, type based, GUC support */
@@ -3229,10 +3232,9 @@ psql_completion(const char *text, int start, int end)
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 false) {} /* FALL THROUGH */
 	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'CURRENT_USER'"
-							" UNION SELECT 'PUBLIC'"
-							" UNION SELECT 'USER'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST3(Query_for_list_of_roles, 
+					 "CURRENT_USER", "PUBLIC", "USER"));
 	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
 	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
@@ -3246,26 +3248,22 @@ psql_completion(const char *text, int start, int end)
  */
 	else if (Matches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'FULL'"
-								   " UNION SELECT 'FREEZE'"
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST4("", "FULL", "FREEZE", "ANALYZE", "VERBOSE"));
 	else if (Matches2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST2("", "ANALYZE", "VERBOSE"));
 	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST1("", "VERBOSE"));
 	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+		   ADDLIST1("", "ANALYZE"));
 	else if (Matches2("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+		   ADDLIST1("", "ANALYZE"));
 	else if (Matches2("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST1("", "VERBOSE"));
 	else if (HeadMatches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
@@ -4030,6 +4028,41 @@ pg_strdup_keyword_case(const char *s, const char *ref)
 	return ret;
 }
 
+/* Construct codelet to append given keywords  */
+static char *
+additional_kw_query(char *prefix, const char *ref, int n, ...)
+{
+	va_list ap;
+	static PQExpBuffer qbuf = NULL;
+	int i;
+
+	if (qbuf == NULL)
+		qbuf = createPQExpBuffer();
+	else
+		resetPQExpBuffer(qbuf);
+
+	appendPQExpBufferStr(qbuf, prefix);
+
+	/* Construct an additional queriy to append keywords */
+	appendPQExpBufferStr(qbuf, " UNION SELECT * FROM (VALUES ");
+
+	va_start(ap, n);
+	for (i = 0 ; i < n ; i++)
+	{
+		char *item = pg_strdup_keyword_case(va_arg(ap, char *), ref);
+		if (i > 0) appendPQExpBufferChar(qbuf, ',');
+		appendPQExpBufferStr(qbuf, "('");
+		appendPQExpBufferStr(qbuf, item);
+		appendPQExpBufferStr(qbuf, "')");
+		pg_free(item);
+	}
+	va_end(ap);
+
+	appendPQExpBufferStr(qbuf, ") as x");
+
+	return qbuf->data;
+}
+
 
 /*
  * escape_string - Escape argument for use as string literal.
-- 
1.8.3.1

#21Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#20)
Re: IF (NOT) EXISTS in psql-completion

2016-03-30 5:43 GMT+02:00 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp

:

Hello,

At Tue, 29 Mar 2016 13:12:06 +0200, Pavel Stehule <pavel.stehule@gmail.com>
wrote in <CAFj8pRCnrpdSqSozg4Y8__2LFyiNqUCE=
KPzFw1+AF_LutmRiQ@mail.gmail.com>

2016-03-29 12:08 GMT+02:00 Kyotaro HORIGUCHI <

As mentioned before, upper-lower problem is an existing
issue. The case of the words in a query result list cannot be
edited since it may contain words that should not be changed,
such as relation names. So we can address it only before issueing
a query but I haven't found simple way to do it.

This is unpleasant. I am sorry. I had very uncomfortable feeling from

this

behave. I am thinking so it should be solvable - you have to convert

only

keyword IF EXISTS or IF NOT EXISTS. Maybe there are not trivial

solution,

but this should be fixed.

I understand that and feel the same. But I don't want to put
puzzling code. Defining a macro enable this by writing as the
following.

puzzle is wrong, but nonconsistent behave is not acceptable

Mmm. Ok, The attched patch, which applies on top of the
IF(NOT)EXIST patch, does this in rather saner appearance, but
needs to run additional C function at runtime and additional some
macros. The function is called up to once per completion, so it
won't be a performance problem.

else if (Matches2("ALTER", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));

or

else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
{
completion_info_charp = prev3_wd;
COMPLETE_WITH_QUERY(
ADDLIST1(Query_for_constraint_of_table, "IF EXISTS"));
}

I think this syntax is acceptable. Only keywords follows the
setting of COMP_KEYWORD_CASE, as Artur suggested.

=# alter table <tab>
ALL IN TABLESPACE pg_catalog. public.
alpha. pg_temp_1. x
IF EXISTS pg_toast.
information_schema. pg_toast_temp_1.
=# alter table i<tab>
if exists
information_schema.sql_features
...
=# alter table if<tab>
=# alter table if exists
======
=# alter table I<tab>
=# alter table IF EXISTS // "information_schema" doesn't match.

Since this is another problem from IF (NOT) EXISTS, this is
in separate form.

What do you think about this?

+1

Regards

Pavel

Show quoted text

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#22Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#18)
Re: IF (NOT) EXISTS in psql-completion

No one should care of this but to make shure..

At Tue, 29 Mar 2016 20:12:03 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <20160329.201203.78219296.horiguchi.kyotaro@lab.ntt.co.jp>

By the way, memory blocks that readline sees are freed by it but
blocks allocated by the additional pstrdup seems abandoned
without being freed. Actually, the address of newly allocated
blocks seems increasing time by time slowly *even without this
patch*..

psql doesn't leak memory. The increment was the result of new
history entry. src/common/exec.c does the following thing,

./common/exec.c:586: putenv(strdup(env_path));
./common/exec.c:597: putenv(strdup(env_path));

valgrind complains that the memory block allocated there is
definitely lost but it seems to be called once in lifetime of a
process.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#23Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#21)
Re: IF (NOT) EXISTS in psql-completion

Hi

...

=# alter table if<tab>
=# alter table if exists
======
=# alter table I<tab>
=# alter table IF EXISTS // "information_schema" doesn't match.

Since this is another problem from IF (NOT) EXISTS, this is
in separate form.

What do you think about this?

+1

The new behave is much better.

I found new warning

tab-complete.c:1438:87: warning: right-hand operand of comma expression
has no effect [-Wunused-value]
gcc -Wall -Wmissing-prototypes -Wpointer-arith
-Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute
-Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard
-g -O2 -I. -I. -I../../../src/interfaces/libpq -I../../../src/include
-D_GNU_SOURCE -I/usr/include/libxml2 -c -o sql_help.o sql_help.c

There is small minor issue - I don't know if it is solvable. Autocomplete
is working only for "if" keyword. When I am writing "if " or "if " or "if
exi" - then autocomplete doesn't work. But this issue is exactly same for
other "multi words" completation like "alter foreign data wrapper". So if
it is fixable, then it can be out of scope this patch.

anything else looks well.

Regards

Pavel

Show quoted text

Regards

Pavel

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#24Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Pavel Stehule (#23)
Re: IF (NOT) EXISTS in psql-completion

Hi,

At Wed, 30 Mar 2016 09:23:49 +0200, Pavel Stehule <pavel.stehule@gmail.com> wrote in <CAFj8pRBVKa6NG4JwZ2QmrO7inudFJws5w0+demVgZZNuF-HUkQ@mail.gmail.com>

Hi

...

=# alter table if<tab>
=# alter table if exists
======
=# alter table I<tab>
=# alter table IF EXISTS // "information_schema" doesn't match.

Since this is another problem from IF (NOT) EXISTS, this is
in separate form.

What do you think about this?

+1

The new behave is much better.

I'm glad to hear that.

I found new warning

tab-complete.c:1438:87: warning: right-hand operand of comma expression
has no effect [-Wunused-value]

Mmm. Google said me that gcc 4.9 does so. I'm using 4.8.5 so I
haven't see the warning.

https://gcc.gnu.org/gcc-4.9/porting_to.html

1436: else if (HeadMatches2("CREATE", "SCHEMA") &&
1437: SHIFT_TO_LAST1("CREATE") &&
1438: false) {} /* FALL THROUGH */

#define SHIFT_TO_LAST1(p1) \
(HEADSHIFT(find_last_index_of(p1, previous_words, previous_words_count)), \
true)

#define HEADSHIFT(n) \
(head_shift += n, true)

expanding the macros the lines at the error will be

else if (HeadMatches2("CREATE", "SCHEMA") &&
(head_shift +=
find_last_index_of("CREATE", previous_words, previous_words_count),
true) &&
false) {} /* FALL THROUGH */

But the right hand value (true) is actually "used" in the
expression (even though not effective). Perhaps (true && false)
was potimized as false and the true is regarded to be unused?
That's stupid.. Using functions instead of macros seems to solve
this but they needed to be wraped by macros as
additional_kw_query(). That's a pain..

Any thougts?

There is small minor issue - I don't know if it is solvable. Autocomplete
is working only for "if" keyword. When I am writing "if " or "if " or "if
exi" - then autocomplete doesn't work. But this issue is exactly same for
other "multi words" completation like "alter foreign data wrapper". So if
it is fixable, then it can be out of scope this patch.

Yes. It can be saved only by adding completion for every word,
as some of the similar completion is doing.

anything else looks well.

Thanks.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#25Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#24)
Re: IF (NOT) EXISTS in psql-completion

2016-03-30 10:34 GMT+02:00 Kyotaro HORIGUCHI <
horiguchi.kyotaro@lab.ntt.co.jp>:

Hi,

At Wed, 30 Mar 2016 09:23:49 +0200, Pavel Stehule <pavel.stehule@gmail.com>
wrote in <
CAFj8pRBVKa6NG4JwZ2QmrO7inudFJws5w0+demVgZZNuF-HUkQ@mail.gmail.com>

Hi

...

=# alter table if<tab>
=# alter table if exists
======
=# alter table I<tab>
=# alter table IF EXISTS // "information_schema" doesn't match.

Since this is another problem from IF (NOT) EXISTS, this is
in separate form.

What do you think about this?

+1

The new behave is much better.

I'm glad to hear that.

I found new warning

tab-complete.c:1438:87: warning: right-hand operand of comma expression
has no effect [-Wunused-value]

Mmm. Google said me that gcc 4.9 does so. I'm using 4.8.5 so I
haven't see the warning.

https://gcc.gnu.org/gcc-4.9/porting_to.html

1436: else if (HeadMatches2("CREATE", "SCHEMA") &&
1437: SHIFT_TO_LAST1("CREATE") &&
1438: false) {} /* FALL THROUGH */

#define SHIFT_TO_LAST1(p1) \
(HEADSHIFT(find_last_index_of(p1, previous_words,

previous_words_count)), \

true)

#define HEADSHIFT(n) \
(head_shift += n, true)

expanding the macros the lines at the error will be

else if (HeadMatches2("CREATE", "SCHEMA") &&
(head_shift +=
find_last_index_of("CREATE", previous_words,
previous_words_count),
true) &&
false) {} /* FALL THROUGH */

But the right hand value (true) is actually "used" in the
expression (even though not effective). Perhaps (true && false)
was potimized as false and the true is regarded to be unused?
That's stupid.. Using functions instead of macros seems to solve
this but they needed to be wraped by macros as
additional_kw_query(). That's a pain..

Any thougts?

There is small minor issue - I don't know if it is solvable. Autocomplete
is working only for "if" keyword. When I am writing "if " or "if " or "if
exi" - then autocomplete doesn't work. But this issue is exactly same for
other "multi words" completation like "alter foreign data wrapper". So

if

it is fixable, then it can be out of scope this patch.

Yes. It can be saved only by adding completion for every word,
as some of the similar completion is doing.

anything else looks well.

Thanks.

please, final patch

Regards

Pavel

Show quoted text

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#26Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Pavel Stehule (#25)
Re: IF (NOT) EXISTS in psql-completion

At Thu, 31 Mar 2016 11:22:20 +0200, Pavel Stehule <pavel.stehule@gmail.com> wrote in <CAFj8pRD7vADuNOiApB8Exwc+C5cCis-rj2dPhvZCwZKgXjb_Xg@mail.gmail.com>

2016-03-30 10:34 GMT+02:00 Kyotaro HORIGUCHI <
horiguchi.kyotaro@lab.ntt.co.jp>:

Hi,

At Wed, 30 Mar 2016 09:23:49 +0200, Pavel Stehule <pavel.stehule@gmail.com>
wrote in <
CAFj8pRBVKa6NG4JwZ2QmrO7inudFJws5w0+demVgZZNuF-HUkQ@mail.gmail.com>

Hi

...

=# alter table if<tab>
=# alter table if exists
======
=# alter table I<tab>
=# alter table IF EXISTS // "information_schema" doesn't match.

Since this is another problem from IF (NOT) EXISTS, this is
in separate form.

What do you think about this?

+1

The new behave is much better.

I'm glad to hear that.

I found new warning

tab-complete.c:1438:87: warning: right-hand operand of comma expression
has no effect [-Wunused-value]

Mmm. Google said me that gcc 4.9 does so. I'm using 4.8.5 so I
haven't see the warning.

https://gcc.gnu.org/gcc-4.9/porting_to.html

1436: else if (HeadMatches2("CREATE", "SCHEMA") &&
1437: SHIFT_TO_LAST1("CREATE") &&
1438: false) {} /* FALL THROUGH */

#define SHIFT_TO_LAST1(p1) \
(HEADSHIFT(find_last_index_of(p1, previous_words,

previous_words_count)), \

true)

#define HEADSHIFT(n) \
(head_shift += n, true)

expanding the macros the lines at the error will be

else if (HeadMatches2("CREATE", "SCHEMA") &&
(head_shift +=
find_last_index_of("CREATE", previous_words,
previous_words_count),
true) &&
false) {} /* FALL THROUGH */

But the right hand value (true) is actually "used" in the
expression (even though not effective). Perhaps (true && false)
was potimized as false and the true is regarded to be unused?
That's stupid.. Using functions instead of macros seems to solve
this but they needed to be wraped by macros as
additional_kw_query(). That's a pain..

Any thougts?

There is small minor issue - I don't know if it is solvable. Autocomplete
is working only for "if" keyword. When I am writing "if " or "if " or "if
exi" - then autocomplete doesn't work. But this issue is exactly same for
other "multi words" completation like "alter foreign data wrapper". So

if

it is fixable, then it can be out of scope this patch.

Yes. It can be saved only by adding completion for every word,
as some of the similar completion is doing.

anything else looks well.

Thanks.

please, final patch

This needs to use gcc 4.9 to address, but CentOS7 doesn't have
devtools-2 repo so now I'm building CentOS6 environment for this
purpose. Please wait for a while.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#27Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#26)
Re: IF (NOT) EXISTS in psql-completion

2016-04-01 4:52 GMT+02:00 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp

:

At Thu, 31 Mar 2016 11:22:20 +0200, Pavel Stehule <pavel.stehule@gmail.com>
wrote in <
CAFj8pRD7vADuNOiApB8Exwc+C5cCis-rj2dPhvZCwZKgXjb_Xg@mail.gmail.com>

2016-03-30 10:34 GMT+02:00 Kyotaro HORIGUCHI <
horiguchi.kyotaro@lab.ntt.co.jp>:

Hi,

At Wed, 30 Mar 2016 09:23:49 +0200, Pavel Stehule <

pavel.stehule@gmail.com>

wrote in <
CAFj8pRBVKa6NG4JwZ2QmrO7inudFJws5w0+demVgZZNuF-HUkQ@mail.gmail.com>

Hi

...

=# alter table if<tab>
=# alter table if exists
======
=# alter table I<tab>
=# alter table IF EXISTS // "information_schema" doesn't match.

Since this is another problem from IF (NOT) EXISTS, this is
in separate form.

What do you think about this?

+1

The new behave is much better.

I'm glad to hear that.

I found new warning

tab-complete.c:1438:87: warning: right-hand operand of comma

expression

has no effect [-Wunused-value]

Mmm. Google said me that gcc 4.9 does so. I'm using 4.8.5 so I
haven't see the warning.

https://gcc.gnu.org/gcc-4.9/porting_to.html

1436: else if (HeadMatches2("CREATE", "SCHEMA") &&
1437: SHIFT_TO_LAST1("CREATE") &&
1438: false) {} /* FALL THROUGH */

#define SHIFT_TO_LAST1(p1) \
(HEADSHIFT(find_last_index_of(p1, previous_words,

previous_words_count)), \

true)

#define HEADSHIFT(n) \
(head_shift += n, true)

expanding the macros the lines at the error will be

else if (HeadMatches2("CREATE", "SCHEMA") &&
(head_shift +=
find_last_index_of("CREATE", previous_words,
previous_words_count),
true) &&
false) {} /* FALL THROUGH */

But the right hand value (true) is actually "used" in the
expression (even though not effective). Perhaps (true && false)
was potimized as false and the true is regarded to be unused?
That's stupid.. Using functions instead of macros seems to solve
this but they needed to be wraped by macros as
additional_kw_query(). That's a pain..

Any thougts?

There is small minor issue - I don't know if it is solvable.

Autocomplete

is working only for "if" keyword. When I am writing "if " or "if "

or "if

exi" - then autocomplete doesn't work. But this issue is exactly

same for

other "multi words" completation like "alter foreign data wrapper".

So

if

it is fixable, then it can be out of scope this patch.

Yes. It can be saved only by adding completion for every word,
as some of the similar completion is doing.

anything else looks well.

Thanks.

please, final patch

This needs to use gcc 4.9 to address, but CentOS7 doesn't have
devtools-2 repo so now I'm building CentOS6 environment for this
purpose. Please wait for a while.

sure, ok

regards

Pavel

Show quoted text

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#28Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#26)
3 attachment(s)
Re: IF (NOT) EXISTS in psql-completion

Hello, sorry for being a bit late.
The attatched are the new version of the patch.. set.

1. 0001-Suggest-IF-NOT-EXISTS-for-tab-completion-of-psql.patch

Adds IF (NOT) EXISTS completion. It doesn't fix the issue that
the case of additional keywords don't follow the input.

2. 0002-Make-added-keywords-for-completion-queries-follow-to.patch

Fixes the case-don't-follow issue by introducing a new macro set
ADDLISTn(). This leaves the issue for keywords along with
attributes.

3. 0003-Make-COMPLETE_WITH_ATTR-to-accept-additional-keyword.patch

Fixes the issue left after 0002 patch.
This patch does the following
things.

1. Change completion_charp from const char * to PQExpBuffer.

2. Chnage COMPLETE_WITH_QUERY and COMPLETE_WITH_ATTR to accept
an expression instead of string literal.

3. Replace all additional keyword lists in psql_copmletion with
ADDLISTn() expression.

At Fri, 01 Apr 2016 11:52:03 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <20160401.115203.98896697.horiguchi.kyotaro@lab.ntt.co.jp>

I found new warning

tab-complete.c:1438:87: warning: right-hand operand of comma expression
has no effect [-Wunused-value]

Mmm. Google said me that gcc 4.9 does so. I'm using 4.8.5 so I
haven't see the warning.

https://gcc.gnu.org/gcc-4.9/porting_to.html

1436: else if (HeadMatches2("CREATE", "SCHEMA") &&
1437: SHIFT_TO_LAST1("CREATE") &&
1438: false) {} /* FALL THROUGH */

...

But the right hand value (true) is actually "used" in the
expression (even though not effective). Perhaps (true && false)
was potimized as false and the true is regarded to be unused?
That's stupid.. Using functions instead of macros seems to solve
this but they needed to be wraped by macros as
additional_kw_query(). That's a pain..

...

This needs to use gcc 4.9 to address, but CentOS7 doesn't have
devtools-2 repo so now I'm building CentOS6 environment for this
purpose. Please wait for a while.

Finally I settled it by replacing comma expression with logical
OR or AND expresssion. gcc 4.9 compains for some unused variables
in flex output but it is the another issue.

I forgot to address COMPLETE_WITH_ATTTR but it needed an overhaul
of some macros and changing the type of completion_charp. The
third patch does it but it might be unacceptable..

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

0001-Suggest-IF-NOT-EXISTS-for-tab-completion-of-psql.patchtext/x-patch; charset=us-asciiDownload
From 3c2bbb46749cffc2fd78cdbfdc181128f99993c1 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 5 Feb 2016 16:50:35 +0900
Subject: [PATCH 1/3] Suggest IF (NOT) EXISTS for tab-completion of psql

This patch lets psql to suggest "IF (NOT) EXISTS". Addition to that,
since this patch introduces some mechanism for syntactical robustness,
it allows psql completion to omit some optional part on matching.
---
 src/bin/psql/tab-complete.c | 625 ++++++++++++++++++++++++++++++++++++--------
 1 file changed, 517 insertions(+), 108 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index eb592bb..a0808cf 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -656,6 +656,10 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "   FROM pg_catalog.pg_roles "\
 "  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"
 
+#define Query_for_list_of_rules \
+"SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules "\
+" WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"
+
 #define Query_for_list_of_grant_roles \
 " SELECT pg_catalog.quote_ident(rolname) "\
 "   FROM pg_catalog.pg_roles "\
@@ -763,6 +767,11 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "SELECT pg_catalog.quote_ident(tmplname) FROM pg_catalog.pg_ts_template "\
 " WHERE substring(pg_catalog.quote_ident(tmplname),1,%d)='%s'"
 
+#define Query_for_list_of_triggers \
+"SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger "\
+" WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s' AND "\
+"       NOT tgisinternal"
+
 #define Query_for_list_of_fdws \
 " SELECT pg_catalog.quote_ident(fdwname) "\
 "   FROM pg_catalog.pg_foreign_data_wrapper "\
@@ -907,7 +916,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
 	{"POLICY", NULL, NULL},
 	{"ROLE", Query_for_list_of_roles},
-	{"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"},
+	{"RULE", Query_for_list_of_rules},
 	{"SCHEMA", Query_for_list_of_schemas},
 	{"SEQUENCE", NULL, &Query_for_list_of_sequences},
 	{"SERVER", Query_for_list_of_servers},
@@ -916,7 +925,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"TEMP", NULL, NULL, THING_NO_DROP},		/* for CREATE TEMP TABLE ... */
 	{"TEMPLATE", Query_for_list_of_ts_templates, NULL, THING_NO_SHOW},
 	{"TEXT SEARCH", NULL, NULL},
-	{"TRIGGER", "SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s' AND NOT tgisinternal"},
+	{"TRIGGER", Query_for_list_of_triggers},
 	{"TYPE", NULL, &Query_for_list_of_datatypes},
 	{"UNIQUE", NULL, NULL, THING_NO_DROP},		/* for CREATE UNIQUE INDEX ... */
 	{"UNLOGGED", NULL, NULL, THING_NO_DROP},	/* for CREATE UNLOGGED TABLE
@@ -945,6 +954,7 @@ static char **complete_from_variables(const char *text,
 					const char *prefix, const char *suffix, bool need_value);
 static char *complete_from_files(const char *text, int state);
 
+static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
@@ -953,6 +963,7 @@ static char **get_previous_words(int point, char **buffer, int *nwords);
 
 static char *get_guctype(const char *varname);
 
+static const pgsql_thing_t *find_thing_entry(char *word);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -1108,6 +1119,9 @@ psql_completion(const char *text, int start, int end)
 	/* The number of words found on the input line. */
 	int			previous_words_count;
 
+	/* The number of prefixing words to be ignored */
+	int			head_shift = 0;
+
 	/*
 	 * For compactness, we use these macros to reference previous_words[].
 	 * Caution: do not access a previous_words[] entry without having checked
@@ -1125,31 +1139,73 @@ psql_completion(const char *text, int start, int end)
 #define prev8_wd  (previous_words[7])
 #define prev9_wd  (previous_words[8])
 
+	/* Move the position of the beginning word for matching macros.  */
+#define HEADSHIFT(n) \
+	((head_shift += n) || true)
+
+	/* Return the number of stored words counting head shift */
+#define WORD_COUNT() (previous_words_count - head_shift)
+
+	/* Return the true index in previous_words for index from the beginning */
+#define HEAD_INDEX(n) \
+	(previous_words_count - head_shift - (n))
+
+	/*
+	 * remove n words from current shifted position, see MidMatchAndRevmove2
+	 * for the reason for the return value
+	 */
+#define COLLAPSE(n) \
+	((memmove(previous_words + HEAD_INDEX(n), previous_words + HEAD_INDEX(0), \
+			 sizeof(char *) * head_shift), \
+	 previous_words_count -= (n)) && false)
+
+	/*
+	 * Find the position the specified word occurs last and shift to there.
+	 * This is used to ignore the words before there.
+	 */
+#define SHIFT_TO_LAST1(p1) \
+	(HEADSHIFT(find_last_index_of(p1, previous_words, previous_words_count))|| \
+	 true)
+
+	/*
+	 * Remove the specified words if they match from the sth word in
+	 * previous_words. This is a bit tricky, COLLAPSE is skipped when
+	 * HeadMatches failed but the last HEADSHIFT anyway should be done.
+	 */
+#define MidMatchAndRemove1(s, p1) \
+	((HEADSHIFT(s) && HeadMatches1(p1) && COLLAPSE(1)) || HEADSHIFT(-s))
+
+#define MidMatchAndRemove2(s, p1, p2) \
+	((HEADSHIFT(s) && HeadMatches2(p1, p2) && COLLAPSE(2)) || HEADSHIFT(-s))
+
+#define MidMatchAndRemove3(s, p1, p2, p3)									\
+	((HEADSHIFT(s) && HeadMatches3(p1, p2, p3) && COLLAPSE(3)) || HEADSHIFT(-s))
+
 	/* Macros for matching the last N words before point, case-insensitively. */
 #define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches(p1, prev_wd))
 
 #define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd))
 
 #define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
+	(WORD_COUNT() >= 3 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd))
 
 #define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
+	(WORD_COUNT() >= 4 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
 	 word_matches(p4, prev4_wd))
 
 #define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
+	(WORD_COUNT() >= 5 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1157,7 +1213,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p5, prev5_wd))
 
 #define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
+	(WORD_COUNT() >= 6 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1166,7 +1222,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p6, prev6_wd))
 
 #define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
+	(WORD_COUNT() >= 7 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1176,7 +1232,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p7, prev7_wd))
 
 #define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
+	(WORD_COUNT() >= 8 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1187,7 +1243,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p8, prev8_wd))
 
 #define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
+	(WORD_COUNT() >= 9 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1200,43 +1256,43 @@ psql_completion(const char *text, int start, int end)
 
 	/* Macros for matching the last N words before point, case-sensitively. */
 #define TailMatchesCS1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches_cs(p1, prev_wd))
 #define TailMatchesCS2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches_cs(p1, prev_wd) && \
 	 word_matches_cs(p2, prev2_wd))
 
 	/*
-	 * Macros for matching N words beginning at the start of the line,
+	 * Macros for matching N words exactly to the line,
 	 * case-insensitively.
 	 */
 #define Matches1(p1) \
-	(previous_words_count == 1 && \
+	(WORD_COUNT() == 1 && \
 	 TailMatches1(p1))
 #define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
+	(WORD_COUNT() == 2 && \
 	 TailMatches2(p1, p2))
 #define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
+	(WORD_COUNT() == 3 && \
 	 TailMatches3(p1, p2, p3))
 #define Matches4(p1, p2, p3, p4) \
-	(previous_words_count == 4 && \
+	(WORD_COUNT() == 4 && \
 	 TailMatches4(p1, p2, p3, p4))
 #define Matches5(p1, p2, p3, p4, p5) \
-	(previous_words_count == 5 && \
+	(WORD_COUNT() == 5 && \
 	 TailMatches5(p1, p2, p3, p4, p5))
 #define Matches6(p1, p2, p3, p4, p5, p6) \
-	(previous_words_count == 6 && \
+	(WORD_COUNT() == 6 && \
 	 TailMatches6(p1, p2, p3, p4, p5, p6))
 #define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(previous_words_count == 7 && \
+	(WORD_COUNT() == 7 && \
 	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
 #define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(previous_words_count == 8 && \
+	(WORD_COUNT() == 8 && \
 	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
 #define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(previous_words_count == 9 && \
+	(WORD_COUNT() == 9 && \
 	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
 
 	/*
@@ -1244,19 +1300,53 @@ psql_completion(const char *text, int start, int end)
 	 * what is after them, case-insensitively.
 	 */
 #define HeadMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]))
+	(HEAD_INDEX(1) >=0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]))
 
 #define HeadMatches2(p1, p2) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]))
+	(HEAD_INDEX(2) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]))
 
 #define HeadMatches3(p1, p2, p3) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]) && \
-	 word_matches(p3, previous_words[previous_words_count - 3]))
+	(HEAD_INDEX(3) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]))
+
+#define HeadMatches4(p1, p2, p3, p4) \
+	(HEAD_INDEX(4) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]))
+
+#define HeadMatches5(p1, p2, p3, p4, p5) \
+	(HEAD_INDEX(5) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]))
+
+#define HeadMatches6(p1, p2, p3, p4, p5, p6)		\
+	(HEAD_INDEX(6) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]) && \
+	 word_matches(p6, previous_words[HEAD_INDEX(6)]))
+
+#define HeadMatches7(p1, p2, p3, p4, p5, p6, p7)	\
+	(HEAD_INDEX(7) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]) && \
+	 word_matches(p6, previous_words[HEAD_INDEX(6)]) && \
+	 word_matches(p7, previous_words[HEAD_INDEX(7)]))
 
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
@@ -1328,9 +1418,16 @@ psql_completion(const char *text, int start, int end)
 	else if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
+	/*
+	 * If this is in CREATE SCHEMA, seek to the last CREATE and regard it as
+	 * current command to complete.
+	 */
+	else if (HeadMatches2("CREATE", "SCHEMA") &&
+			 SHIFT_TO_LAST1("CREATE") &&
+			 false) {} /* FALL THROUGH */
 /* CREATE */
 	/* complete with something you can create */
-	else if (TailMatches1("CREATE"))
+	else if (Matches1("CREATE"))
 		matches = completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
@@ -1343,7 +1440,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE */
 	else if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
+	/* ALTER TABLE after removing optional words IF EXISTS*/
+	else if (HeadMatches2("ALTER", "TABLE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("ALTER", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER something */
 	else if (Matches1("ALTER"))
@@ -1421,6 +1524,17 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
+	/* ALTER FOREIGN TABLE */
+	else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
+								   " UNION SELECT 'IF EXISTS'");
+
+	/* ALTER|DROP FOREIGN TABLE after removing optinal words IF EXISTS */
+	else if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+
 	/* ALTER FOREIGN TABLE <name> */
 	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
@@ -1432,10 +1546,21 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_FOREIGN_TABLE);
 	}
 
+	/* ALTER FOREIGN TABLE xxx RENAME */
+	else if (Matches5("ALTER", "FOREIGN", "TABLE", MatchAny, "RENAME"))
+		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'TO'");
+
 	/* ALTER INDEX */
 	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
+	/* ALTER INDEX after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "INDEX") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("ALTER", "INDEX"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+
 	/* ALTER INDEX <name> */
 	else if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
@@ -1464,8 +1589,15 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER MATERIALIZED VIEW */
 	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
+	/* ALTER MATERIALIZED VIEW with name after removing optional words */
+	else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("ALTER", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+
 	/* ALTER USER,ROLE <name> */
 	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
@@ -1516,8 +1648,23 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER DOMAIN <sth> DROP */
 	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
-	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	/* ALTER DOMAIN <sth> RENAME|VALIDATE CONSTRAINT */
+	else if (Matches5("ALTER", "DOMAIN", MatchAny, "RENAME|VALIDATE", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+	}
+	/* ALTER DOMAIN <sth> DROP CONSTRAINT */
+	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type
+							"UNION SELECT 'IF EXISTS'");
+	}
+	/* Try the same match after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
@@ -1532,8 +1679,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER DOMAIN <sth> SET */
 	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
-	/* ALTER SEQUENCE <name> */
-	else if (Matches3("ALTER", "SEQUENCE", MatchAny))
+	else if (Matches2("ALTER", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
+								   " UNION SELECT 'IF EXISTS'");
+	/* ALTER SEQUENCE with name after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "SEQUENCE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1556,8 +1708,14 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER SYSTEM SET|RESET <name> */
 	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
-	/* ALTER VIEW <name> */
-	else if (Matches3("ALTER", "VIEW", MatchAny))
+	/* ALTER VIEW */
+	else if (Matches2("ALTER", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   "UNION SELECT 'IF EXISTS'");
+	/* ALTER VIEW <name> with subcommands after removing optional worlds */
+	else if (HeadMatches2("ALTER", "VIEW") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
@@ -1565,11 +1723,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
-	/* ALTER POLICY <name> */
+	/* ALTER POLICY */
 	else if (Matches2("ALTER", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
-	/* ALTER POLICY <name> ON */
-	else if (Matches3("ALTER", "POLICY", MatchAny))
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies
+							"UNION SELECT 'IF EXISTS'");
+	/* ALTER POLICY <name> with ON after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "POLICY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
 	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
@@ -1693,8 +1854,10 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
-	/* ALTER TABLE xxx RENAME yyy */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
+	/* ALTER [FOREIGN] TABLE xxx RENAME yyy */
+	else if ((HeadMatches2("ALTER", "TABLE") ||
+			  HeadMatches3("ALTER", "FOREIGN", "TABLE")) &&
+			 TailMatches2("RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
@@ -1704,15 +1867,47 @@ psql_completion(const char *text, int start, int end)
 	/* If we have ALTER TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
+	/*  ALTER TABLE DROP COLUMN may take IF EXISTS */
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+	/* ALTER TABLE <name> with ADD/ALTER/DROP after removing optional words */
+	else if (HeadMatches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP") &&
+			 MidMatchAndRemove1(4, "COLUMN") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 Matches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP"))
+		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
-	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
+	 * If we have ALTER TABLE <sth> ALTER|RENAME|VALIDATE CONSTRAINT,
+	 * provide list of constraints
+	 */
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME|VALIDATE", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+	}
+	/*
+	 * If we have ALTER TABLE <sth> DROP CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table
+							"UNION SELECT 'IF EXISTS'");
+	}
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
@@ -1834,8 +2029,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 	else if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING",
-							"DROP MAPPING FOR",
-							"OWNER TO", "RENAME TO", "SET SCHEMA");
+							"DROP MAPPING",	"OWNER TO", "RENAME TO", "SET SCHEMA");
+	else if (Matches7("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny, "DROP", "MAPPING"))
+		COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR");
+	/* Remove optional words IF EXISTS */
+	else if (HeadMatches7("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny, "DROP", "MAPPING") &&
+			 MidMatchAndRemove2(7, "IF", "EXISTS") &&
+			 false) {} /* Nothing to do for now */
 
 	/* complete ALTER TYPE <foo> with actions */
 	else if (Matches3("ALTER", "TYPE", MatchAny))
@@ -1845,6 +2045,12 @@ psql_completion(const char *text, int start, int end)
 	/* complete ALTER TYPE <foo> ADD with actions */
 	else if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
+	else if (Matches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE"))
+		COMPLETE_WITH_LIST2("IF NOT EXISTS", "");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE") &&
+			 MidMatchAndRemove3(5, "IF", "NOT", "EXISTS") &&
+			 false) {} /* Nothing to do for now */
 	/* ALTER TYPE <foo> RENAME	*/
 	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "TO");
@@ -1856,10 +2062,15 @@ psql_completion(const char *text, int start, int end)
 	 * If we have ALTER TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list
 	 * of attributes
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
+	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
+	else if (Matches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE"))
+		COMPLETE_WITH_ATTR(prev3_wd, " UNION SELECT 'IF EXISTS'");
+	/* Remove optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
+			 Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* complete ALTER GROUP <foo> */
 	else if (Matches3("ALTER", "GROUP", MatchAny))
@@ -2002,6 +2213,12 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	else if (Matches2("CREATE", "EXTENSION"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions
+							" UNION SELECT 'IF NOT EXISTS'");
+	/* Try the same after removing optional words IF NOT EXISTS */
+	else if (HeadMatches2("CREATE", "EXTENSION") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
 	else if (Matches3("CREATE", "EXTENSION", MatchAny))
@@ -2017,6 +2234,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
+	/* CREATE FOREIGN TABLE */
+	else if (Matches3("CREATE", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
+			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
@@ -2025,23 +2250,37 @@ psql_completion(const char *text, int start, int end)
 	/* First off we complete CREATE UNIQUE with "INDEX" */
 	else if (TailMatches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
-	/* If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY",
+
+	/* Remove optional word UNIQUE from CREATE UNIQUE INDEX */
+	/* If we have CREATE INDEX, then add "ON", "CONCURRENTLY" or IF NOT EXISTS,
 	   and existing indexes */
-	else if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	else if (HeadMatches3("CREATE", MatchAny, "INDEX") &&
+			 MidMatchAndRemove1(1, "UNIQUE") &&
+			 Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
-								   " UNION SELECT 'CONCURRENTLY'");
-	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	else if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
-			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-	/* Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing indexes */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+								   " UNION SELECT 'CONCURRENTLY'"
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and
+	 * existing indexes */
+	else if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF NOT EXISTS'"
 								   " UNION SELECT 'ON'");
-	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
-			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
+
+	/* Remove optional words "CONCURRENTLY",  "IF NOT EXISTS" */
+	else if (HeadMatches2("CREATE", "INDEX") &&
+			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
+	/* Complete CREATE INDEX [<name>] ON with a list of tables */
+	else if (Matches4("CREATE", "INDEX", MatchAny, "ON") ||
+			 Matches3("CREATE", "INDEX", "ON"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+
+	/* Complete CREATE INDEX <sth> with "ON" */
+	else if (Matches3("CREATE", "INDEX", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/*
@@ -2049,17 +2288,16 @@ psql_completion(const char *text, int start, int end)
 	 * should really be in parens)
 	 */
 	else if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-			 TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+			 TailMatches3("INDEX", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-			 TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
+	else if (Matches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
+			 Matches4("INDEX", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* same if you put in USING */
 	else if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	else if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
-			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
+	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 	else if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
@@ -2113,27 +2351,52 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-/* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	else if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
-			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
+/* CREATE SEQUENCE, removing optional words TEMPORARY/TEMP */
+	else if (HeadMatches3("CREATE", MatchAny, "SEQUENCE") &&
+			 MidMatchAndRemove1(1, "TEMP|TEMPORARY") &&
+			 Matches2("CREATE", "SEQUENCE"))
+		COMPLETE_WITH_LIST2("IF NOT EXISTS", "");
+	else if(HeadMatches2("CREATE", "SEQUENCE") &&
+			MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			Matches3("CREATE", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	else if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
-		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
+	else if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
 	else if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
-/* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE SCHEMA <name> */
+	else if (Matches2("CREATE", "SCHEMA"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+							" UNION SELECT 'IF NOT EXISTS'");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches2("CREATE", "SCHEMA") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH*/
+
+/* CREATE TABLE  */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	else if (Matches2("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	else if (TailMatches2("CREATE", "UNLOGGED"))
+	else if (Matches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
+	/* CREATE TABLE with name after removing optional words */
+	else if (HeadMatches3("CREATE", MatchAny, "TABLE") &&
+			 MidMatchAndRemove1(1, "TEMP|TEMPORARY|UNLOGGED") &&
+			 Matches2("CREATE", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+								   " UNION SELECT 'IF NOT EXISTS'");
+
+	/* Remove optional words here */
+	else if (HeadMatches2("CREATE", "TABLE") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
@@ -2147,18 +2410,18 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
-/* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	else if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	else if (Matches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	else if (Matches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	else if (Matches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+	else if (Matches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
 	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
@@ -2215,9 +2478,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
-/* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	/* Complete CREATE VIEW <name> with AS */
-	else if (TailMatches3("CREATE", "VIEW", MatchAny))
+/* CREATE VIEW  */
+	else if (Matches2("CREATE", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* CREATE VIEW <name> with AS after removing optional words */
+	else if (HeadMatches2("CREATE", "VIEW") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
 	else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
@@ -2226,6 +2494,15 @@ psql_completion(const char *text, int start, int end)
 /* CREATE MATERIALIZED VIEW */
 	else if (Matches2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+	else if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Try the same after removing optional words IF NOT EXISTS. VIEW will be
+	 * completed afterwards */
+	else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
+			 Matches3("CREATE", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
 	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
@@ -2285,28 +2562,61 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
+	else if (Matches2("DROP", "AGGREGATE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates,
+								   " UNION SELECT 'IF EXISTS'");
+	else if (Matches2("DROP", "FUNCTION"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
 	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
+	else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 
 	/* DROP INDEX */
 	else if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF EXISTS'"
 								   " UNION SELECT 'CONCURRENTLY'");
 	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches3("DROP", "INDEX", MatchAny))
-		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	else if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */
+	else if (HeadMatches2("DROP", "INDEX") &&
+			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
 	else if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+	else if (Matches2("DROP", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Remove optional words IF EXISTS  */
+	else if (HeadMatches2("DROP", "VIEW") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
 	/* DROP OWNED BY */
@@ -2319,7 +2629,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (Matches3("DROP", "TRIGGER", MatchAny))
+	else if (Matches2("DROP", "TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_triggers
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "TRIGGER") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
@@ -2333,15 +2649,27 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	else if (Matches3("DROP", "EVENT", "TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers
+							" UNION SELECT 'IF EXISTS'");
+	/* Trye the same after removing optional words IF EXISTS */
+	else if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
-	/* DROP POLICY <name>  */
+	/* DROP POLICY */
 	else if (Matches2("DROP", "POLICY"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "POLICY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
-	/* DROP POLICY <name> ON */
+	/* DROP POLICY <name> */
 	else if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	/* DROP POLICY <name> ON <table> */
+	/* DROP POLICY <name> ON */
 	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
@@ -2349,7 +2677,13 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* DROP RULE */
-	else if (Matches3("DROP", "RULE", MatchAny))
+	else if (Matches2("DROP", "RULE"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_rules
+							"UNION SELECT 'IF EXISTS'");
+	/* DROP RULE <name>, after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "RULE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
@@ -2359,6 +2693,52 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
+	/* IF EXISTS processing for DROP everything else */
+	else if (Matches2("DROP",
+					  "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|"
+					  "GROUP|LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|"
+					  "SERVER|TABLE|TABLESPACE|TYPE|USER") ||
+			 Matches4("DROP", "TEXT", "SEARCH",
+					  "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE"))
+
+	{
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
+		char *addition = " UNION SELECT 'IF EXISTS'";
+
+		if (ent)
+		{
+			/* Completing USER needs special treat */
+			if (pg_strcasecmp(prev_wd, "USER") == 0)
+			{
+				COMPLETE_WITH_QUERY(Query_for_list_of_roles 
+									"UNION SELECT 'MAPPING' UNION SELECT 'IF EXISTS'");
+			}
+			else if (ent->query)
+			{
+				char *buf = pg_malloc(strlen(ent->query) +
+									  strlen(addition) + 1);
+				strcpy(buf, ent->query);
+				strcat(buf, addition);
+				COMPLETE_WITH_QUERY(buf);
+				free(buf);
+			}
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery,
+										   " UNION SELECT 'IF EXISTS'");
+		}
+	}
+	/* Remove optional IF EXISTS from DROP */
+	else if (HeadMatches2("DROP",
+						  "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|GROUP|"
+						  "LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|SERVER|"
+						  "TABLE|TABLESPACE|TYPE|USER") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+	else if (HeadMatches4("DROP", "TEXT", "SEARCH",
+						  "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
 /* EXECUTE */
 	else if (Matches1("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
@@ -2405,8 +2785,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /* FOREIGN TABLE */
-	else if (TailMatches2("FOREIGN", "TABLE") &&
-			 !TailMatches3("CREATE", MatchAny, MatchAny))
+	else if (TailMatches2("FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* FOREIGN SERVER */
@@ -2842,8 +3221,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
+	else if (Matches3("ALTER|CREATE", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
+	else if (Matches3("DROP", "USER", "MAPPING"))
+		COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR");
+	else if (HeadMatches3("DROP", "USER", "MAPPING") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
@@ -3071,19 +3455,14 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else
 	{
-		int			i;
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
 
-		for (i = 0; words_after_create[i].name; i++)
+		if (ent)
 		{
-			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
-			{
-				if (words_after_create[i].query)
-					COMPLETE_WITH_QUERY(words_after_create[i].query);
-				else if (words_after_create[i].squery)
-					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
-											   NULL);
-				break;
-			}
+			if (ent->query)
+				COMPLETE_WITH_QUERY(ent->query);
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, NULL);
 		}
 	}
 
@@ -3608,6 +3987,18 @@ complete_from_files(const char *text, int state)
 
 /* HELPER FUNCTIONS */
 
+/*
+ * Return the index (reverse to the index of previous_words) of the tailmost
+ * (topmost in the array) appearance of w.
+ */
+static int
+find_last_index_of(char *w, char **previous_words, int len)
+{
+	int i;
+
+	for (i = 0 ; i < len && !word_matches(w, previous_words[i]) ; i++);
+	return i < len ? (len - i - 1) : 0;
+}
 
 /*
  * Make a pg_strdup copy of s and convert the case according to
@@ -3854,6 +4245,24 @@ get_guctype(const char *varname)
 	return guctype;
 }
 
+/*
+ * Finds the entry in words_after_create[] that matches the word.
+ * NULL if not found.
+ */
+static const pgsql_thing_t *
+find_thing_entry(char *word)
+{
+	int			i;
+
+	for (i = 0; words_after_create[i].name; i++)
+	{
+		if (pg_strcasecmp(word, words_after_create[i].name) == 0)
+			return words_after_create + i;
+	}
+
+	return NULL;
+}
+
 #ifdef NOT_USED
 
 /*
-- 
1.8.3.1

0002-Make-added-keywords-for-completion-queries-follow-to.patchtext/x-patch; charset=us-asciiDownload
From 7b1785289fe8c347ee8d46a91ab1d5a205401149 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Tue, 29 Mar 2016 19:01:13 +0900
Subject: [PATCH 2/3] Make added keywords for completion queries follow to
 input.

Currently some keyword shown along with database objects are always in
upper case. This patch changes the behavior so that the case of the
additional keywords follow the setting of COMP_KEYWORD_CASE.
Only COMPLETE_WITH_QUERY/COMPLETE_WITH_SCHEMA_QUERY are fixed.
---
 src/bin/psql/tab-complete.c | 231 +++++++++++++++++++++++++-------------------
 1 file changed, 132 insertions(+), 99 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index a0808cf..ea8694c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -312,6 +312,18 @@ do { \
 	COMPLETE_WITH_LIST_CS(list); \
 } while (0)
 
+#define ADDLIST1(p, s1) additional_kw_query(p, text, 1, s1)
+#define ADDLIST2(p, s1, s2) additional_kw_query(p, text, 2, s1, s2)
+#define ADDLIST3(p, s1, s2, s3) additional_kw_query(p, text, 3, s1, s2, s3)
+#define ADDLIST4(p, s1, s2, s3, s4) \
+	additional_kw_query(p, text, 4, s1, s2, s3, s4)
+#define ADDLIST13(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
+	additional_kw_query(p, text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13)
+#define ADDLIST15(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
+	additional_kw_query(p, text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13, s14, s15)
+
 /*
  * Assembly instructions for schema queries
  */
@@ -956,6 +968,7 @@ static char *complete_from_files(const char *text, int state);
 
 static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
+static char *additional_kw_query(char *prefix, const char *ref, int n, ...);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
 
@@ -1440,8 +1453,8 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE */
 	else if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   "UNION SELECT 'IF EXISTS'"
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+			ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
+
 	/* ALTER TABLE after removing optional words IF EXISTS*/
 	else if (HeadMatches2("ALTER", "TABLE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1527,7 +1540,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER FOREIGN TABLE */
 	else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 
 	/* ALTER|DROP FOREIGN TABLE after removing optinal words IF EXISTS */
 	else if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
@@ -1553,8 +1566,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER INDEX */
 	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   "UNION SELECT 'IF EXISTS'"
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+			   ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
 	/* ALTER INDEX after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "INDEX") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1589,8 +1601,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER MATERIALIZED VIEW */
 	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   "UNION SELECT 'IF EXISTS'"
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+			   ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
 
 	/* ALTER MATERIALIZED VIEW with name after removing optional words */
 	else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
@@ -1658,8 +1669,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_constraint_of_type, "IF EXISTS"));
 	}
 	/* Try the same match after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
@@ -1681,7 +1692,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	else if (Matches2("ALTER", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* ALTER SEQUENCE with name after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "SEQUENCE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1711,7 +1722,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER VIEW */
 	else if (Matches2("ALTER", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   "UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* ALTER VIEW <name> with subcommands after removing optional worlds */
 	else if (HeadMatches2("ALTER", "VIEW") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1725,8 +1736,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER POLICY */
 	else if (Matches2("ALTER", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_policies, "IF EXISTS"));
 	/* ALTER POLICY <name> with ON after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1901,8 +1912,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_constraint_of_table, "IF EXISTS"));
 	}
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
@@ -2096,7 +2107,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
 	else if (Matches1("CLUSTER"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
+								   ADDLIST1("", "VERBOSE"));
 	else if (Matches2("CLUSTER", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 	/* If we have CLUSTER <sth>, then add "USING" */
@@ -2158,7 +2170,7 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION ALL SELECT '('");
+								   ADDLIST1("", "("));
 	/* If we have COPY BINARY, complete with list of tables */
 	else if (Matches2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
@@ -2237,7 +2249,7 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN TABLE */
 	else if (Matches3("CREATE", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
 			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
@@ -2258,15 +2270,12 @@ psql_completion(const char *text, int start, int end)
 			 MidMatchAndRemove1(1, "UNIQUE") &&
 			 Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'ON'"
-								   " UNION SELECT 'CONCURRENTLY'"
-								   " UNION SELECT 'IF NOT EXISTS'");
+				   ADDLIST3("", "ON", "CONCURRENTLY", "IF NOT EXISTS"));
 	/* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and
 	 * existing indexes */
 	else if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'IF NOT EXISTS'"
-								   " UNION SELECT 'ON'");
+								   ADDLIST2("", "IF NOT EXISTS", "ON"));
 
 	/* Remove optional words "CONCURRENTLY",  "IF NOT EXISTS" */
 	else if (HeadMatches2("CREATE", "INDEX") &&
@@ -2370,8 +2379,8 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE SCHEMA <name> */
 	else if (Matches2("CREATE", "SCHEMA"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas
-							" UNION SELECT 'IF NOT EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_schemas, "IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches2("CREATE", "SCHEMA") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2390,7 +2399,7 @@ psql_completion(const char *text, int start, int end)
 			 MidMatchAndRemove1(1, "TEMP|TEMPORARY|UNLOGGED") &&
 			 Matches2("CREATE", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 
 	/* Remove optional words here */
 	else if (HeadMatches2("CREATE", "TABLE") &&
@@ -2481,7 +2490,7 @@ psql_completion(const char *text, int start, int end)
 /* CREATE VIEW  */
 	else if (Matches2("CREATE", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 	/* CREATE VIEW <name> with AS after removing optional words */
 	else if (HeadMatches2("CREATE", "VIEW") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2496,7 +2505,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS. VIEW will be
 	 * completed afterwards */
 	else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
@@ -2564,10 +2573,10 @@ psql_completion(const char *text, int start, int end)
 	/* help completing some of the variants */
 	else if (Matches2("DROP", "AGGREGATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	else if (Matches2("DROP", "FUNCTION"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2578,8 +2587,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 	else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_fdws, "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&
 			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
@@ -2588,11 +2597,10 @@ psql_completion(const char *text, int start, int end)
 	/* DROP INDEX */
 	else if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'IF EXISTS'"
-								   " UNION SELECT 'CONCURRENTLY'");
+								   ADDLIST2("", "IF EXISTS","CONCURRENTLY"));
 	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */
 	else if (HeadMatches2("DROP", "INDEX") &&
 			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
@@ -2605,14 +2613,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches2("DROP", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Remove optional words IF EXISTS  */
 	else if (HeadMatches2("DROP", "VIEW") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 false) {} /* FALL THROUGH */
 	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
@@ -2630,8 +2638,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* DROP TRIGGER */
 	else if (Matches2("DROP", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_triggers
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_triggers, "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "TRIGGER") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2649,8 +2657,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	else if (Matches3("DROP", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_event_triggers, "IF EXISTS"));
 	/* Trye the same after removing optional words IF EXISTS */
 	else if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
@@ -2659,8 +2667,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* DROP POLICY */
 	else if (Matches2("DROP", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_policies, "IF EXISTS"));
 	/* Try the same after after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2678,8 +2686,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* DROP RULE */
 	else if (Matches2("DROP", "RULE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_rules
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_rules, "IF EXISTS"));
 	/* DROP RULE <name>, after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "RULE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2710,8 +2718,8 @@ psql_completion(const char *text, int start, int end)
 			/* Completing USER needs special treat */
 			if (pg_strcasecmp(prev_wd, "USER") == 0)
 			{
-				COMPLETE_WITH_QUERY(Query_for_list_of_roles 
-									"UNION SELECT 'MAPPING' UNION SELECT 'IF EXISTS'");
+				COMPLETE_WITH_QUERY(
+					ADDLIST2(Query_for_list_of_roles, "MAPPING", "IF EXISTS"));
 			}
 			else if (ent->query)
 			{
@@ -2724,7 +2732,7 @@ psql_completion(const char *text, int start, int end)
 			}
 			else if (ent->squery)
 				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery,
-										   " UNION SELECT 'IF EXISTS'");
+										   ADDLIST1("", "IF EXISTS"));
 		}
 	}
 	/* Remove optional IF EXISTS from DROP */
@@ -2795,20 +2803,11 @@ psql_completion(const char *text, int start, int end)
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
 	else if (TailMatches1("GRANT|REVOKE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'SELECT'"
-							" UNION SELECT 'INSERT'"
-							" UNION SELECT 'UPDATE'"
-							" UNION SELECT 'DELETE'"
-							" UNION SELECT 'TRUNCATE'"
-							" UNION SELECT 'REFERENCES'"
-							" UNION SELECT 'TRIGGER'"
-							" UNION SELECT 'CREATE'"
-							" UNION SELECT 'CONNECT'"
-							" UNION SELECT 'TEMPORARY'"
-							" UNION SELECT 'EXECUTE'"
-							" UNION SELECT 'USAGE'"
-							" UNION SELECT 'ALL'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST13(Query_for_list_of_roles,
+					  "SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
+					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
+					  "EXECUTE", "USAGE", "ALL"));
 
 	/*
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
@@ -2837,21 +2836,22 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
-								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
-								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
-								   " UNION SELECT 'ALL TABLES IN SCHEMA'"
-								   " UNION SELECT 'DATABASE'"
-								   " UNION SELECT 'DOMAIN'"
-								   " UNION SELECT 'FOREIGN DATA WRAPPER'"
-								   " UNION SELECT 'FOREIGN SERVER'"
-								   " UNION SELECT 'FUNCTION'"
-								   " UNION SELECT 'LANGUAGE'"
-								   " UNION SELECT 'LARGE OBJECT'"
-								   " UNION SELECT 'SCHEMA'"
-								   " UNION SELECT 'SEQUENCE'"
-								   " UNION SELECT 'TABLE'"
-								   " UNION SELECT 'TABLESPACE'"
-								   " UNION SELECT 'TYPE'");
+			   ADDLIST15("",
+						 "ALL FUNCTIONS IN SCHEMA",
+						 "ALL SEQUENCES IN SCHEMA",
+						 "ALL TABLES IN SCHEMA",
+						 "DATABASE",
+						 "DOMAIN",
+						 "FOREIGN DATA WRAPPER",
+						 "FOREIGN SERVER",
+						 "FUNCTION",
+						 "LANGUAGE",
+						 "LARGE OBJECT",
+						 "SCHEMA",
+						 "SEQUENCE",
+						 "TABLE",
+						 "TABLESPACE",
+						 "TYPE"));
 
 	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
@@ -2977,7 +2977,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete LOCK [TABLE] with a list of tables */
 	else if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION SELECT 'TABLE'");
+								   ADDLIST1("", "TABLE"));
 	else if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
@@ -3043,7 +3043,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'CONCURRENTLY'");
+								   ADDLIST1("", "CONCURRENTLY"));
 	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
@@ -3119,7 +3119,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
 	else if (Matches2("SET", "CONSTRAINTS"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema,
+								   ADDLIST1("", "ALL"));
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
 	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
@@ -3131,7 +3132,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
 	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_roles, "DEFAULT"));
 	/* Complete RESET SESSION with AUTHORIZATION */
 	else if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
@@ -3157,10 +3159,11 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST(my_list);
 		}
 		else if (TailMatches2("search_path", "TO|="))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
-								" AND nspname not like 'pg\\_toast%%' "
-								" AND nspname not like 'pg\\_temp%%' "
-								" UNION SELECT 'DEFAULT' ");
+			COMPLETE_WITH_QUERY(
+				ADDLIST1(Query_for_list_of_schemas
+						 " AND nspname not like 'pg\\_toast%%' "
+						 " AND nspname not like 'pg\\_temp%%' ",
+						 "DEFAULT"));
 		else
 		{
 			/* generic, type based, GUC support */
@@ -3229,10 +3232,9 @@ psql_completion(const char *text, int start, int end)
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 false) {} /* FALL THROUGH */
 	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'CURRENT_USER'"
-							" UNION SELECT 'PUBLIC'"
-							" UNION SELECT 'USER'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST3(Query_for_list_of_roles, 
+					 "CURRENT_USER", "PUBLIC", "USER"));
 	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
 	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
@@ -3246,26 +3248,22 @@ psql_completion(const char *text, int start, int end)
  */
 	else if (Matches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'FULL'"
-								   " UNION SELECT 'FREEZE'"
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST4("", "FULL", "FREEZE", "ANALYZE", "VERBOSE"));
 	else if (Matches2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST2("", "ANALYZE", "VERBOSE"));
 	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST1("", "VERBOSE"));
 	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+		   ADDLIST1("", "ANALYZE"));
 	else if (Matches2("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+		   ADDLIST1("", "ANALYZE"));
 	else if (Matches2("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST1("", "VERBOSE"));
 	else if (HeadMatches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
@@ -4030,6 +4028,41 @@ pg_strdup_keyword_case(const char *s, const char *ref)
 	return ret;
 }
 
+/* Construct codelet to append given keywords  */
+static char *
+additional_kw_query(char *prefix, const char *ref, int n, ...)
+{
+	va_list ap;
+	static PQExpBuffer qbuf = NULL;
+	int i;
+
+	if (qbuf == NULL)
+		qbuf = createPQExpBuffer();
+	else
+		resetPQExpBuffer(qbuf);
+
+	appendPQExpBufferStr(qbuf, prefix);
+
+	/* Construct an additional queriy to append keywords */
+	appendPQExpBufferStr(qbuf, " UNION SELECT * FROM (VALUES ");
+
+	va_start(ap, n);
+	for (i = 0 ; i < n ; i++)
+	{
+		char *item = pg_strdup_keyword_case(va_arg(ap, char *), ref);
+		if (i > 0) appendPQExpBufferChar(qbuf, ',');
+		appendPQExpBufferStr(qbuf, "('");
+		appendPQExpBufferStr(qbuf, item);
+		appendPQExpBufferStr(qbuf, "')");
+		pg_free(item);
+	}
+	va_end(ap);
+
+	appendPQExpBufferStr(qbuf, ") as x");
+
+	return qbuf->data;
+}
+
 
 /*
  * escape_string - Escape argument for use as string literal.
-- 
1.8.3.1

0003-Make-COMPLETE_WITH_ATTR-to-accept-additional-keyword.patchtext/x-patch; charset=us-asciiDownload
From bffe79bf5551e6f90916c5a6686ff875fa9965ed Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 1 Apr 2016 17:01:42 +0900
Subject: [PATCH 3/3] Make COMPLETE_WITH_ATTR to accept additional keyword
 query.

So far the previous commit, COMPLETE_WITH_ATTR cannot accept the new
style of additional keyword list. This patch does the following
things.

1. Change completion_charp from const char * to PQExpBuffer.

2. Chnage COMPLETE_WITH_QUERY and COMPLETE_WITH_ATTR to accept
   an expression instead of string literal.

3. Replace all additional keyword lists in psql_copmletion with
   ADDLISTn() expression.

This leaves keywords contained in Query_for_list_of_grant_roles and
Query_for_enum, but it is the another problem.
---
 src/bin/psql/tab-complete.c | 472 ++++++++++++++++++++++----------------------
 1 file changed, 239 insertions(+), 233 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ea8694c..ad488ff 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -125,7 +125,7 @@ static int	completion_max_records;
  * Communication variables set by COMPLETE_WITH_FOO macros and then used by
  * the completion callback functions.  Ugly but there is no better way.
  */
-static const char *completion_charp;	/* to pass a string */
+static PQExpBuffer completion_charp = NULL;		/* to pass a string */
 static const char *const * completion_charpp;	/* to pass a list of strings */
 static const char *completion_info_charp;		/* to pass a second string */
 static const char *completion_info_charp2;		/* to pass a third string */
@@ -143,16 +143,26 @@ static bool completion_case_sensitive;	/* completion is case sensitive */
  * 5) The list of attributes of the given table (possibly schema-qualified).
  * 6/ The list of arguments to the given function (possibly schema-qualified).
  */
-#define COMPLETE_WITH_QUERY(query) \
+#define APPEND_COMP_CHARP(charp) \
+	appendPQExpBufferStr(completion_charp, charp);
+
+#define SET_COMP_CHARP(charp) \
+	resetPQExpBuffer(completion_charp);	\
+	APPEND_COMP_CHARP(charp);
+
+#define COMPLETION_CHARP (completion_charp->data)
+
+#define COMPLETE_WITH_QUERY(query, addon) \
 do { \
-	completion_charp = query; \
+	SET_COMP_CHARP(query);	\
+	APPEND_COMP_CHARP(addon); \
 	matches = completion_matches(text, complete_from_query); \
 } while (0)
 
 #define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
 do { \
 	completion_squery = &(query); \
-	completion_charp = addon; \
+	SET_COMP_CHARP(addon); \
 	matches = completion_matches(text, complete_from_schema_query); \
 } while (0)
 
@@ -172,7 +182,7 @@ do { \
 
 #define COMPLETE_WITH_CONST(string) \
 do { \
-	completion_charp = string; \
+	SET_COMP_CHARP(string);	\
 	completion_case_sensitive = false; \
 	matches = completion_matches(text, complete_from_const); \
 } while (0)
@@ -190,12 +200,14 @@ do { \
 								false, false, pset.encoding); \
 	if (_completion_table == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_attributes  addon; \
+		SET_COMP_CHARP(Query_for_list_of_attributes); \
+		APPEND_COMP_CHARP(addon);					  \
 		completion_info_charp = relation; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_attributes_with_schema  addon; \
+		SET_COMP_CHARP(Query_for_list_of_attributes_with_schema); \
+		APPEND_COMP_CHARP(addon); \
 		completion_info_charp = _completion_table; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -215,12 +227,12 @@ do { \
 								   false, false, pset.encoding); \
 	if (_completion_function == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_arguments; \
+		SET_COMP_CHARP(Query_for_list_of_arguments); \
 		completion_info_charp = function; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_arguments_with_schema; \
+		SET_COMP_CHARP(Query_for_list_of_arguments_with_schema); \
 		completion_info_charp = _completion_function; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -312,16 +324,16 @@ do { \
 	COMPLETE_WITH_LIST_CS(list); \
 } while (0)
 
-#define ADDLIST1(p, s1) additional_kw_query(p, text, 1, s1)
-#define ADDLIST2(p, s1, s2) additional_kw_query(p, text, 2, s1, s2)
-#define ADDLIST3(p, s1, s2, s3) additional_kw_query(p, text, 3, s1, s2, s3)
-#define ADDLIST4(p, s1, s2, s3, s4) \
-	additional_kw_query(p, text, 4, s1, s2, s3, s4)
-#define ADDLIST13(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
-	additional_kw_query(p, text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+#define ADDLIST1(s1) additional_kw_query(text, 1, s1)
+#define ADDLIST2(s1, s2) additional_kw_query(text, 2, s1, s2)
+#define ADDLIST3(s1, s2, s3) additional_kw_query(text, 3, s1, s2, s3)
+#define ADDLIST4(s1, s2, s3, s4) \
+	additional_kw_query(text, 4, s1, s2, s3, s4)
+#define ADDLIST13(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
+	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
 						s8, s9, s10, s11, s12, s13)
-#define ADDLIST15(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
-	additional_kw_query(p, text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+#define ADDLIST15(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
+	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
 						s8, s9, s10, s11, s12, s13, s14, s15)
 
 /*
@@ -968,7 +980,7 @@ static char *complete_from_files(const char *text, int state);
 
 static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
-static char *additional_kw_query(char *prefix, const char *ref, int n, ...);
+static char *additional_kw_query( const char *ref, int n, ...);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
 
@@ -1398,7 +1410,8 @@ psql_completion(const char *text, int start, int end)
 #endif
 
 	/* Clear a few things. */
-	completion_charp = NULL;
+	if (completion_charp == NULL)
+		completion_charp = createPQExpBuffer();
 	completion_charpp = NULL;
 	completion_info_charp = NULL;
 	completion_info_charp2 = NULL;
@@ -1453,13 +1466,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE */
 	else if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-			ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
+			ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
 
 	/* ALTER TABLE after removing optional words IF EXISTS*/
 	else if (HeadMatches2("ALTER", "TABLE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 Matches2("ALTER", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* ALTER something */
 	else if (Matches1("ALTER"))
@@ -1479,7 +1492,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("SET TABLESPACE", "OWNED BY");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY */
 	else if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY xxx */
 	else if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("SET TABLESPACE");
@@ -1515,7 +1528,7 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER EVENT TRIGGER */
 	else if (Matches3("ALTER", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* ALTER EVENT TRIGGER <name> */
 	else if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
@@ -1540,13 +1553,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER FOREIGN TABLE */
 	else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 
 	/* ALTER|DROP FOREIGN TABLE after removing optinal words IF EXISTS */
 	else if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 
 	/* ALTER FOREIGN TABLE <name> */
 	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
@@ -1561,17 +1574,17 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER FOREIGN TABLE xxx RENAME */
 	else if (Matches5("ALTER", "FOREIGN", "TABLE", MatchAny, "RENAME"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'TO'");
+		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST2("COLUMN", "TO"));
 
 	/* ALTER INDEX */
 	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-			   ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
+			   ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
 	/* ALTER INDEX after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "INDEX") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 Matches2("ALTER", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 
 	/* ALTER INDEX <name> */
 	else if (Matches3("ALTER", "INDEX", MatchAny))
@@ -1601,13 +1614,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER MATERIALIZED VIEW */
 	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-			   ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
+			   ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
 
 	/* ALTER MATERIALIZED VIEW with name after removing optional words */
 	else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 Matches3("ALTER", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 
 	/* ALTER USER,ROLE <name> */
 	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
@@ -1663,14 +1676,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "DOMAIN", MatchAny, "RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, "");
 	}
 	/* ALTER DOMAIN <sth> DROP CONSTRAINT */
 	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_constraint_of_type, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type,
+							ADDLIST1("IF EXISTS"));
 	}
 	/* Try the same match after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
@@ -1678,7 +1691,7 @@ psql_completion(const char *text, int start, int end)
 			 Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, "");
 	}
 	/* ALTER DOMAIN <sth> RENAME */
 	else if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
@@ -1692,7 +1705,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	else if (Matches2("ALTER", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* ALTER SEQUENCE with name after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "SEQUENCE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1718,11 +1731,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
 	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars, "");
 	/* ALTER VIEW */
 	else if (Matches2("ALTER", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* ALTER VIEW <name> with subcommands after removing optional worlds */
 	else if (HeadMatches2("ALTER", "VIEW") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1736,8 +1749,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER POLICY */
 	else if (Matches2("ALTER", "POLICY"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_policies, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies,
+							ADDLIST1("IF EXISTS"));
 	/* ALTER POLICY <name> with ON after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1747,14 +1760,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, "");
 	}
 	/* ALTER POLICY <name> ON <table> - show options */
 	else if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING (", "WITH CHECK (");
 	/* ALTER POLICY <name> ON <table> TO <role> */
 	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 	/* ALTER POLICY <name> ON <table> USING ( */
 	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
@@ -1770,7 +1783,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("ALTER", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, "");
 	}
 
 	/* ALTER RULE <name> ON <name> */
@@ -1784,14 +1797,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, "");
 	}
 
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
 	else if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* ALTER TRIGGER <name> ON <name> */
 	else if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
@@ -1818,22 +1831,22 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
 	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
 	{
 		completion_info_charp = prev4_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
 	{
 		completion_info_charp = prev4_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 	/* ALTER TABLE xxx INHERIT */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
@@ -1847,21 +1860,21 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 
 	/* ALTER TABLE xxx ALTER */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
+		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST2("COLUMN", "CONSTRAINT"));
 
 	/* ALTER TABLE xxx RENAME */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
+		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST3("COLUMN", "CONSTRAINT", "TO"));
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
@@ -1880,7 +1893,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/*  ALTER TABLE DROP COLUMN may take IF EXISTS */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
 	/* ALTER TABLE <name> with ADD/ALTER/DROP after removing optional words */
 	else if (HeadMatches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP") &&
 			 MidMatchAndRemove1(4, "COLUMN") &&
@@ -1889,7 +1902,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN") &&
 			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
@@ -1903,7 +1916,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, "");
 	}
 	/*
 	 * If we have ALTER TABLE <sth> DROP CONSTRAINT,
@@ -1912,8 +1925,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_constraint_of_table, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table,
+							ADDLIST1("IF EXISTS"));
 	}
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
@@ -1921,7 +1934,7 @@ psql_completion(const char *text, int start, int end)
 			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, "");
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
 	else if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
@@ -1948,7 +1961,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
 	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
@@ -1960,7 +1973,7 @@ psql_completion(const char *text, int start, int end)
 	 * tablespaces
 	 */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
 	/* If we have ALTER TABLE <sth> SET WITH provide OIDS */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
 		COMPLETE_WITH_CONST("OIDS");
@@ -2011,7 +2024,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
 	else if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
@@ -2076,7 +2089,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 	else if (Matches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE"))
-		COMPLETE_WITH_ATTR(prev3_wd, " UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
 	/* Remove optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE") &&
 			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
@@ -2091,7 +2104,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
 	else if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* BEGIN, END, ABORT */
 	else if (Matches1("BEGIN|END|ABORT"))
@@ -2108,9 +2121,9 @@ psql_completion(const char *text, int start, int end)
 /* CLUSTER */
 	else if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   ADDLIST1("", "VERBOSE"));
+								   ADDLIST1("VERBOSE"));
 	else if (Matches2("CLUSTER", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 	/* If we have CLUSTER <sth>, then add "USING" */
 	else if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
 		COMPLETE_WITH_CONST("USING");
@@ -2122,7 +2135,7 @@ psql_completion(const char *text, int start, int end)
 			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
 
 /* COMMENT */
@@ -2145,18 +2158,18 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 	else if (Matches3("COMMENT", "ON", "CONSTRAINT"))
-		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
+		COMPLETE_WITH_QUERY(Query_for_all_table_constraints, "");
 	else if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	else if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint, "");
 	}
 	else if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	else if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 	else if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
 		Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
 			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
@@ -2170,10 +2183,10 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   ADDLIST1("", "("));
+								   ADDLIST1("("));
 	/* If we have COPY BINARY, complete with list of tables */
 	else if (Matches2("COPY", "BINARY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* If we have COPY (, complete it with legal commands */
 	else if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
@@ -2185,7 +2198,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
 			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
 	{
-		completion_charp = "";
+		SET_COMP_CHARP("");
 		matches = completion_matches(text, complete_from_files);
 	}
 
@@ -2220,18 +2233,18 @@ psql_completion(const char *text, int start, int end)
 							"LC_COLLATE", "LC_CTYPE");
 
 	else if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
+		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases, "");
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	else if (Matches2("CREATE", "EXTENSION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions
-							" UNION SELECT 'IF NOT EXISTS'");
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions,
+							ADDLIST1("IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS */
 	else if (HeadMatches2("CREATE", "EXTENSION") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
 			 Matches2("CREATE", "EXTENSION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions, "");
 	/* CREATE EXTENSION <name> */
 	else if (Matches3("CREATE", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
@@ -2239,7 +2252,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions, "");
 	}
 
 	/* CREATE FOREIGN */
@@ -2249,7 +2262,7 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN TABLE */
 	else if (Matches3("CREATE", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
 			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
@@ -2270,12 +2283,12 @@ psql_completion(const char *text, int start, int end)
 			 MidMatchAndRemove1(1, "UNIQUE") &&
 			 Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-				   ADDLIST3("", "ON", "CONCURRENTLY", "IF NOT EXISTS"));
+				   ADDLIST3("ON", "CONCURRENTLY", "IF NOT EXISTS"));
 	/* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and
 	 * existing indexes */
 	else if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   ADDLIST2("", "IF NOT EXISTS", "ON"));
+								   ADDLIST2("IF NOT EXISTS", "ON"));
 
 	/* Remove optional words "CONCURRENTLY",  "IF NOT EXISTS" */
 	else if (HeadMatches2("CREATE", "INDEX") &&
@@ -2286,7 +2299,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete CREATE INDEX [<name>] ON with a list of tables */
 	else if (Matches4("CREATE", "INDEX", MatchAny, "ON") ||
 			 Matches3("CREATE", "INDEX", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 
 	/* Complete CREATE INDEX <sth> with "ON" */
 	else if (Matches3("CREATE", "INDEX", MatchAny))
@@ -2308,7 +2321,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete USING with an index method */
 	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, "");
 	else if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
 			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
 			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
@@ -2320,7 +2333,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
 	else if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
 	else if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING (", "WITH CHECK (");
@@ -2338,7 +2351,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("TO", "USING (", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
 	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
 	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
@@ -2358,7 +2371,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
 	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 /* CREATE SEQUENCE, removing optional words TEMPORARY/TEMP */
 	else if (HeadMatches3("CREATE", MatchAny, "SEQUENCE") &&
@@ -2379,8 +2392,8 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE SCHEMA <name> */
 	else if (Matches2("CREATE", "SCHEMA"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_schemas, "IF NOT EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas,
+							ADDLIST1("IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches2("CREATE", "SCHEMA") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2399,7 +2412,7 @@ psql_completion(const char *text, int start, int end)
 			 MidMatchAndRemove1(1, "TEMP|TEMPORARY|UNLOGGED") &&
 			 Matches2("CREATE", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 
 	/* Remove optional words here */
 	else if (HeadMatches2("CREATE", "TABLE") &&
@@ -2439,10 +2452,10 @@ psql_completion(const char *text, int start, int end)
 	 * tables
 	 */
 	else if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
 	else if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
 	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
@@ -2490,7 +2503,7 @@ psql_completion(const char *text, int start, int end)
 /* CREATE VIEW  */
 	else if (Matches2("CREATE", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 	/* CREATE VIEW <name> with AS after removing optional words */
 	else if (HeadMatches2("CREATE", "VIEW") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2505,13 +2518,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS. VIEW will be
 	 * completed afterwards */
 	else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
 			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
 			 Matches3("CREATE", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
 	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
@@ -2542,7 +2555,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
 	else if (TailMatches2("DELETE", "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete DELETE FROM <table> */
 	else if (TailMatches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
@@ -2573,10 +2586,10 @@ psql_completion(const char *text, int start, int end)
 	/* help completing some of the variants */
 	else if (Matches2("DROP", "AGGREGATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	else if (Matches2("DROP", "FUNCTION"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2587,8 +2600,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 	else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_fdws, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws,
+							ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&
 			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
@@ -2597,10 +2610,10 @@ psql_completion(const char *text, int start, int end)
 	/* DROP INDEX */
 	else if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   ADDLIST2("", "IF EXISTS","CONCURRENTLY"));
+								   ADDLIST2("IF EXISTS","CONCURRENTLY"));
 	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */
 	else if (HeadMatches2("DROP", "INDEX") &&
 			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
@@ -2613,33 +2626,33 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches2("DROP", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* Remove optional words IF EXISTS  */
 	else if (HeadMatches2("DROP", "VIEW") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 false) {} /* FALL THROUGH */
 	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 Matches3("DROP", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 
 	/* DROP OWNED BY */
 	else if (Matches2("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
 	else if (Matches3("DROP", "OWNED", "BY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 	else if (Matches3("DROP", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
 	else if (Matches2("DROP", "TRIGGER"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_triggers, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_triggers,
+							ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "TRIGGER") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2648,7 +2661,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, "");
 	}
 	else if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
@@ -2657,23 +2670,23 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	else if (Matches3("DROP", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_event_triggers, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers,
+							ADDLIST1("IF EXISTS"));
 	/* Trye the same after removing optional words IF EXISTS */
 	else if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 Matches3("DROP", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* DROP POLICY */
 	else if (Matches2("DROP", "POLICY"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_policies, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies,
+							ADDLIST1("IF EXISTS"));
 	/* Try the same after after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 Matches2("DROP", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies, "");
 	/* DROP POLICY <name> */
 	else if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
@@ -2681,13 +2694,13 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, "");
 	}
 
 	/* DROP RULE */
 	else if (Matches2("DROP", "RULE"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_rules, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_rules,
+							ADDLIST1("IF EXISTS"));
 	/* DROP RULE <name>, after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "RULE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2696,7 +2709,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, "");
 	}
 	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
@@ -2711,15 +2724,15 @@ psql_completion(const char *text, int start, int end)
 
 	{
 		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
-		char *addition = " UNION SELECT 'IF EXISTS'";
+		char *addition = ADDLIST1("IF EXISTS");
 
 		if (ent)
 		{
 			/* Completing USER needs special treat */
 			if (pg_strcasecmp(prev_wd, "USER") == 0)
 			{
-				COMPLETE_WITH_QUERY(
-					ADDLIST2(Query_for_list_of_roles, "MAPPING", "IF EXISTS"));
+				COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+									ADDLIST2("MAPPING", "IF EXISTS"));
 			}
 			else if (ent->query)
 			{
@@ -2727,12 +2740,12 @@ psql_completion(const char *text, int start, int end)
 									  strlen(addition) + 1);
 				strcpy(buf, ent->query);
 				strcat(buf, addition);
-				COMPLETE_WITH_QUERY(buf);
+				COMPLETE_WITH_QUERY(buf, "");
 				free(buf);
 			}
 			else if (ent->squery)
 				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery,
-										   ADDLIST1("", "IF EXISTS"));
+										   ADDLIST1("IF EXISTS"));
 		}
 	}
 	/* Remove optional IF EXISTS from DROP */
@@ -2749,7 +2762,7 @@ psql_completion(const char *text, int start, int end)
 
 /* EXECUTE */
 	else if (Matches1("EXECUTE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
+		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements, "");
 
 /* EXPLAIN */
 
@@ -2786,7 +2799,7 @@ psql_completion(const char *text, int start, int end)
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
 	else if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
 			 !TailMatches4("CREATE", MatchAny, MatchAny, MatchAny))
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, "");
 	/* applies in CREATE SERVER */
 	else if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
 			 HeadMatches2("CREATE", "SERVER"))
@@ -2794,18 +2807,17 @@ psql_completion(const char *text, int start, int end)
 
 /* FOREIGN TABLE */
 	else if (TailMatches2("FOREIGN", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 
 /* FOREIGN SERVER */
 	else if (TailMatches2("FOREIGN", "SERVER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_servers, "");
 
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
 	else if (TailMatches1("GRANT|REVOKE"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST13(Query_for_list_of_roles,
-					  "SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+			ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
 					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
 					  "EXECUTE", "USAGE", "ALL"));
 
@@ -2836,8 +2848,7 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
-			   ADDLIST15("",
-						 "ALL FUNCTIONS IN SCHEMA",
+			   ADDLIST15("ALL FUNCTIONS IN SCHEMA",
 						 "ALL SEQUENCES IN SCHEMA",
 						 "ALL TABLES IN SCHEMA",
 						 "DATABASE",
@@ -2869,23 +2880,23 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
 		if (TailMatches1("DATABASE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+			COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 		else if (TailMatches1("DOMAIN"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, "");
 		else if (TailMatches1("FUNCTION"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 		else if (TailMatches1("LANGUAGE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
+			COMPLETE_WITH_QUERY(Query_for_list_of_languages, "");
 		else if (TailMatches1("SCHEMA"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+			COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
 		else if (TailMatches1("SEQUENCE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
 		else if (TailMatches1("TABLE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 		else if (TailMatches1("TABLESPACE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
 		else if (TailMatches1("TYPE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, "");
 		else if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
@@ -2898,7 +2909,7 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
 			 (HeadMatches1("REVOKE") && TailMatches1("FROM")))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 
 	/* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */
 	else if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
@@ -2949,7 +2960,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
 	else if (TailMatches2("INSERT", "INTO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete "INSERT INTO <table> (" with attribute names */
 	else if (TailMatches4("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
@@ -2977,7 +2988,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete LOCK [TABLE] with a list of tables */
 	else if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   ADDLIST1("", "TABLE"));
+								   ADDLIST1("TABLE"));
 	else if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
@@ -2999,7 +3010,7 @@ psql_completion(const char *text, int start, int end)
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
 	else if (TailMatches1("NOTIFY"))
-		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
+		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'", "");
 
 /* OPTIONS */
 	else if (TailMatches1("OPTIONS"))
@@ -3007,7 +3018,7 @@ psql_completion(const char *text, int start, int end)
 
 /* OWNER TO  - complete with available roles */
 	else if (TailMatches2("OWNER", "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* ORDER BY */
 	else if (TailMatches3("FROM", MatchAny, "ORDER"))
@@ -3030,11 +3041,11 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
 	else if (Matches3("REASSIGN", "OWNED", "BY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	else if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 	else if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* REFRESH MATERIALIZED VIEW */
 	else if (Matches1("REFRESH"))
@@ -3043,9 +3054,9 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   ADDLIST1("", "CONCURRENTLY"));
+								   ADDLIST1("CONCURRENTLY"));
 	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
 	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
@@ -3063,13 +3074,13 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
 	else if (Matches2("REINDEX", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 	else if (Matches2("REINDEX", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 	else if (Matches2("REINDEX", "SCHEMA"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
 	else if (Matches2("REINDEX", "SYSTEM|DATABASE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+		COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 
 /* SECURITY LABEL */
 	else if (Matches1("SECURITY"))
@@ -3098,9 +3109,9 @@ psql_completion(const char *text, int start, int end)
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
 	else if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars, "");
 	else if (Matches1("SHOW"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars, "");
 	/* Complete "SET TRANSACTION" */
 	else if (Matches2("SET|BEGIN|START", "TRANSACTION") ||
 			 Matches2("BEGIN", "WORK") ||
@@ -3120,20 +3131,20 @@ psql_completion(const char *text, int start, int end)
 	/* SET CONSTRAINTS */
 	else if (Matches2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema,
-								   ADDLIST1("", "ALL"));
+								   ADDLIST1("ALL"));
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
 	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
 	else if (Matches2("SET", "ROLE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
 	else if (Matches2("SET", "SESSION"))
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
 	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_roles, "DEFAULT"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+							ADDLIST1("DEFAULT"));
 	/* Complete RESET SESSION with AUTHORIZATION */
 	else if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
@@ -3159,11 +3170,10 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST(my_list);
 		}
 		else if (TailMatches2("search_path", "TO|="))
-			COMPLETE_WITH_QUERY(
-				ADDLIST1(Query_for_list_of_schemas
-						 " AND nspname not like 'pg\\_toast%%' "
-						 " AND nspname not like 'pg\\_temp%%' ",
-						 "DEFAULT"));
+			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+								" AND nspname not like 'pg\\_toast%%' "
+								" AND nspname not like 'pg\\_temp%%' ",
+								ADDLIST1("DEFAULT"));
 		else
 		{
 			/* generic, type based, GUC support */
@@ -3174,7 +3184,7 @@ psql_completion(const char *text, int start, int end)
 				char		querybuf[1024];
 
 				snprintf(querybuf, sizeof(querybuf), Query_for_enum, prev2_wd);
-				COMPLETE_WITH_QUERY(querybuf);
+				COMPLETE_WITH_QUERY(querybuf, "");
 			}
 			else if (guctype && strcmp(guctype, "bool") == 0)
 				COMPLETE_WITH_LIST9("on", "off", "true", "false", "yes", "no",
@@ -3193,26 +3203,26 @@ psql_completion(const char *text, int start, int end)
 
 /* TABLE, but not TABLE embedded in other commands */
 	else if (Matches1("TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, "");
 
 /* TABLESAMPLE */
 	else if (TailMatches1("TABLESAMPLE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods, "");
 	else if (TailMatches2("TABLESAMPLE", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
 	else if (Matches1("TRUNCATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 /* UNLISTEN */
 	else if (Matches1("UNLISTEN"))
-		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
+		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'", "");
 
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
 	else if (TailMatches1("UPDATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete UPDATE <table> with "SET" */
 	else if (TailMatches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
@@ -3232,11 +3242,10 @@ psql_completion(const char *text, int start, int end)
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 false) {} /* FALL THROUGH */
 	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST3(Query_for_list_of_roles, 
-					 "CURRENT_USER", "PUBLIC", "USER"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+							ADDLIST3("CURRENT_USER", "PUBLIC", "USER"));
 	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, "");
 	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("SERVER");
 	else if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
@@ -3248,24 +3257,24 @@ psql_completion(const char *text, int start, int end)
  */
 	else if (Matches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST4("", "FULL", "FREEZE", "ANALYZE", "VERBOSE"));
+		   ADDLIST4("FULL", "FREEZE", "ANALYZE", "VERBOSE"));
 	else if (Matches2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST2("", "ANALYZE", "VERBOSE"));
+		   ADDLIST2("ANALYZE", "VERBOSE"));
 	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST1("", "VERBOSE"));
+		   ADDLIST1("VERBOSE"));
 	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST1("", "ANALYZE"));
+		   ADDLIST1("ANALYZE"));
 	else if (Matches2("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST1("", "ANALYZE"));
+		   ADDLIST1("ANALYZE"));
 	else if (Matches2("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST1("", "VERBOSE"));
+		   ADDLIST1("VERBOSE"));
 	else if (HeadMatches1("VACUUM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 
 /* WITH [RECURSIVE] */
 
@@ -3279,7 +3288,7 @@ psql_completion(const char *text, int start, int end)
 /* ANALYZE */
 	/* Complete with list of tables */
 	else if (Matches1("ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, "");
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
@@ -3289,11 +3298,11 @@ psql_completion(const char *text, int start, int end)
 /* ... FROM ... */
 /* TODO: also include SRF ? */
 	else if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 
 /* ... JOIN ... */
 	else if (TailMatches1("JOIN"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
@@ -3302,80 +3311,80 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatchesCS1("\\connect|\\c"))
 	{
 		if (!recognized_connection_string(text))
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+			COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 	}
 	else if (TailMatchesCS2("\\connect|\\c", MatchAny))
 	{
 		if (!recognized_connection_string(prev_wd))
-			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+			COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	}
 	else if (TailMatchesCS1("\\da*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, "");
 	else if (TailMatchesCS1("\\db*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
 	else if (TailMatchesCS1("\\dD*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, "");
 	else if (TailMatchesCS1("\\des*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_servers, "");
 	else if (TailMatchesCS1("\\deu*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, "");
 	else if (TailMatchesCS1("\\dew*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, "");
 	else if (TailMatchesCS1("\\df*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 
 	else if (TailMatchesCS1("\\dFd*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries, "");
 	else if (TailMatchesCS1("\\dFp*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers, "");
 	else if (TailMatchesCS1("\\dFt*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates, "");
 	/* must be at end of \dF alternatives: */
 	else if (TailMatchesCS1("\\dF*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations, "");
 
 	else if (TailMatchesCS1("\\di*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 	else if (TailMatchesCS1("\\dL*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
+		COMPLETE_WITH_QUERY(Query_for_list_of_languages, "");
 	else if (TailMatchesCS1("\\dn*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
 	else if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 	else if (TailMatchesCS1("\\ds*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
 	else if (TailMatchesCS1("\\dt*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	else if (TailMatchesCS1("\\dT*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, "");
 	else if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	else if (TailMatchesCS1("\\dv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	else if (TailMatchesCS1("\\dx*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_extensions, "");
 	else if (TailMatchesCS1("\\dm*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	else if (TailMatchesCS1("\\dE*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 	else if (TailMatchesCS1("\\dy*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* must be at end of \d alternatives: */
 	else if (TailMatchesCS1("\\d*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, "");
 
 	else if (TailMatchesCS1("\\ef"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 	else if (TailMatchesCS1("\\ev"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 
 	else if (TailMatchesCS1("\\encoding"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_encodings, "");
 	else if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
 		COMPLETE_WITH_LIST(sql_commands);
 	else if (TailMatchesCS1("\\password"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	else if (TailMatchesCS1("\\pset"))
 	{
 		static const char *const my_list[] =
@@ -3435,14 +3444,14 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
 	}
 	else if (TailMatchesCS1("\\sf*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 	else if (TailMatchesCS1("\\sv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	else if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
 							"\\ir|\\include_relative|\\o|\\out|"
 							"\\s|\\w|\\write|\\lo_import"))
 	{
-		completion_charp = "\\";
+		SET_COMP_CHARP("\\");
 		matches = completion_matches(text, complete_from_files);
 	}
 
@@ -3458,9 +3467,9 @@ psql_completion(const char *text, int start, int end)
 		if (ent)
 		{
 			if (ent->query)
-				COMPLETE_WITH_QUERY(ent->query);
+				COMPLETE_WITH_QUERY(ent->query, "");
 			else if (ent->squery)
-				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, NULL);
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, "");
 		}
 	}
 
@@ -3718,13 +3727,13 @@ _complete_from_query(int is_schema_query, const char *text, int state)
 							  char_length, e_text);
 
 			/* If an addon query was provided, use it */
-			if (completion_charp)
-				appendPQExpBuffer(&query_buffer, "\n%s", completion_charp);
+			if (COMPLETION_CHARP[0])
+				appendPQExpBuffer(&query_buffer, "\n%s", COMPLETION_CHARP);
 		}
 		else
 		{
 			/* completion_charp is an sprintf-style format string */
-			appendPQExpBuffer(&query_buffer, completion_charp,
+			appendPQExpBuffer(&query_buffer, COMPLETION_CHARP,
 							  char_length, e_text,
 							  e_info_charp, e_info_charp,
 							  e_info_charp2, e_info_charp2);
@@ -3839,18 +3848,17 @@ complete_from_list(const char *text, int state)
 static char *
 complete_from_const(const char *text, int state)
 {
-	Assert(completion_charp != NULL);
 	if (state == 0)
 	{
 		if (completion_case_sensitive)
-			return pg_strdup(completion_charp);
+			return pg_strdup(COMPLETION_CHARP);
 		else
 
 			/*
 			 * If case insensitive matching was requested initially, adjust
 			 * the case according to setting.
 			 */
-			return pg_strdup_keyword_case(completion_charp, text);
+			return pg_strdup_keyword_case(COMPLETION_CHARP, text);
 	}
 	else
 		return NULL;
@@ -3951,7 +3959,7 @@ complete_from_files(const char *text, int state)
 	if (state == 0)
 	{
 		/* Initialization: stash the unquoted input. */
-		unquoted_text = strtokx(text, "", NULL, "'", *completion_charp,
+		unquoted_text = strtokx(text, "", NULL, "'", COMPLETION_CHARP[0],
 								false, true, pset.encoding);
 		/* expect a NULL return for the empty string only */
 		if (!unquoted_text)
@@ -3972,7 +3980,7 @@ complete_from_files(const char *text, int state)
 		 * bother providing a macro to simplify this.
 		 */
 		ret = quote_if_needed(unquoted_match, " \t\r\n\"`",
-							  '\'', *completion_charp, pset.encoding);
+							  '\'', COMPLETION_CHARP[0], pset.encoding);
 		if (ret)
 			free(unquoted_match);
 		else
@@ -4030,7 +4038,7 @@ pg_strdup_keyword_case(const char *s, const char *ref)
 
 /* Construct codelet to append given keywords  */
 static char *
-additional_kw_query(char *prefix, const char *ref, int n, ...)
+additional_kw_query(const char *ref, int n, ...)
 {
 	va_list ap;
 	static PQExpBuffer qbuf = NULL;
@@ -4041,8 +4049,6 @@ additional_kw_query(char *prefix, const char *ref, int n, ...)
 	else
 		resetPQExpBuffer(qbuf);
 
-	appendPQExpBufferStr(qbuf, prefix);
-
 	/* Construct an additional queriy to append keywords */
 	appendPQExpBufferStr(qbuf, " UNION SELECT * FROM (VALUES ");
 
-- 
1.8.3.1

#29Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#28)
Re: IF (NOT) EXISTS in psql-completion

Hi

2016-04-01 10:21 GMT+02:00 Kyotaro HORIGUCHI <
horiguchi.kyotaro@lab.ntt.co.jp>:

Hello, sorry for being a bit late.
The attatched are the new version of the patch.. set.

1. 0001-Suggest-IF-NOT-EXISTS-for-tab-completion-of-psql.patch

Adds IF (NOT) EXISTS completion. It doesn't fix the issue that
the case of additional keywords don't follow the input.

2. 0002-Make-added-keywords-for-completion-queries-follow-to.patch

Fixes the case-don't-follow issue by introducing a new macro set
ADDLISTn(). This leaves the issue for keywords along with
attributes.

3. 0003-Make-COMPLETE_WITH_ATTR-to-accept-additional-keyword.patch

Fixes the issue left after 0002 patch.
This patch does the following
things.

1. Change completion_charp from const char * to PQExpBuffer.

2. Chnage COMPLETE_WITH_QUERY and COMPLETE_WITH_ATTR to accept
an expression instead of string literal.

3. Replace all additional keyword lists in psql_copmletion with
ADDLISTn() expression.

At Fri, 01 Apr 2016 11:52:03 +0900 (Tokyo Standard Time), Kyotaro
HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <
20160401.115203.98896697.horiguchi.kyotaro@lab.ntt.co.jp>

I found new warning

tab-complete.c:1438:87: warning: right-hand operand of comma

expression

has no effect [-Wunused-value]

Mmm. Google said me that gcc 4.9 does so. I'm using 4.8.5 so I
haven't see the warning.

https://gcc.gnu.org/gcc-4.9/porting_to.html

1436: else if (HeadMatches2("CREATE", "SCHEMA") &&
1437: SHIFT_TO_LAST1("CREATE") &&
1438: false) {} /* FALL THROUGH */

...

But the right hand value (true) is actually "used" in the
expression (even though not effective). Perhaps (true && false)
was potimized as false and the true is regarded to be unused?
That's stupid.. Using functions instead of macros seems to solve
this but they needed to be wraped by macros as
additional_kw_query(). That's a pain..

...

This needs to use gcc 4.9 to address, but CentOS7 doesn't have
devtools-2 repo so now I'm building CentOS6 environment for this
purpose. Please wait for a while.

Finally I settled it by replacing comma expression with logical
OR or AND expresssion. gcc 4.9 compains for some unused variables
in flex output but it is the another issue.

I forgot to address COMPLETE_WITH_ATTTR but it needed an overhaul
of some macros and changing the type of completion_charp. The
third patch does it but it might be unacceptable..

something is wrong, autocomplete for CREATE TABLE IF NOT EXISTS doesn't work

Regards

Pavel

Show quoted text

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#30Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#29)
Re: IF (NOT) EXISTS in psql-completion

2016-04-02 7:16 GMT+02:00 Pavel Stehule <pavel.stehule@gmail.com>:

Hi

2016-04-01 10:21 GMT+02:00 Kyotaro HORIGUCHI <
horiguchi.kyotaro@lab.ntt.co.jp>:

Hello, sorry for being a bit late.
The attatched are the new version of the patch.. set.

1. 0001-Suggest-IF-NOT-EXISTS-for-tab-completion-of-psql.patch

Adds IF (NOT) EXISTS completion. It doesn't fix the issue that
the case of additional keywords don't follow the input.

2. 0002-Make-added-keywords-for-completion-queries-follow-to.patch

Fixes the case-don't-follow issue by introducing a new macro set
ADDLISTn(). This leaves the issue for keywords along with
attributes.

3. 0003-Make-COMPLETE_WITH_ATTR-to-accept-additional-keyword.patch

Fixes the issue left after 0002 patch.
This patch does the following
things.

1. Change completion_charp from const char * to PQExpBuffer.

2. Chnage COMPLETE_WITH_QUERY and COMPLETE_WITH_ATTR to accept
an expression instead of string literal.

3. Replace all additional keyword lists in psql_copmletion with
ADDLISTn() expression.

At Fri, 01 Apr 2016 11:52:03 +0900 (Tokyo Standard Time), Kyotaro
HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <
20160401.115203.98896697.horiguchi.kyotaro@lab.ntt.co.jp>

I found new warning

tab-complete.c:1438:87: warning: right-hand operand of comma

expression

has no effect [-Wunused-value]

Mmm. Google said me that gcc 4.9 does so. I'm using 4.8.5 so I
haven't see the warning.

https://gcc.gnu.org/gcc-4.9/porting_to.html

1436: else if (HeadMatches2("CREATE", "SCHEMA") &&
1437: SHIFT_TO_LAST1("CREATE") &&
1438: false) {} /* FALL THROUGH */

...

But the right hand value (true) is actually "used" in the
expression (even though not effective). Perhaps (true && false)
was potimized as false and the true is regarded to be unused?
That's stupid.. Using functions instead of macros seems to solve
this but they needed to be wraped by macros as
additional_kw_query(). That's a pain..

...

This needs to use gcc 4.9 to address, but CentOS7 doesn't have
devtools-2 repo so now I'm building CentOS6 environment for this
purpose. Please wait for a while.

Finally I settled it by replacing comma expression with logical
OR or AND expresssion. gcc 4.9 compains for some unused variables
in flex output but it is the another issue.

I forgot to address COMPLETE_WITH_ATTTR but it needed an overhaul
of some macros and changing the type of completion_charp. The
third patch does it but it might be unacceptable..

something is wrong, autocomplete for CREATE TABLE IF NOT EXISTS doesn't
work

CREATE UNLOGGED/TEMP table is working.

Pavel

Show quoted text

Regards

Pavel

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#31Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Pavel Stehule (#30)
3 attachment(s)
Re: IF (NOT) EXISTS in psql-completion

Thank you for testing. That is a silly mistake, sorry.

The attached is the fixed version.

# Can I add a suffix to format-patche's output files?

At Sat, 2 Apr 2016 07:18:32 +0200, Pavel Stehule <pavel.stehule@gmail.com> wrote in <CAFj8pRADF3rmQ3y33aeR1C7wOi2QsS65C8bBtiRNqU0zWVWayg@mail.gmail.com>

Finally I settled it by replacing comma expression with logical
OR or AND expresssion. gcc 4.9 compains for some unused variables
in flex output but it is the another issue.

I forgot to address COMPLETE_WITH_ATTTR but it needed an overhaul
of some macros and changing the type of completion_charp. The
third patch does it but it might be unacceptable..

something is wrong, autocomplete for CREATE TABLE IF NOT EXISTS doesn't
work

CREATE UNLOGGED/TEMP table is working.

Mmm. I mitakenly refactored multi-step matching.

else if (HeadMatches3("CREATE", MatchAny, "TABLE") &&
MidMatchAndRemove1(1, "TEMP|TEMPORARY|UNLOGGED") &&
Matches2("CREATE", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
ADDLIST1("IF NOT EXISTS"));

The completion runs only for CREATE AnyKeyword TABLE when
AnyKeyword is removable. It is wrong to do so when any of
prev_words[] that matches the last Matches() can be fileterd out
by the first Headmatches(). The same kind of mistake was found in
the following syntaxes. CREATE SEQENCE had one more mistake.

"CREATE [UNIQUE] INDEX"
"CREATE [TEMP] SEQUENCE"
"CREATE [TEMP..] TABLE"

It is arguable that it is proper to suggest existing object for
CREATE statement, but most of the statement is suggested. It is
semantically wrong but practically useful to know what kind of
word should be there.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

0001-Suggest-IF-NOT-EXISTS-for-tab-completion-of-psql.patchtext/x-patch; charset=us-asciiDownload
From eb7a91950dab394e6d6e0de9285b4f6002f22a09 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 5 Feb 2016 16:50:35 +0900
Subject: [PATCH 1/3] Suggest IF (NOT) EXISTS for tab-completion of psql

This patch lets psql to suggest "IF (NOT) EXISTS". Addition to that,
since this patch introduces some mechanism for syntactical robustness,
it allows psql completion to omit some optional part on matching.
---
 src/bin/psql/tab-complete.c | 630 ++++++++++++++++++++++++++++++++++++--------
 1 file changed, 522 insertions(+), 108 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 688d92a..9b6e704 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -656,6 +656,10 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "   FROM pg_catalog.pg_roles "\
 "  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"
 
+#define Query_for_list_of_rules \
+"SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules "\
+" WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"
+
 #define Query_for_list_of_grant_roles \
 " SELECT pg_catalog.quote_ident(rolname) "\
 "   FROM pg_catalog.pg_roles "\
@@ -763,6 +767,11 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "SELECT pg_catalog.quote_ident(tmplname) FROM pg_catalog.pg_ts_template "\
 " WHERE substring(pg_catalog.quote_ident(tmplname),1,%d)='%s'"
 
+#define Query_for_list_of_triggers \
+"SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger "\
+" WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s' AND "\
+"       NOT tgisinternal"
+
 #define Query_for_list_of_fdws \
 " SELECT pg_catalog.quote_ident(fdwname) "\
 "   FROM pg_catalog.pg_foreign_data_wrapper "\
@@ -907,7 +916,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
 	{"POLICY", NULL, NULL},
 	{"ROLE", Query_for_list_of_roles},
-	{"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"},
+	{"RULE", Query_for_list_of_rules},
 	{"SCHEMA", Query_for_list_of_schemas},
 	{"SEQUENCE", NULL, &Query_for_list_of_sequences},
 	{"SERVER", Query_for_list_of_servers},
@@ -916,7 +925,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"TEMP", NULL, NULL, THING_NO_DROP},		/* for CREATE TEMP TABLE ... */
 	{"TEMPLATE", Query_for_list_of_ts_templates, NULL, THING_NO_SHOW},
 	{"TEXT SEARCH", NULL, NULL},
-	{"TRIGGER", "SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s' AND NOT tgisinternal"},
+	{"TRIGGER", Query_for_list_of_triggers},
 	{"TYPE", NULL, &Query_for_list_of_datatypes},
 	{"UNIQUE", NULL, NULL, THING_NO_DROP},		/* for CREATE UNIQUE INDEX ... */
 	{"UNLOGGED", NULL, NULL, THING_NO_DROP},	/* for CREATE UNLOGGED TABLE
@@ -945,6 +954,7 @@ static char **complete_from_variables(const char *text,
 					const char *prefix, const char *suffix, bool need_value);
 static char *complete_from_files(const char *text, int state);
 
+static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
@@ -953,6 +963,7 @@ static char **get_previous_words(int point, char **buffer, int *nwords);
 
 static char *get_guctype(const char *varname);
 
+static const pgsql_thing_t *find_thing_entry(char *word);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -1108,6 +1119,9 @@ psql_completion(const char *text, int start, int end)
 	/* The number of words found on the input line. */
 	int			previous_words_count;
 
+	/* The number of prefixing words to be ignored */
+	int			head_shift = 0;
+
 	/*
 	 * For compactness, we use these macros to reference previous_words[].
 	 * Caution: do not access a previous_words[] entry without having checked
@@ -1125,31 +1139,73 @@ psql_completion(const char *text, int start, int end)
 #define prev8_wd  (previous_words[7])
 #define prev9_wd  (previous_words[8])
 
+	/* Move the position of the beginning word for matching macros.  */
+#define HEADSHIFT(n) \
+	((head_shift += n) || true)
+
+	/* Return the number of stored words counting head shift */
+#define WORD_COUNT() (previous_words_count - head_shift)
+
+	/* Return the true index in previous_words for index from the beginning */
+#define HEAD_INDEX(n) \
+	(previous_words_count - head_shift - (n))
+
+	/*
+	 * remove n words from current shifted position, see MidMatchAndRevmove2
+	 * for the reason for the return value
+	 */
+#define COLLAPSE(n) \
+	((memmove(previous_words + HEAD_INDEX(n), previous_words + HEAD_INDEX(0), \
+			 sizeof(char *) * head_shift), \
+	 previous_words_count -= (n)) && false)
+
+	/*
+	 * Find the position the specified word occurs last and shift to there.
+	 * This is used to ignore the words before there.
+	 */
+#define SHIFT_TO_LAST1(p1) \
+	(HEADSHIFT(find_last_index_of(p1, previous_words, previous_words_count))|| \
+	 true)
+
+	/*
+	 * Remove the specified words if they match from the sth word in
+	 * previous_words. This is a bit tricky, COLLAPSE is skipped when
+	 * HeadMatches failed but the last HEADSHIFT anyway should be done.
+	 */
+#define MidMatchAndRemove1(s, p1) \
+	((HEADSHIFT(s) && HeadMatches1(p1) && COLLAPSE(1)) || HEADSHIFT(-s))
+
+#define MidMatchAndRemove2(s, p1, p2) \
+	((HEADSHIFT(s) && HeadMatches2(p1, p2) && COLLAPSE(2)) || HEADSHIFT(-s))
+
+#define MidMatchAndRemove3(s, p1, p2, p3)									\
+	((HEADSHIFT(s) && HeadMatches3(p1, p2, p3) && COLLAPSE(3)) || HEADSHIFT(-s))
+
 	/* Macros for matching the last N words before point, case-insensitively. */
 #define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches(p1, prev_wd))
 
 #define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd))
 
 #define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
+	(WORD_COUNT() >= 3 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd))
 
 #define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
+	(WORD_COUNT() >= 4 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
 	 word_matches(p4, prev4_wd))
 
 #define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
+	(WORD_COUNT() >= 5 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1157,7 +1213,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p5, prev5_wd))
 
 #define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
+	(WORD_COUNT() >= 6 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1166,7 +1222,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p6, prev6_wd))
 
 #define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
+	(WORD_COUNT() >= 7 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1176,7 +1232,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p7, prev7_wd))
 
 #define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
+	(WORD_COUNT() >= 8 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1187,7 +1243,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p8, prev8_wd))
 
 #define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
+	(WORD_COUNT() >= 9 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1200,43 +1256,43 @@ psql_completion(const char *text, int start, int end)
 
 	/* Macros for matching the last N words before point, case-sensitively. */
 #define TailMatchesCS1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches_cs(p1, prev_wd))
 #define TailMatchesCS2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches_cs(p1, prev_wd) && \
 	 word_matches_cs(p2, prev2_wd))
 
 	/*
-	 * Macros for matching N words beginning at the start of the line,
+	 * Macros for matching N words exactly to the line,
 	 * case-insensitively.
 	 */
 #define Matches1(p1) \
-	(previous_words_count == 1 && \
+	(WORD_COUNT() == 1 && \
 	 TailMatches1(p1))
 #define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
+	(WORD_COUNT() == 2 && \
 	 TailMatches2(p1, p2))
 #define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
+	(WORD_COUNT() == 3 && \
 	 TailMatches3(p1, p2, p3))
 #define Matches4(p1, p2, p3, p4) \
-	(previous_words_count == 4 && \
+	(WORD_COUNT() == 4 && \
 	 TailMatches4(p1, p2, p3, p4))
 #define Matches5(p1, p2, p3, p4, p5) \
-	(previous_words_count == 5 && \
+	(WORD_COUNT() == 5 && \
 	 TailMatches5(p1, p2, p3, p4, p5))
 #define Matches6(p1, p2, p3, p4, p5, p6) \
-	(previous_words_count == 6 && \
+	(WORD_COUNT() == 6 && \
 	 TailMatches6(p1, p2, p3, p4, p5, p6))
 #define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(previous_words_count == 7 && \
+	(WORD_COUNT() == 7 && \
 	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
 #define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(previous_words_count == 8 && \
+	(WORD_COUNT() == 8 && \
 	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
 #define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(previous_words_count == 9 && \
+	(WORD_COUNT() == 9 && \
 	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
 
 	/*
@@ -1244,19 +1300,53 @@ psql_completion(const char *text, int start, int end)
 	 * what is after them, case-insensitively.
 	 */
 #define HeadMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]))
+	(HEAD_INDEX(1) >=0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]))
 
 #define HeadMatches2(p1, p2) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]))
+	(HEAD_INDEX(2) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]))
 
 #define HeadMatches3(p1, p2, p3) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]) && \
-	 word_matches(p3, previous_words[previous_words_count - 3]))
+	(HEAD_INDEX(3) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]))
+
+#define HeadMatches4(p1, p2, p3, p4) \
+	(HEAD_INDEX(4) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]))
+
+#define HeadMatches5(p1, p2, p3, p4, p5) \
+	(HEAD_INDEX(5) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]))
+
+#define HeadMatches6(p1, p2, p3, p4, p5, p6)		\
+	(HEAD_INDEX(6) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]) && \
+	 word_matches(p6, previous_words[HEAD_INDEX(6)]))
+
+#define HeadMatches7(p1, p2, p3, p4, p5, p6, p7)	\
+	(HEAD_INDEX(7) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]) && \
+	 word_matches(p6, previous_words[HEAD_INDEX(6)]) && \
+	 word_matches(p7, previous_words[HEAD_INDEX(7)]))
 
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
@@ -1328,9 +1418,16 @@ psql_completion(const char *text, int start, int end)
 	else if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
+	/*
+	 * If this is in CREATE SCHEMA, seek to the last CREATE and regard it as
+	 * current command to complete.
+	 */
+	else if (HeadMatches2("CREATE", "SCHEMA") &&
+			 SHIFT_TO_LAST1("CREATE") &&
+			 false) {} /* FALL THROUGH */
 /* CREATE */
 	/* complete with something you can create */
-	else if (TailMatches1("CREATE"))
+	else if (Matches1("CREATE"))
 		matches = completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
@@ -1343,7 +1440,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE */
 	else if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
+	/* ALTER TABLE after removing optional words IF EXISTS*/
+	else if (HeadMatches2("ALTER", "TABLE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("ALTER", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER something */
 	else if (Matches1("ALTER"))
@@ -1421,6 +1524,17 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
+	/* ALTER FOREIGN TABLE */
+	else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
+								   " UNION SELECT 'IF EXISTS'");
+
+	/* ALTER|DROP FOREIGN TABLE after removing optinal words IF EXISTS */
+	else if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+
 	/* ALTER FOREIGN TABLE <name> */
 	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
@@ -1432,10 +1546,21 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_FOREIGN_TABLE);
 	}
 
+	/* ALTER FOREIGN TABLE xxx RENAME */
+	else if (Matches5("ALTER", "FOREIGN", "TABLE", MatchAny, "RENAME"))
+		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'TO'");
+
 	/* ALTER INDEX */
 	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
+	/* ALTER INDEX after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "INDEX") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("ALTER", "INDEX"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+
 	/* ALTER INDEX <name> */
 	else if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
@@ -1464,8 +1589,15 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER MATERIALIZED VIEW */
 	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
+	/* ALTER MATERIALIZED VIEW with name after removing optional words */
+	else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("ALTER", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+
 	/* ALTER USER,ROLE <name> */
 	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
@@ -1516,8 +1648,23 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER DOMAIN <sth> DROP */
 	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
-	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	/* ALTER DOMAIN <sth> RENAME|VALIDATE CONSTRAINT */
+	else if (Matches5("ALTER", "DOMAIN", MatchAny, "RENAME|VALIDATE", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+	}
+	/* ALTER DOMAIN <sth> DROP CONSTRAINT */
+	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type
+							"UNION SELECT 'IF EXISTS'");
+	}
+	/* Try the same match after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
@@ -1532,8 +1679,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER DOMAIN <sth> SET */
 	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
-	/* ALTER SEQUENCE <name> */
-	else if (Matches3("ALTER", "SEQUENCE", MatchAny))
+	else if (Matches2("ALTER", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
+								   " UNION SELECT 'IF EXISTS'");
+	/* ALTER SEQUENCE with name after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "SEQUENCE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1556,8 +1708,14 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER SYSTEM SET|RESET <name> */
 	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
-	/* ALTER VIEW <name> */
-	else if (Matches3("ALTER", "VIEW", MatchAny))
+	/* ALTER VIEW */
+	else if (Matches2("ALTER", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   "UNION SELECT 'IF EXISTS'");
+	/* ALTER VIEW <name> with subcommands after removing optional worlds */
+	else if (HeadMatches2("ALTER", "VIEW") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
@@ -1565,11 +1723,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
-	/* ALTER POLICY <name> */
+	/* ALTER POLICY */
 	else if (Matches2("ALTER", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
-	/* ALTER POLICY <name> ON */
-	else if (Matches3("ALTER", "POLICY", MatchAny))
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies
+							"UNION SELECT 'IF EXISTS'");
+	/* ALTER POLICY <name> with ON after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "POLICY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
 	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
@@ -1693,8 +1854,10 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
-	/* ALTER TABLE xxx RENAME yyy */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
+	/* ALTER [FOREIGN] TABLE xxx RENAME yyy */
+	else if ((HeadMatches2("ALTER", "TABLE") ||
+			  HeadMatches3("ALTER", "FOREIGN", "TABLE")) &&
+			 TailMatches2("RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
@@ -1704,15 +1867,47 @@ psql_completion(const char *text, int start, int end)
 	/* If we have ALTER TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
+	/*  ALTER TABLE DROP COLUMN may take IF EXISTS */
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+	/* ALTER TABLE <name> with ADD/ALTER/DROP after removing optional words */
+	else if (HeadMatches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP") &&
+			 MidMatchAndRemove1(4, "COLUMN") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 Matches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP"))
+		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
-	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
+	 * If we have ALTER TABLE <sth> ALTER|RENAME|VALIDATE CONSTRAINT,
+	 * provide list of constraints
+	 */
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME|VALIDATE", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+	}
+	/*
+	 * If we have ALTER TABLE <sth> DROP CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table
+							"UNION SELECT 'IF EXISTS'");
+	}
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
@@ -1834,8 +2029,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 	else if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING",
-							"DROP MAPPING FOR",
-							"OWNER TO", "RENAME TO", "SET SCHEMA");
+							"DROP MAPPING",	"OWNER TO", "RENAME TO", "SET SCHEMA");
+	else if (Matches7("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny, "DROP", "MAPPING"))
+		COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR");
+	/* Remove optional words IF EXISTS */
+	else if (HeadMatches7("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny, "DROP", "MAPPING") &&
+			 MidMatchAndRemove2(7, "IF", "EXISTS") &&
+			 false) {} /* Nothing to do for now */
 
 	/* complete ALTER TYPE <foo> with actions */
 	else if (Matches3("ALTER", "TYPE", MatchAny))
@@ -1845,6 +2045,12 @@ psql_completion(const char *text, int start, int end)
 	/* complete ALTER TYPE <foo> ADD with actions */
 	else if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
+	else if (Matches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE"))
+		COMPLETE_WITH_LIST2("IF NOT EXISTS", "");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE") &&
+			 MidMatchAndRemove3(5, "IF", "NOT", "EXISTS") &&
+			 false) {} /* Nothing to do for now */
 	/* ALTER TYPE <foo> RENAME	*/
 	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "TO");
@@ -1856,10 +2062,15 @@ psql_completion(const char *text, int start, int end)
 	 * If we have ALTER TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list
 	 * of attributes
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
+	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
+	else if (Matches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE"))
+		COMPLETE_WITH_ATTR(prev3_wd, " UNION SELECT 'IF EXISTS'");
+	/* Remove optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
+			 Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* complete ALTER GROUP <foo> */
 	else if (Matches3("ALTER", "GROUP", MatchAny))
@@ -2002,6 +2213,12 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	else if (Matches2("CREATE", "EXTENSION"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions
+							" UNION SELECT 'IF NOT EXISTS'");
+	/* Try the same after removing optional words IF NOT EXISTS */
+	else if (HeadMatches2("CREATE", "EXTENSION") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
 	else if (Matches3("CREATE", "EXTENSION", MatchAny))
@@ -2017,6 +2234,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
+	/* CREATE FOREIGN TABLE */
+	else if (Matches3("CREATE", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
+			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
@@ -2025,23 +2250,38 @@ psql_completion(const char *text, int start, int end)
 	/* First off we complete CREATE UNIQUE with "INDEX" */
 	else if (TailMatches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
-	/* If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY",
+
+	/* Remove optional word UNIQUE from CREATE UNIQUE INDEX */
+	else if (HeadMatches3("CREATE", MatchAny, "INDEX") &&
+			 MidMatchAndRemove1(1, "UNIQUE") &&
+			 false) {} /* FALL THROUGH */
+	/* If we have CREATE INDEX, then add "ON", "CONCURRENTLY" or IF NOT EXISTS,
 	   and existing indexes */
-	else if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	else if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
-								   " UNION SELECT 'CONCURRENTLY'");
-	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	else if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
-			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-	/* Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing indexes */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+								   " UNION SELECT 'CONCURRENTLY'"
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and
+	 * existing indexes */
+	else if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF NOT EXISTS'"
 								   " UNION SELECT 'ON'");
-	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
-			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
+
+	/* Remove optional words "CONCURRENTLY",  "IF NOT EXISTS" */
+	else if (HeadMatches2("CREATE", "INDEX") &&
+			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
+	/* Complete CREATE INDEX [<name>] ON with a list of tables */
+	else if (Matches4("CREATE", "INDEX", MatchAny, "ON") ||
+			 Matches3("CREATE", "INDEX", "ON"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+
+	/* Complete CREATE INDEX <sth> with "ON" */
+	else if (Matches3("CREATE", "INDEX", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/*
@@ -2049,17 +2289,16 @@ psql_completion(const char *text, int start, int end)
 	 * should really be in parens)
 	 */
 	else if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-			 TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+			 TailMatches3("INDEX", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-			 TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
+	else if (Matches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
+			 Matches4("INDEX", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* same if you put in USING */
 	else if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	else if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
-			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
+	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 	else if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
@@ -2113,27 +2352,56 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-/* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	else if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
-			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
+/* Remove optional words TEMPORARY/TEMP */
+	else if (HeadMatches3("CREATE", MatchAny, "SEQUENCE") &&
+			 MidMatchAndRemove1(1, "TEMP|TEMPORARY") &&
+			 false) {} /* FALL THROUGH */
+/* CREATE SEQUENCE */
+	else if(Matches2("CREATE", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	else if(HeadMatches2("CREATE", "SEQUENCE") &&
+			MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			Matches3("CREATE", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	else if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
-		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
+	else if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
 	else if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
-/* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE SCHEMA <name> */
+	else if (Matches2("CREATE", "SCHEMA"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+							" UNION SELECT 'IF NOT EXISTS'");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches2("CREATE", "SCHEMA") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH*/
+
+/* CREATE TABLE  */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	else if (Matches2("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	else if (TailMatches2("CREATE", "UNLOGGED"))
+	else if (Matches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
+	/* CREATE TABLE with name after removing optional words */
+	else if (HeadMatches3("CREATE", MatchAny, "TABLE") &&
+			 MidMatchAndRemove1(1, "TEMP|TEMPORARY|UNLOGGED") &&
+			 false) {} /* FALL THROUGH*/
+	else if (Matches2("CREATE", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+								   " UNION SELECT 'IF NOT EXISTS'");
+
+	/* Remove optional words here */
+	else if (HeadMatches2("CREATE", "TABLE") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
@@ -2147,18 +2415,18 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
-/* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	else if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	else if (Matches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	else if (Matches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	else if (Matches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+	else if (Matches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
 	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
@@ -2215,9 +2483,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
-/* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	/* Complete CREATE VIEW <name> with AS */
-	else if (TailMatches3("CREATE", "VIEW", MatchAny))
+/* CREATE VIEW  */
+	else if (Matches2("CREATE", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* CREATE VIEW <name> with AS after removing optional words */
+	else if (HeadMatches2("CREATE", "VIEW") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
 	else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
@@ -2226,6 +2499,15 @@ psql_completion(const char *text, int start, int end)
 /* CREATE MATERIALIZED VIEW */
 	else if (Matches2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+	else if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Try the same after removing optional words IF NOT EXISTS. VIEW will be
+	 * completed afterwards */
+	else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
+			 Matches3("CREATE", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
 	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
@@ -2285,28 +2567,61 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
+	else if (Matches2("DROP", "AGGREGATE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates,
+								   " UNION SELECT 'IF EXISTS'");
+	else if (Matches2("DROP", "FUNCTION"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
 	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
+	else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 
 	/* DROP INDEX */
 	else if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF EXISTS'"
 								   " UNION SELECT 'CONCURRENTLY'");
 	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches3("DROP", "INDEX", MatchAny))
-		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	else if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */
+	else if (HeadMatches2("DROP", "INDEX") &&
+			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
 	else if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+	else if (Matches2("DROP", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Remove optional words IF EXISTS  */
+	else if (HeadMatches2("DROP", "VIEW") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
 	/* DROP OWNED BY */
@@ -2319,7 +2634,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (Matches3("DROP", "TRIGGER", MatchAny))
+	else if (Matches2("DROP", "TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_triggers
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "TRIGGER") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
@@ -2333,15 +2654,27 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	else if (Matches3("DROP", "EVENT", "TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers
+							" UNION SELECT 'IF EXISTS'");
+	/* Trye the same after removing optional words IF EXISTS */
+	else if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
-	/* DROP POLICY <name>  */
+	/* DROP POLICY */
 	else if (Matches2("DROP", "POLICY"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "POLICY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
-	/* DROP POLICY <name> ON */
+	/* DROP POLICY <name> */
 	else if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	/* DROP POLICY <name> ON <table> */
+	/* DROP POLICY <name> ON */
 	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
@@ -2349,7 +2682,13 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* DROP RULE */
-	else if (Matches3("DROP", "RULE", MatchAny))
+	else if (Matches2("DROP", "RULE"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_rules
+							"UNION SELECT 'IF EXISTS'");
+	/* DROP RULE <name>, after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "RULE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
@@ -2359,6 +2698,52 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
+	/* IF EXISTS processing for DROP everything else */
+	else if (Matches2("DROP",
+					  "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|"
+					  "GROUP|LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|"
+					  "SERVER|TABLE|TABLESPACE|TYPE|USER") ||
+			 Matches4("DROP", "TEXT", "SEARCH",
+					  "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE"))
+
+	{
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
+		char *addition = " UNION SELECT 'IF EXISTS'";
+
+		if (ent)
+		{
+			/* Completing USER needs special treat */
+			if (pg_strcasecmp(prev_wd, "USER") == 0)
+			{
+				COMPLETE_WITH_QUERY(Query_for_list_of_roles 
+									"UNION SELECT 'MAPPING' UNION SELECT 'IF EXISTS'");
+			}
+			else if (ent->query)
+			{
+				char *buf = pg_malloc(strlen(ent->query) +
+									  strlen(addition) + 1);
+				strcpy(buf, ent->query);
+				strcat(buf, addition);
+				COMPLETE_WITH_QUERY(buf);
+				free(buf);
+			}
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery,
+										   " UNION SELECT 'IF EXISTS'");
+		}
+	}
+	/* Remove optional IF EXISTS from DROP */
+	else if (HeadMatches2("DROP",
+						  "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|GROUP|"
+						  "LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|SERVER|"
+						  "TABLE|TABLESPACE|TYPE|USER") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+	else if (HeadMatches4("DROP", "TEXT", "SEARCH",
+						  "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
 /* EXECUTE */
 	else if (Matches1("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
@@ -2405,8 +2790,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /* FOREIGN TABLE */
-	else if (TailMatches2("FOREIGN", "TABLE") &&
-			 !TailMatches3("CREATE", MatchAny, MatchAny))
+	else if (TailMatches2("FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* FOREIGN SERVER */
@@ -2842,8 +3226,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
+	else if (Matches3("ALTER|CREATE", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
+	else if (Matches3("DROP", "USER", "MAPPING"))
+		COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR");
+	else if (HeadMatches3("DROP", "USER", "MAPPING") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
@@ -3071,19 +3460,14 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else
 	{
-		int			i;
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
 
-		for (i = 0; words_after_create[i].name; i++)
+		if (ent)
 		{
-			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
-			{
-				if (words_after_create[i].query)
-					COMPLETE_WITH_QUERY(words_after_create[i].query);
-				else if (words_after_create[i].squery)
-					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
-											   NULL);
-				break;
-			}
+			if (ent->query)
+				COMPLETE_WITH_QUERY(ent->query);
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, NULL);
 		}
 	}
 
@@ -3608,6 +3992,18 @@ complete_from_files(const char *text, int state)
 
 /* HELPER FUNCTIONS */
 
+/*
+ * Return the index (reverse to the index of previous_words) of the tailmost
+ * (topmost in the array) appearance of w.
+ */
+static int
+find_last_index_of(char *w, char **previous_words, int len)
+{
+	int i;
+
+	for (i = 0 ; i < len && !word_matches(w, previous_words[i]) ; i++);
+	return i < len ? (len - i - 1) : 0;
+}
 
 /*
  * Make a pg_strdup copy of s and convert the case according to
@@ -3854,6 +4250,24 @@ get_guctype(const char *varname)
 	return guctype;
 }
 
+/*
+ * Finds the entry in words_after_create[] that matches the word.
+ * NULL if not found.
+ */
+static const pgsql_thing_t *
+find_thing_entry(char *word)
+{
+	int			i;
+
+	for (i = 0; words_after_create[i].name; i++)
+	{
+		if (pg_strcasecmp(word, words_after_create[i].name) == 0)
+			return words_after_create + i;
+	}
+
+	return NULL;
+}
+
 #ifdef NOT_USED
 
 /*
-- 
1.8.3.1

0002-Make-added-keywords-for-completion-queries-follow-to.patchtext/x-patch; charset=us-asciiDownload
From 63a03319aa8a168c660bf205eba6dc95ac25b012 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Tue, 29 Mar 2016 19:01:13 +0900
Subject: [PATCH 2/3] Make added keywords for completion queries follow to
 input.

Currently some keyword shown along with database objects are always in
upper case. This patch changes the behavior so that the case of the
additional keywords follow the setting of COMP_KEYWORD_CASE.
Only COMPLETE_WITH_QUERY/COMPLETE_WITH_SCHEMA_QUERY are fixed.
---
 src/bin/psql/tab-complete.c | 239 +++++++++++++++++++++++++-------------------
 1 file changed, 136 insertions(+), 103 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 9b6e704..3edbdb7 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -312,6 +312,18 @@ do { \
 	COMPLETE_WITH_LIST_CS(list); \
 } while (0)
 
+#define ADDLIST1(p, s1) additional_kw_query(p, text, 1, s1)
+#define ADDLIST2(p, s1, s2) additional_kw_query(p, text, 2, s1, s2)
+#define ADDLIST3(p, s1, s2, s3) additional_kw_query(p, text, 3, s1, s2, s3)
+#define ADDLIST4(p, s1, s2, s3, s4) \
+	additional_kw_query(p, text, 4, s1, s2, s3, s4)
+#define ADDLIST13(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
+	additional_kw_query(p, text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13)
+#define ADDLIST15(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
+	additional_kw_query(p, text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13, s14, s15)
+
 /*
  * Assembly instructions for schema queries
  */
@@ -956,6 +968,7 @@ static char *complete_from_files(const char *text, int state);
 
 static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
+static char *additional_kw_query(char *prefix, const char *ref, int n, ...);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
 
@@ -1440,8 +1453,8 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE */
 	else if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   "UNION SELECT 'IF EXISTS'"
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+			ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
+
 	/* ALTER TABLE after removing optional words IF EXISTS*/
 	else if (HeadMatches2("ALTER", "TABLE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1527,7 +1540,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER FOREIGN TABLE */
 	else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 
 	/* ALTER|DROP FOREIGN TABLE after removing optinal words IF EXISTS */
 	else if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
@@ -1553,8 +1566,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER INDEX */
 	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   "UNION SELECT 'IF EXISTS'"
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+			   ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
 	/* ALTER INDEX after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "INDEX") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1589,8 +1601,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER MATERIALIZED VIEW */
 	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   "UNION SELECT 'IF EXISTS'"
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+			   ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
 
 	/* ALTER MATERIALIZED VIEW with name after removing optional words */
 	else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
@@ -1658,8 +1669,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_constraint_of_type, "IF EXISTS"));
 	}
 	/* Try the same match after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
@@ -1681,7 +1692,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	else if (Matches2("ALTER", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* ALTER SEQUENCE with name after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "SEQUENCE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1711,7 +1722,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER VIEW */
 	else if (Matches2("ALTER", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   "UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* ALTER VIEW <name> with subcommands after removing optional worlds */
 	else if (HeadMatches2("ALTER", "VIEW") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1725,8 +1736,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER POLICY */
 	else if (Matches2("ALTER", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_policies, "IF EXISTS"));
 	/* ALTER POLICY <name> with ON after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1901,8 +1912,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_constraint_of_table, "IF EXISTS"));
 	}
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
@@ -2096,7 +2107,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
 	else if (Matches1("CLUSTER"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
+								   ADDLIST1("", "VERBOSE"));
 	else if (Matches2("CLUSTER", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 	/* If we have CLUSTER <sth>, then add "USING" */
@@ -2158,7 +2170,7 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION ALL SELECT '('");
+								   ADDLIST1("", "("));
 	/* If we have COPY BINARY, complete with list of tables */
 	else if (Matches2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
@@ -2213,8 +2225,8 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	else if (Matches2("CREATE", "EXTENSION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions
-							" UNION SELECT 'IF NOT EXISTS'");
+		COMPLETE_WITH_QUERY(ADDLIST1(Query_for_list_of_available_extensions,
+									 "IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS */
 	else if (HeadMatches2("CREATE", "EXTENSION") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2237,7 +2249,7 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN TABLE */
 	else if (Matches3("CREATE", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
 			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
@@ -2259,15 +2271,12 @@ psql_completion(const char *text, int start, int end)
 	   and existing indexes */
 	else if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'ON'"
-								   " UNION SELECT 'CONCURRENTLY'"
-								   " UNION SELECT 'IF NOT EXISTS'");
+				   ADDLIST3("", "ON", "CONCURRENTLY", "IF NOT EXISTS"));
 	/* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and
 	 * existing indexes */
 	else if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'IF NOT EXISTS'"
-								   " UNION SELECT 'ON'");
+								   ADDLIST2("", "IF NOT EXISTS", "ON"));
 
 	/* Remove optional words "CONCURRENTLY",  "IF NOT EXISTS" */
 	else if (HeadMatches2("CREATE", "INDEX") &&
@@ -2359,7 +2368,7 @@ psql_completion(const char *text, int start, int end)
 /* CREATE SEQUENCE */
 	else if(Matches2("CREATE", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 	else if(HeadMatches2("CREATE", "SEQUENCE") &&
 			MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
 			Matches3("CREATE", "SEQUENCE", MatchAny))
@@ -2374,8 +2383,8 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE SCHEMA <name> */
 	else if (Matches2("CREATE", "SCHEMA"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas
-							" UNION SELECT 'IF NOT EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_schemas, "IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches2("CREATE", "SCHEMA") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2395,7 +2404,7 @@ psql_completion(const char *text, int start, int end)
 			 false) {} /* FALL THROUGH*/
 	else if (Matches2("CREATE", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 
 	/* Remove optional words here */
 	else if (HeadMatches2("CREATE", "TABLE") &&
@@ -2486,7 +2495,7 @@ psql_completion(const char *text, int start, int end)
 /* CREATE VIEW  */
 	else if (Matches2("CREATE", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 	/* CREATE VIEW <name> with AS after removing optional words */
 	else if (HeadMatches2("CREATE", "VIEW") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2501,7 +2510,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS. VIEW will be
 	 * completed afterwards */
 	else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
@@ -2569,10 +2578,10 @@ psql_completion(const char *text, int start, int end)
 	/* help completing some of the variants */
 	else if (Matches2("DROP", "AGGREGATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	else if (Matches2("DROP", "FUNCTION"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2583,8 +2592,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 	else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_fdws, "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&
 			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
@@ -2593,11 +2602,10 @@ psql_completion(const char *text, int start, int end)
 	/* DROP INDEX */
 	else if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'IF EXISTS'"
-								   " UNION SELECT 'CONCURRENTLY'");
+								   ADDLIST2("", "IF EXISTS","CONCURRENTLY"));
 	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */
 	else if (HeadMatches2("DROP", "INDEX") &&
 			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
@@ -2610,14 +2618,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches2("DROP", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Remove optional words IF EXISTS  */
 	else if (HeadMatches2("DROP", "VIEW") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 false) {} /* FALL THROUGH */
 	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
@@ -2635,8 +2643,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* DROP TRIGGER */
 	else if (Matches2("DROP", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_triggers
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_triggers, "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "TRIGGER") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2654,8 +2662,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	else if (Matches3("DROP", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_event_triggers, "IF EXISTS"));
 	/* Trye the same after removing optional words IF EXISTS */
 	else if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
@@ -2664,8 +2672,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* DROP POLICY */
 	else if (Matches2("DROP", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_policies, "IF EXISTS"));
 	/* Try the same after after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2683,8 +2691,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* DROP RULE */
 	else if (Matches2("DROP", "RULE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_rules
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_rules, "IF EXISTS"));
 	/* DROP RULE <name>, after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "RULE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2708,15 +2716,15 @@ psql_completion(const char *text, int start, int end)
 
 	{
 		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
-		char *addition = " UNION SELECT 'IF EXISTS'";
+		char *addition = ADDLIST1("", "IF EXISTS");
 
 		if (ent)
 		{
 			/* Completing USER needs special treat */
 			if (pg_strcasecmp(prev_wd, "USER") == 0)
 			{
-				COMPLETE_WITH_QUERY(Query_for_list_of_roles 
-									"UNION SELECT 'MAPPING' UNION SELECT 'IF EXISTS'");
+				COMPLETE_WITH_QUERY(
+					ADDLIST2(Query_for_list_of_roles, "MAPPING", "IF EXISTS"));
 			}
 			else if (ent->query)
 			{
@@ -2729,7 +2737,7 @@ psql_completion(const char *text, int start, int end)
 			}
 			else if (ent->squery)
 				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery,
-										   " UNION SELECT 'IF EXISTS'");
+										   ADDLIST1("", "IF EXISTS"));
 		}
 	}
 	/* Remove optional IF EXISTS from DROP */
@@ -2800,20 +2808,11 @@ psql_completion(const char *text, int start, int end)
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
 	else if (TailMatches1("GRANT|REVOKE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'SELECT'"
-							" UNION SELECT 'INSERT'"
-							" UNION SELECT 'UPDATE'"
-							" UNION SELECT 'DELETE'"
-							" UNION SELECT 'TRUNCATE'"
-							" UNION SELECT 'REFERENCES'"
-							" UNION SELECT 'TRIGGER'"
-							" UNION SELECT 'CREATE'"
-							" UNION SELECT 'CONNECT'"
-							" UNION SELECT 'TEMPORARY'"
-							" UNION SELECT 'EXECUTE'"
-							" UNION SELECT 'USAGE'"
-							" UNION SELECT 'ALL'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST13(Query_for_list_of_roles,
+					  "SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
+					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
+					  "EXECUTE", "USAGE", "ALL"));
 
 	/*
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
@@ -2842,21 +2841,22 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
-								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
-								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
-								   " UNION SELECT 'ALL TABLES IN SCHEMA'"
-								   " UNION SELECT 'DATABASE'"
-								   " UNION SELECT 'DOMAIN'"
-								   " UNION SELECT 'FOREIGN DATA WRAPPER'"
-								   " UNION SELECT 'FOREIGN SERVER'"
-								   " UNION SELECT 'FUNCTION'"
-								   " UNION SELECT 'LANGUAGE'"
-								   " UNION SELECT 'LARGE OBJECT'"
-								   " UNION SELECT 'SCHEMA'"
-								   " UNION SELECT 'SEQUENCE'"
-								   " UNION SELECT 'TABLE'"
-								   " UNION SELECT 'TABLESPACE'"
-								   " UNION SELECT 'TYPE'");
+			   ADDLIST15("",
+						 "ALL FUNCTIONS IN SCHEMA",
+						 "ALL SEQUENCES IN SCHEMA",
+						 "ALL TABLES IN SCHEMA",
+						 "DATABASE",
+						 "DOMAIN",
+						 "FOREIGN DATA WRAPPER",
+						 "FOREIGN SERVER",
+						 "FUNCTION",
+						 "LANGUAGE",
+						 "LARGE OBJECT",
+						 "SCHEMA",
+						 "SEQUENCE",
+						 "TABLE",
+						 "TABLESPACE",
+						 "TYPE"));
 
 	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
@@ -2982,7 +2982,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete LOCK [TABLE] with a list of tables */
 	else if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION SELECT 'TABLE'");
+								   ADDLIST1("", "TABLE"));
 	else if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
@@ -3048,7 +3048,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'CONCURRENTLY'");
+								   ADDLIST1("", "CONCURRENTLY"));
 	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
@@ -3124,7 +3124,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
 	else if (Matches2("SET", "CONSTRAINTS"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema,
+								   ADDLIST1("", "ALL"));
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
 	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
@@ -3136,7 +3137,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
 	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_roles, "DEFAULT"));
 	/* Complete RESET SESSION with AUTHORIZATION */
 	else if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
@@ -3162,10 +3164,11 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST(my_list);
 		}
 		else if (TailMatches2("search_path", "TO|="))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
-								" AND nspname not like 'pg\\_toast%%' "
-								" AND nspname not like 'pg\\_temp%%' "
-								" UNION SELECT 'DEFAULT' ");
+			COMPLETE_WITH_QUERY(
+				ADDLIST1(Query_for_list_of_schemas
+						 " AND nspname not like 'pg\\_toast%%' "
+						 " AND nspname not like 'pg\\_temp%%' ",
+						 "DEFAULT"));
 		else
 		{
 			/* generic, type based, GUC support */
@@ -3234,10 +3237,9 @@ psql_completion(const char *text, int start, int end)
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 false) {} /* FALL THROUGH */
 	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'CURRENT_USER'"
-							" UNION SELECT 'PUBLIC'"
-							" UNION SELECT 'USER'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST3(Query_for_list_of_roles, 
+					 "CURRENT_USER", "PUBLIC", "USER"));
 	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
 	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
@@ -3251,26 +3253,22 @@ psql_completion(const char *text, int start, int end)
  */
 	else if (Matches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'FULL'"
-								   " UNION SELECT 'FREEZE'"
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST4("", "FULL", "FREEZE", "ANALYZE", "VERBOSE"));
 	else if (Matches2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST2("", "ANALYZE", "VERBOSE"));
 	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST1("", "VERBOSE"));
 	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+		   ADDLIST1("", "ANALYZE"));
 	else if (Matches2("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+		   ADDLIST1("", "ANALYZE"));
 	else if (Matches2("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST1("", "VERBOSE"));
 	else if (HeadMatches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
@@ -4035,6 +4033,41 @@ pg_strdup_keyword_case(const char *s, const char *ref)
 	return ret;
 }
 
+/* Construct codelet to append given keywords  */
+static char *
+additional_kw_query(char *prefix, const char *ref, int n, ...)
+{
+	va_list ap;
+	static PQExpBuffer qbuf = NULL;
+	int i;
+
+	if (qbuf == NULL)
+		qbuf = createPQExpBuffer();
+	else
+		resetPQExpBuffer(qbuf);
+
+	appendPQExpBufferStr(qbuf, prefix);
+
+	/* Construct an additional queriy to append keywords */
+	appendPQExpBufferStr(qbuf, " UNION SELECT * FROM (VALUES ");
+
+	va_start(ap, n);
+	for (i = 0 ; i < n ; i++)
+	{
+		char *item = pg_strdup_keyword_case(va_arg(ap, char *), ref);
+		if (i > 0) appendPQExpBufferChar(qbuf, ',');
+		appendPQExpBufferStr(qbuf, "('");
+		appendPQExpBufferStr(qbuf, item);
+		appendPQExpBufferStr(qbuf, "')");
+		pg_free(item);
+	}
+	va_end(ap);
+
+	appendPQExpBufferStr(qbuf, ") as x");
+
+	return qbuf->data;
+}
+
 
 /*
  * escape_string - Escape argument for use as string literal.
-- 
1.8.3.1

0003-Make-COMPLETE_WITH_ATTR-to-accept-additional-keyword.patchtext/x-patch; charset=us-asciiDownload
From 26ea708bc9e1ce4b0d78a95aab9db168262e6cd5 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 1 Apr 2016 17:01:42 +0900
Subject: [PATCH 3/3] Make COMPLETE_WITH_ATTR to accept additional keyword
 query.

So far the previous commit, COMPLETE_WITH_ATTR cannot accept the new
style of additional keyword list. This patch does the following
things.

1. Change completion_charp from const char * to PQExpBuffer.

2. Chnage COMPLETE_WITH_QUERY and COMPLETE_WITH_ATTR to accept
   an expression instead of string literal.

3. Replace all additional keyword lists in psql_copmletion with
   ADDLISTn() expression.

This leaves keywords contained in Query_for_list_of_grant_roles and
Query_for_enum, but it is the another problem.
---
 src/bin/psql/tab-complete.c | 474 ++++++++++++++++++++++----------------------
 1 file changed, 240 insertions(+), 234 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 3edbdb7..1a69711 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -125,7 +125,7 @@ static int	completion_max_records;
  * Communication variables set by COMPLETE_WITH_FOO macros and then used by
  * the completion callback functions.  Ugly but there is no better way.
  */
-static const char *completion_charp;	/* to pass a string */
+static PQExpBuffer completion_charp = NULL;		/* to pass a string */
 static const char *const * completion_charpp;	/* to pass a list of strings */
 static const char *completion_info_charp;		/* to pass a second string */
 static const char *completion_info_charp2;		/* to pass a third string */
@@ -143,16 +143,26 @@ static bool completion_case_sensitive;	/* completion is case sensitive */
  * 5) The list of attributes of the given table (possibly schema-qualified).
  * 6/ The list of arguments to the given function (possibly schema-qualified).
  */
-#define COMPLETE_WITH_QUERY(query) \
+#define APPEND_COMP_CHARP(charp) \
+	appendPQExpBufferStr(completion_charp, charp);
+
+#define SET_COMP_CHARP(charp) \
+	resetPQExpBuffer(completion_charp);	\
+	APPEND_COMP_CHARP(charp);
+
+#define COMPLETION_CHARP (completion_charp->data)
+
+#define COMPLETE_WITH_QUERY(query, addon) \
 do { \
-	completion_charp = query; \
+	SET_COMP_CHARP(query);	\
+	APPEND_COMP_CHARP(addon); \
 	matches = completion_matches(text, complete_from_query); \
 } while (0)
 
 #define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
 do { \
 	completion_squery = &(query); \
-	completion_charp = addon; \
+	SET_COMP_CHARP(addon); \
 	matches = completion_matches(text, complete_from_schema_query); \
 } while (0)
 
@@ -172,7 +182,7 @@ do { \
 
 #define COMPLETE_WITH_CONST(string) \
 do { \
-	completion_charp = string; \
+	SET_COMP_CHARP(string);	\
 	completion_case_sensitive = false; \
 	matches = completion_matches(text, complete_from_const); \
 } while (0)
@@ -190,12 +200,14 @@ do { \
 								false, false, pset.encoding); \
 	if (_completion_table == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_attributes  addon; \
+		SET_COMP_CHARP(Query_for_list_of_attributes); \
+		APPEND_COMP_CHARP(addon);					  \
 		completion_info_charp = relation; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_attributes_with_schema  addon; \
+		SET_COMP_CHARP(Query_for_list_of_attributes_with_schema); \
+		APPEND_COMP_CHARP(addon); \
 		completion_info_charp = _completion_table; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -215,12 +227,12 @@ do { \
 								   false, false, pset.encoding); \
 	if (_completion_function == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_arguments; \
+		SET_COMP_CHARP(Query_for_list_of_arguments); \
 		completion_info_charp = function; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_arguments_with_schema; \
+		SET_COMP_CHARP(Query_for_list_of_arguments_with_schema); \
 		completion_info_charp = _completion_function; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -312,16 +324,16 @@ do { \
 	COMPLETE_WITH_LIST_CS(list); \
 } while (0)
 
-#define ADDLIST1(p, s1) additional_kw_query(p, text, 1, s1)
-#define ADDLIST2(p, s1, s2) additional_kw_query(p, text, 2, s1, s2)
-#define ADDLIST3(p, s1, s2, s3) additional_kw_query(p, text, 3, s1, s2, s3)
-#define ADDLIST4(p, s1, s2, s3, s4) \
-	additional_kw_query(p, text, 4, s1, s2, s3, s4)
-#define ADDLIST13(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
-	additional_kw_query(p, text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+#define ADDLIST1(s1) additional_kw_query(text, 1, s1)
+#define ADDLIST2(s1, s2) additional_kw_query(text, 2, s1, s2)
+#define ADDLIST3(s1, s2, s3) additional_kw_query(text, 3, s1, s2, s3)
+#define ADDLIST4(s1, s2, s3, s4) \
+	additional_kw_query(text, 4, s1, s2, s3, s4)
+#define ADDLIST13(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
+	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
 						s8, s9, s10, s11, s12, s13)
-#define ADDLIST15(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
-	additional_kw_query(p, text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+#define ADDLIST15(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
+	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
 						s8, s9, s10, s11, s12, s13, s14, s15)
 
 /*
@@ -968,7 +980,7 @@ static char *complete_from_files(const char *text, int state);
 
 static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
-static char *additional_kw_query(char *prefix, const char *ref, int n, ...);
+static char *additional_kw_query( const char *ref, int n, ...);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
 
@@ -1398,7 +1410,8 @@ psql_completion(const char *text, int start, int end)
 #endif
 
 	/* Clear a few things. */
-	completion_charp = NULL;
+	if (completion_charp == NULL)
+		completion_charp = createPQExpBuffer();
 	completion_charpp = NULL;
 	completion_info_charp = NULL;
 	completion_info_charp2 = NULL;
@@ -1453,13 +1466,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE */
 	else if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-			ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
+			ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
 
 	/* ALTER TABLE after removing optional words IF EXISTS*/
 	else if (HeadMatches2("ALTER", "TABLE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 Matches2("ALTER", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* ALTER something */
 	else if (Matches1("ALTER"))
@@ -1479,7 +1492,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("SET TABLESPACE", "OWNED BY");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY */
 	else if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY xxx */
 	else if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("SET TABLESPACE");
@@ -1515,7 +1528,7 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER EVENT TRIGGER */
 	else if (Matches3("ALTER", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* ALTER EVENT TRIGGER <name> */
 	else if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
@@ -1540,13 +1553,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER FOREIGN TABLE */
 	else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 
 	/* ALTER|DROP FOREIGN TABLE after removing optinal words IF EXISTS */
 	else if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 
 	/* ALTER FOREIGN TABLE <name> */
 	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
@@ -1561,17 +1574,17 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER FOREIGN TABLE xxx RENAME */
 	else if (Matches5("ALTER", "FOREIGN", "TABLE", MatchAny, "RENAME"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'TO'");
+		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST2("COLUMN", "TO"));
 
 	/* ALTER INDEX */
 	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-			   ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
+			   ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
 	/* ALTER INDEX after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "INDEX") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 Matches2("ALTER", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 
 	/* ALTER INDEX <name> */
 	else if (Matches3("ALTER", "INDEX", MatchAny))
@@ -1601,13 +1614,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER MATERIALIZED VIEW */
 	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-			   ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
+			   ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
 
 	/* ALTER MATERIALIZED VIEW with name after removing optional words */
 	else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 Matches3("ALTER", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 
 	/* ALTER USER,ROLE <name> */
 	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
@@ -1663,14 +1676,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "DOMAIN", MatchAny, "RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, "");
 	}
 	/* ALTER DOMAIN <sth> DROP CONSTRAINT */
 	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_constraint_of_type, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type,
+							ADDLIST1("IF EXISTS"));
 	}
 	/* Try the same match after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
@@ -1678,7 +1691,7 @@ psql_completion(const char *text, int start, int end)
 			 Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, "");
 	}
 	/* ALTER DOMAIN <sth> RENAME */
 	else if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
@@ -1692,7 +1705,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	else if (Matches2("ALTER", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* ALTER SEQUENCE with name after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "SEQUENCE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1718,11 +1731,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
 	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars, "");
 	/* ALTER VIEW */
 	else if (Matches2("ALTER", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* ALTER VIEW <name> with subcommands after removing optional worlds */
 	else if (HeadMatches2("ALTER", "VIEW") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1736,8 +1749,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER POLICY */
 	else if (Matches2("ALTER", "POLICY"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_policies, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies,
+							ADDLIST1("IF EXISTS"));
 	/* ALTER POLICY <name> with ON after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1747,14 +1760,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, "");
 	}
 	/* ALTER POLICY <name> ON <table> - show options */
 	else if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING (", "WITH CHECK (");
 	/* ALTER POLICY <name> ON <table> TO <role> */
 	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 	/* ALTER POLICY <name> ON <table> USING ( */
 	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
@@ -1770,7 +1783,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("ALTER", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, "");
 	}
 
 	/* ALTER RULE <name> ON <name> */
@@ -1784,14 +1797,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, "");
 	}
 
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
 	else if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* ALTER TRIGGER <name> ON <name> */
 	else if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
@@ -1818,22 +1831,22 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
 	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
 	{
 		completion_info_charp = prev4_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
 	{
 		completion_info_charp = prev4_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 	/* ALTER TABLE xxx INHERIT */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
@@ -1847,21 +1860,21 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 
 	/* ALTER TABLE xxx ALTER */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
+		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST2("COLUMN", "CONSTRAINT"));
 
 	/* ALTER TABLE xxx RENAME */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
+		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST3("COLUMN", "CONSTRAINT", "TO"));
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
@@ -1880,7 +1893,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/*  ALTER TABLE DROP COLUMN may take IF EXISTS */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
 	/* ALTER TABLE <name> with ADD/ALTER/DROP after removing optional words */
 	else if (HeadMatches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP") &&
 			 MidMatchAndRemove1(4, "COLUMN") &&
@@ -1889,7 +1902,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN") &&
 			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
@@ -1903,7 +1916,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, "");
 	}
 	/*
 	 * If we have ALTER TABLE <sth> DROP CONSTRAINT,
@@ -1912,8 +1925,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_constraint_of_table, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table,
+							ADDLIST1("IF EXISTS"));
 	}
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
@@ -1921,7 +1934,7 @@ psql_completion(const char *text, int start, int end)
 			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, "");
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
 	else if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
@@ -1948,7 +1961,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
 	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
@@ -1960,7 +1973,7 @@ psql_completion(const char *text, int start, int end)
 	 * tablespaces
 	 */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
 	/* If we have ALTER TABLE <sth> SET WITH provide OIDS */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
 		COMPLETE_WITH_CONST("OIDS");
@@ -2011,7 +2024,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
 	else if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
@@ -2076,7 +2089,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 	else if (Matches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE"))
-		COMPLETE_WITH_ATTR(prev3_wd, " UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
 	/* Remove optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE") &&
 			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
@@ -2091,7 +2104,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
 	else if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* BEGIN, END, ABORT */
 	else if (Matches1("BEGIN|END|ABORT"))
@@ -2108,9 +2121,9 @@ psql_completion(const char *text, int start, int end)
 /* CLUSTER */
 	else if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   ADDLIST1("", "VERBOSE"));
+								   ADDLIST1("VERBOSE"));
 	else if (Matches2("CLUSTER", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 	/* If we have CLUSTER <sth>, then add "USING" */
 	else if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
 		COMPLETE_WITH_CONST("USING");
@@ -2122,7 +2135,7 @@ psql_completion(const char *text, int start, int end)
 			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
 
 /* COMMENT */
@@ -2145,18 +2158,18 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 	else if (Matches3("COMMENT", "ON", "CONSTRAINT"))
-		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
+		COMPLETE_WITH_QUERY(Query_for_all_table_constraints, "");
 	else if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	else if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint, "");
 	}
 	else if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	else if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 	else if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
 		Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
 			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
@@ -2170,10 +2183,10 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   ADDLIST1("", "("));
+								   ADDLIST1("("));
 	/* If we have COPY BINARY, complete with list of tables */
 	else if (Matches2("COPY", "BINARY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* If we have COPY (, complete it with legal commands */
 	else if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
@@ -2185,7 +2198,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
 			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
 	{
-		completion_charp = "";
+		SET_COMP_CHARP("");
 		matches = completion_matches(text, complete_from_files);
 	}
 
@@ -2220,18 +2233,18 @@ psql_completion(const char *text, int start, int end)
 							"LC_COLLATE", "LC_CTYPE");
 
 	else if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
+		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases, "");
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	else if (Matches2("CREATE", "EXTENSION"))
-		COMPLETE_WITH_QUERY(ADDLIST1(Query_for_list_of_available_extensions,
-									 "IF NOT EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions,
+							ADDLIST1("IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS */
 	else if (HeadMatches2("CREATE", "EXTENSION") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
 			 Matches2("CREATE", "EXTENSION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions, "");
 	/* CREATE EXTENSION <name> */
 	else if (Matches3("CREATE", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
@@ -2239,7 +2252,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions, "");
 	}
 
 	/* CREATE FOREIGN */
@@ -2249,7 +2262,7 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN TABLE */
 	else if (Matches3("CREATE", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
 			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
@@ -2271,12 +2284,12 @@ psql_completion(const char *text, int start, int end)
 	   and existing indexes */
 	else if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-				   ADDLIST3("", "ON", "CONCURRENTLY", "IF NOT EXISTS"));
+				   ADDLIST3("ON", "CONCURRENTLY", "IF NOT EXISTS"));
 	/* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and
 	 * existing indexes */
 	else if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   ADDLIST2("", "IF NOT EXISTS", "ON"));
+								   ADDLIST2("IF NOT EXISTS", "ON"));
 
 	/* Remove optional words "CONCURRENTLY",  "IF NOT EXISTS" */
 	else if (HeadMatches2("CREATE", "INDEX") &&
@@ -2287,7 +2300,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete CREATE INDEX [<name>] ON with a list of tables */
 	else if (Matches4("CREATE", "INDEX", MatchAny, "ON") ||
 			 Matches3("CREATE", "INDEX", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 
 	/* Complete CREATE INDEX <sth> with "ON" */
 	else if (Matches3("CREATE", "INDEX", MatchAny))
@@ -2309,7 +2322,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete USING with an index method */
 	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, "");
 	else if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
 			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
 			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
@@ -2321,7 +2334,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
 	else if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
 	else if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING (", "WITH CHECK (");
@@ -2339,7 +2352,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("TO", "USING (", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
 	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
 	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
@@ -2359,7 +2372,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
 	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 /* Remove optional words TEMPORARY/TEMP */
 	else if (HeadMatches3("CREATE", MatchAny, "SEQUENCE") &&
@@ -2368,7 +2381,7 @@ psql_completion(const char *text, int start, int end)
 /* CREATE SEQUENCE */
 	else if(Matches2("CREATE", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 	else if(HeadMatches2("CREATE", "SEQUENCE") &&
 			MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
 			Matches3("CREATE", "SEQUENCE", MatchAny))
@@ -2383,8 +2396,8 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE SCHEMA <name> */
 	else if (Matches2("CREATE", "SCHEMA"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_schemas, "IF NOT EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas,
+							ADDLIST1("IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches2("CREATE", "SCHEMA") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2404,7 +2417,7 @@ psql_completion(const char *text, int start, int end)
 			 false) {} /* FALL THROUGH*/
 	else if (Matches2("CREATE", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 
 	/* Remove optional words here */
 	else if (HeadMatches2("CREATE", "TABLE") &&
@@ -2444,10 +2457,10 @@ psql_completion(const char *text, int start, int end)
 	 * tables
 	 */
 	else if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
 	else if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
 	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
@@ -2495,7 +2508,7 @@ psql_completion(const char *text, int start, int end)
 /* CREATE VIEW  */
 	else if (Matches2("CREATE", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 	/* CREATE VIEW <name> with AS after removing optional words */
 	else if (HeadMatches2("CREATE", "VIEW") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2510,13 +2523,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS. VIEW will be
 	 * completed afterwards */
 	else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
 			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
 			 Matches3("CREATE", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
 	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
@@ -2547,7 +2560,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
 	else if (TailMatches2("DELETE", "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete DELETE FROM <table> */
 	else if (TailMatches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
@@ -2578,10 +2591,10 @@ psql_completion(const char *text, int start, int end)
 	/* help completing some of the variants */
 	else if (Matches2("DROP", "AGGREGATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	else if (Matches2("DROP", "FUNCTION"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2592,8 +2605,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 	else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_fdws, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws,
+							ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&
 			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
@@ -2602,10 +2615,10 @@ psql_completion(const char *text, int start, int end)
 	/* DROP INDEX */
 	else if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   ADDLIST2("", "IF EXISTS","CONCURRENTLY"));
+								   ADDLIST2("IF EXISTS","CONCURRENTLY"));
 	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */
 	else if (HeadMatches2("DROP", "INDEX") &&
 			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
@@ -2618,33 +2631,33 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches2("DROP", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* Remove optional words IF EXISTS  */
 	else if (HeadMatches2("DROP", "VIEW") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 false) {} /* FALL THROUGH */
 	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 Matches3("DROP", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 
 	/* DROP OWNED BY */
 	else if (Matches2("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
 	else if (Matches3("DROP", "OWNED", "BY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 	else if (Matches3("DROP", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
 	else if (Matches2("DROP", "TRIGGER"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_triggers, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_triggers,
+							ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "TRIGGER") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2653,7 +2666,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, "");
 	}
 	else if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
@@ -2662,23 +2675,23 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	else if (Matches3("DROP", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_event_triggers, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers,
+							ADDLIST1("IF EXISTS"));
 	/* Trye the same after removing optional words IF EXISTS */
 	else if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 Matches3("DROP", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* DROP POLICY */
 	else if (Matches2("DROP", "POLICY"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_policies, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies,
+							ADDLIST1("IF EXISTS"));
 	/* Try the same after after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 Matches2("DROP", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies, "");
 	/* DROP POLICY <name> */
 	else if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
@@ -2686,13 +2699,13 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, "");
 	}
 
 	/* DROP RULE */
 	else if (Matches2("DROP", "RULE"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_rules, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_rules,
+							ADDLIST1("IF EXISTS"));
 	/* DROP RULE <name>, after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "RULE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2701,7 +2714,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, "");
 	}
 	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
@@ -2716,15 +2729,15 @@ psql_completion(const char *text, int start, int end)
 
 	{
 		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
-		char *addition = ADDLIST1("", "IF EXISTS");
+		char *addition = ADDLIST1("IF EXISTS");
 
 		if (ent)
 		{
 			/* Completing USER needs special treat */
 			if (pg_strcasecmp(prev_wd, "USER") == 0)
 			{
-				COMPLETE_WITH_QUERY(
-					ADDLIST2(Query_for_list_of_roles, "MAPPING", "IF EXISTS"));
+				COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+									ADDLIST2("MAPPING", "IF EXISTS"));
 			}
 			else if (ent->query)
 			{
@@ -2732,12 +2745,12 @@ psql_completion(const char *text, int start, int end)
 									  strlen(addition) + 1);
 				strcpy(buf, ent->query);
 				strcat(buf, addition);
-				COMPLETE_WITH_QUERY(buf);
+				COMPLETE_WITH_QUERY(buf, "");
 				free(buf);
 			}
 			else if (ent->squery)
 				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery,
-										   ADDLIST1("", "IF EXISTS"));
+										   ADDLIST1("IF EXISTS"));
 		}
 	}
 	/* Remove optional IF EXISTS from DROP */
@@ -2754,7 +2767,7 @@ psql_completion(const char *text, int start, int end)
 
 /* EXECUTE */
 	else if (Matches1("EXECUTE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
+		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements, "");
 
 /* EXPLAIN */
 
@@ -2791,7 +2804,7 @@ psql_completion(const char *text, int start, int end)
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
 	else if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
 			 !TailMatches4("CREATE", MatchAny, MatchAny, MatchAny))
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, "");
 	/* applies in CREATE SERVER */
 	else if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
 			 HeadMatches2("CREATE", "SERVER"))
@@ -2799,18 +2812,17 @@ psql_completion(const char *text, int start, int end)
 
 /* FOREIGN TABLE */
 	else if (TailMatches2("FOREIGN", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 
 /* FOREIGN SERVER */
 	else if (TailMatches2("FOREIGN", "SERVER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_servers, "");
 
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
 	else if (TailMatches1("GRANT|REVOKE"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST13(Query_for_list_of_roles,
-					  "SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+			ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
 					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
 					  "EXECUTE", "USAGE", "ALL"));
 
@@ -2841,8 +2853,7 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
-			   ADDLIST15("",
-						 "ALL FUNCTIONS IN SCHEMA",
+			   ADDLIST15("ALL FUNCTIONS IN SCHEMA",
 						 "ALL SEQUENCES IN SCHEMA",
 						 "ALL TABLES IN SCHEMA",
 						 "DATABASE",
@@ -2874,23 +2885,23 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
 		if (TailMatches1("DATABASE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+			COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 		else if (TailMatches1("DOMAIN"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, "");
 		else if (TailMatches1("FUNCTION"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 		else if (TailMatches1("LANGUAGE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
+			COMPLETE_WITH_QUERY(Query_for_list_of_languages, "");
 		else if (TailMatches1("SCHEMA"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+			COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
 		else if (TailMatches1("SEQUENCE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
 		else if (TailMatches1("TABLE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 		else if (TailMatches1("TABLESPACE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
 		else if (TailMatches1("TYPE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, "");
 		else if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
@@ -2903,7 +2914,7 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
 			 (HeadMatches1("REVOKE") && TailMatches1("FROM")))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 
 	/* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */
 	else if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
@@ -2954,7 +2965,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
 	else if (TailMatches2("INSERT", "INTO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete "INSERT INTO <table> (" with attribute names */
 	else if (TailMatches4("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
@@ -2982,7 +2993,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete LOCK [TABLE] with a list of tables */
 	else if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   ADDLIST1("", "TABLE"));
+								   ADDLIST1("TABLE"));
 	else if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
@@ -3004,7 +3015,7 @@ psql_completion(const char *text, int start, int end)
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
 	else if (TailMatches1("NOTIFY"))
-		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
+		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'", "");
 
 /* OPTIONS */
 	else if (TailMatches1("OPTIONS"))
@@ -3012,7 +3023,7 @@ psql_completion(const char *text, int start, int end)
 
 /* OWNER TO  - complete with available roles */
 	else if (TailMatches2("OWNER", "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* ORDER BY */
 	else if (TailMatches3("FROM", MatchAny, "ORDER"))
@@ -3035,11 +3046,11 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
 	else if (Matches3("REASSIGN", "OWNED", "BY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	else if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 	else if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* REFRESH MATERIALIZED VIEW */
 	else if (Matches1("REFRESH"))
@@ -3048,9 +3059,9 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   ADDLIST1("", "CONCURRENTLY"));
+								   ADDLIST1("CONCURRENTLY"));
 	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
 	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
@@ -3068,13 +3079,13 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
 	else if (Matches2("REINDEX", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 	else if (Matches2("REINDEX", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 	else if (Matches2("REINDEX", "SCHEMA"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
 	else if (Matches2("REINDEX", "SYSTEM|DATABASE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+		COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 
 /* SECURITY LABEL */
 	else if (Matches1("SECURITY"))
@@ -3103,9 +3114,9 @@ psql_completion(const char *text, int start, int end)
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
 	else if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars, "");
 	else if (Matches1("SHOW"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars, "");
 	/* Complete "SET TRANSACTION" */
 	else if (Matches2("SET|BEGIN|START", "TRANSACTION") ||
 			 Matches2("BEGIN", "WORK") ||
@@ -3125,20 +3136,20 @@ psql_completion(const char *text, int start, int end)
 	/* SET CONSTRAINTS */
 	else if (Matches2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema,
-								   ADDLIST1("", "ALL"));
+								   ADDLIST1("ALL"));
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
 	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
 	else if (Matches2("SET", "ROLE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
 	else if (Matches2("SET", "SESSION"))
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
 	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_roles, "DEFAULT"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+							ADDLIST1("DEFAULT"));
 	/* Complete RESET SESSION with AUTHORIZATION */
 	else if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
@@ -3164,11 +3175,10 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST(my_list);
 		}
 		else if (TailMatches2("search_path", "TO|="))
-			COMPLETE_WITH_QUERY(
-				ADDLIST1(Query_for_list_of_schemas
-						 " AND nspname not like 'pg\\_toast%%' "
-						 " AND nspname not like 'pg\\_temp%%' ",
-						 "DEFAULT"));
+			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+								" AND nspname not like 'pg\\_toast%%' "
+								" AND nspname not like 'pg\\_temp%%' ",
+								ADDLIST1("DEFAULT"));
 		else
 		{
 			/* generic, type based, GUC support */
@@ -3179,7 +3189,7 @@ psql_completion(const char *text, int start, int end)
 				char		querybuf[1024];
 
 				snprintf(querybuf, sizeof(querybuf), Query_for_enum, prev2_wd);
-				COMPLETE_WITH_QUERY(querybuf);
+				COMPLETE_WITH_QUERY(querybuf, "");
 			}
 			else if (guctype && strcmp(guctype, "bool") == 0)
 				COMPLETE_WITH_LIST9("on", "off", "true", "false", "yes", "no",
@@ -3198,26 +3208,26 @@ psql_completion(const char *text, int start, int end)
 
 /* TABLE, but not TABLE embedded in other commands */
 	else if (Matches1("TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, "");
 
 /* TABLESAMPLE */
 	else if (TailMatches1("TABLESAMPLE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods, "");
 	else if (TailMatches2("TABLESAMPLE", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
 	else if (Matches1("TRUNCATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 /* UNLISTEN */
 	else if (Matches1("UNLISTEN"))
-		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
+		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'", "");
 
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
 	else if (TailMatches1("UPDATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete UPDATE <table> with "SET" */
 	else if (TailMatches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
@@ -3237,11 +3247,10 @@ psql_completion(const char *text, int start, int end)
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 false) {} /* FALL THROUGH */
 	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST3(Query_for_list_of_roles, 
-					 "CURRENT_USER", "PUBLIC", "USER"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+							ADDLIST3("CURRENT_USER", "PUBLIC", "USER"));
 	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, "");
 	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("SERVER");
 	else if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
@@ -3253,24 +3262,24 @@ psql_completion(const char *text, int start, int end)
  */
 	else if (Matches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST4("", "FULL", "FREEZE", "ANALYZE", "VERBOSE"));
+		   ADDLIST4("FULL", "FREEZE", "ANALYZE", "VERBOSE"));
 	else if (Matches2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST2("", "ANALYZE", "VERBOSE"));
+		   ADDLIST2("ANALYZE", "VERBOSE"));
 	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST1("", "VERBOSE"));
+		   ADDLIST1("VERBOSE"));
 	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST1("", "ANALYZE"));
+		   ADDLIST1("ANALYZE"));
 	else if (Matches2("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST1("", "ANALYZE"));
+		   ADDLIST1("ANALYZE"));
 	else if (Matches2("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST1("", "VERBOSE"));
+		   ADDLIST1("VERBOSE"));
 	else if (HeadMatches1("VACUUM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 
 /* WITH [RECURSIVE] */
 
@@ -3284,7 +3293,7 @@ psql_completion(const char *text, int start, int end)
 /* ANALYZE */
 	/* Complete with list of tables */
 	else if (Matches1("ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, "");
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
@@ -3294,11 +3303,11 @@ psql_completion(const char *text, int start, int end)
 /* ... FROM ... */
 /* TODO: also include SRF ? */
 	else if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 
 /* ... JOIN ... */
 	else if (TailMatches1("JOIN"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
@@ -3307,80 +3316,80 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatchesCS1("\\connect|\\c"))
 	{
 		if (!recognized_connection_string(text))
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+			COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 	}
 	else if (TailMatchesCS2("\\connect|\\c", MatchAny))
 	{
 		if (!recognized_connection_string(prev_wd))
-			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+			COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	}
 	else if (TailMatchesCS1("\\da*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, "");
 	else if (TailMatchesCS1("\\db*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
 	else if (TailMatchesCS1("\\dD*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, "");
 	else if (TailMatchesCS1("\\des*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_servers, "");
 	else if (TailMatchesCS1("\\deu*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, "");
 	else if (TailMatchesCS1("\\dew*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, "");
 	else if (TailMatchesCS1("\\df*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 
 	else if (TailMatchesCS1("\\dFd*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries, "");
 	else if (TailMatchesCS1("\\dFp*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers, "");
 	else if (TailMatchesCS1("\\dFt*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates, "");
 	/* must be at end of \dF alternatives: */
 	else if (TailMatchesCS1("\\dF*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations, "");
 
 	else if (TailMatchesCS1("\\di*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 	else if (TailMatchesCS1("\\dL*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
+		COMPLETE_WITH_QUERY(Query_for_list_of_languages, "");
 	else if (TailMatchesCS1("\\dn*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
 	else if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 	else if (TailMatchesCS1("\\ds*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
 	else if (TailMatchesCS1("\\dt*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	else if (TailMatchesCS1("\\dT*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, "");
 	else if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	else if (TailMatchesCS1("\\dv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	else if (TailMatchesCS1("\\dx*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_extensions, "");
 	else if (TailMatchesCS1("\\dm*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	else if (TailMatchesCS1("\\dE*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 	else if (TailMatchesCS1("\\dy*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* must be at end of \d alternatives: */
 	else if (TailMatchesCS1("\\d*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, "");
 
 	else if (TailMatchesCS1("\\ef"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 	else if (TailMatchesCS1("\\ev"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 
 	else if (TailMatchesCS1("\\encoding"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_encodings, "");
 	else if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
 		COMPLETE_WITH_LIST(sql_commands);
 	else if (TailMatchesCS1("\\password"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	else if (TailMatchesCS1("\\pset"))
 	{
 		static const char *const my_list[] =
@@ -3440,14 +3449,14 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
 	}
 	else if (TailMatchesCS1("\\sf*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 	else if (TailMatchesCS1("\\sv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	else if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
 							"\\ir|\\include_relative|\\o|\\out|"
 							"\\s|\\w|\\write|\\lo_import"))
 	{
-		completion_charp = "\\";
+		SET_COMP_CHARP("\\");
 		matches = completion_matches(text, complete_from_files);
 	}
 
@@ -3463,9 +3472,9 @@ psql_completion(const char *text, int start, int end)
 		if (ent)
 		{
 			if (ent->query)
-				COMPLETE_WITH_QUERY(ent->query);
+				COMPLETE_WITH_QUERY(ent->query, "");
 			else if (ent->squery)
-				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, NULL);
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, "");
 		}
 	}
 
@@ -3723,13 +3732,13 @@ _complete_from_query(int is_schema_query, const char *text, int state)
 							  char_length, e_text);
 
 			/* If an addon query was provided, use it */
-			if (completion_charp)
-				appendPQExpBuffer(&query_buffer, "\n%s", completion_charp);
+			if (COMPLETION_CHARP[0])
+				appendPQExpBuffer(&query_buffer, "\n%s", COMPLETION_CHARP);
 		}
 		else
 		{
 			/* completion_charp is an sprintf-style format string */
-			appendPQExpBuffer(&query_buffer, completion_charp,
+			appendPQExpBuffer(&query_buffer, COMPLETION_CHARP,
 							  char_length, e_text,
 							  e_info_charp, e_info_charp,
 							  e_info_charp2, e_info_charp2);
@@ -3844,18 +3853,17 @@ complete_from_list(const char *text, int state)
 static char *
 complete_from_const(const char *text, int state)
 {
-	Assert(completion_charp != NULL);
 	if (state == 0)
 	{
 		if (completion_case_sensitive)
-			return pg_strdup(completion_charp);
+			return pg_strdup(COMPLETION_CHARP);
 		else
 
 			/*
 			 * If case insensitive matching was requested initially, adjust
 			 * the case according to setting.
 			 */
-			return pg_strdup_keyword_case(completion_charp, text);
+			return pg_strdup_keyword_case(COMPLETION_CHARP, text);
 	}
 	else
 		return NULL;
@@ -3956,7 +3964,7 @@ complete_from_files(const char *text, int state)
 	if (state == 0)
 	{
 		/* Initialization: stash the unquoted input. */
-		unquoted_text = strtokx(text, "", NULL, "'", *completion_charp,
+		unquoted_text = strtokx(text, "", NULL, "'", COMPLETION_CHARP[0],
 								false, true, pset.encoding);
 		/* expect a NULL return for the empty string only */
 		if (!unquoted_text)
@@ -3977,7 +3985,7 @@ complete_from_files(const char *text, int state)
 		 * bother providing a macro to simplify this.
 		 */
 		ret = quote_if_needed(unquoted_match, " \t\r\n\"`",
-							  '\'', *completion_charp, pset.encoding);
+							  '\'', COMPLETION_CHARP[0], pset.encoding);
 		if (ret)
 			free(unquoted_match);
 		else
@@ -4035,7 +4043,7 @@ pg_strdup_keyword_case(const char *s, const char *ref)
 
 /* Construct codelet to append given keywords  */
 static char *
-additional_kw_query(char *prefix, const char *ref, int n, ...)
+additional_kw_query(const char *ref, int n, ...)
 {
 	va_list ap;
 	static PQExpBuffer qbuf = NULL;
@@ -4046,8 +4054,6 @@ additional_kw_query(char *prefix, const char *ref, int n, ...)
 	else
 		resetPQExpBuffer(qbuf);
 
-	appendPQExpBufferStr(qbuf, prefix);
-
 	/* Construct an additional queriy to append keywords */
 	appendPQExpBufferStr(qbuf, " UNION SELECT * FROM (VALUES ");
 
-- 
1.8.3.1

#32Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#31)
Re: IF (NOT) EXISTS in psql-completion

Hi

2016-04-04 7:58 GMT+02:00 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp

:

Thank you for testing. That is a silly mistake, sorry.

The attached is the fixed version.

# Can I add a suffix to format-patche's output files?

At Sat, 2 Apr 2016 07:18:32 +0200, Pavel Stehule <pavel.stehule@gmail.com>
wrote in <
CAFj8pRADF3rmQ3y33aeR1C7wOi2QsS65C8bBtiRNqU0zWVWayg@mail.gmail.com>

Finally I settled it by replacing comma expression with logical
OR or AND expresssion. gcc 4.9 compains for some unused variables
in flex output but it is the another issue.

I forgot to address COMPLETE_WITH_ATTTR but it needed an overhaul
of some macros and changing the type of completion_charp. The
third patch does it but it might be unacceptable..

something is wrong, autocomplete for CREATE TABLE IF NOT EXISTS doesn't
work

CREATE UNLOGGED/TEMP table is working.

Mmm. I mitakenly refactored multi-step matching.

else if (HeadMatches3("CREATE", MatchAny, "TABLE") &&
MidMatchAndRemove1(1, "TEMP|TEMPORARY|UNLOGGED") &&
Matches2("CREATE", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
ADDLIST1("IF NOT EXISTS"));

The completion runs only for CREATE AnyKeyword TABLE when
AnyKeyword is removable. It is wrong to do so when any of
prev_words[] that matches the last Matches() can be fileterd out
by the first Headmatches(). The same kind of mistake was found in
the following syntaxes. CREATE SEQENCE had one more mistake.

"CREATE [UNIQUE] INDEX"
"CREATE [TEMP] SEQUENCE"
"CREATE [TEMP..] TABLE"

It is arguable that it is proper to suggest existing object for
CREATE statement, but most of the statement is suggested. It is
semantically wrong but practically useful to know what kind of
word should be there.

I tested this patch and I didn't find any problem.

1. We want this patch - it increase a functionality of autocomplete - IF
(NOT) EXISTS is relative long phrase and autocomplete is nice. - next
implementation can be CREATE "OR REPLACE" FUNCTION

2. The patch is possible to apply - no problems, no problems with compiling

3. All regress tests passed without problems

4. Patch respects PostgreSQL's codding style and it is enough commented

5. The regress tests are not possible - interactive process

6. The documentation is not necessary

7. It should not to have any impacts on SQL or performance

I'll mark this patch as ready for commiter

Thank you for the patch

Regards

Pavel

Show quoted text

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#33Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#32)
Re: IF (NOT) EXISTS in psql-completion

Pavel Stehule <pavel.stehule@gmail.com> writes:

1. We want this patch - it increase a functionality of autocomplete

TBH, I do not think that is an agreed-to statement. I concur with
Peter's comments upthread questioning how much use-case there is for
interactive completion of IF (NOT) EXISTS. If it were a small and
uncomplicated patch, I wouldn't object ... but this is neither.

It increases the size of tab-complete.c by nearly 10%, and would
increase it more than that if it were adequately documented as to
what all these new macros and variables do. (To take just the first
example, APPEND_COMP_CHARP and SET_COMP_CHARP not only lack any
documentation, but have been inserted between a comment documenting
some macros and those macros. Another complaint in the same vein is
that MatchesN() no longer means even approximately what it did before,
but the comment for those macros hasn't been changed.) On top of that,
it seems like there's an awful lot of klugery going on here. An example
is the use of the COLLAPSE macro (hidden inside MidMatchAndRemoveN),
which seems like a seriously bad idea because it destroys data even if
the match the macro is embedded in ultimately fails. That will create
order dependencies between match rules, which is not something we want
IMO, most especially when it's not clearly marked in the match rules
what's dependent on what.

Seeing things like "if (something-with-side-effects && false)" doesn't
fill me with any great admiration for the cleanliness of the code, either.

In short, I'm not sold that we need autocomplete for IF EXISTS,
and if the price we have to pay for it is klugery on this scale,
it's no sale. I think this needs to be sent back for a considerable
amount of rethinking.

One thing that might be worth considering to get rid of this
side-effects-in-IF-conditions mess is to move the match rules into
a separate function so that after doing a successful match, we just
"return". This could be implemented in most places by adding
return statements into the COMPLETE_WITH_FOO macros. Then we would
not need the enormous else-if chain, but just simple independent
if statements, where we know a successful match will end with a
"return" instead of falling through to the next statement. The
big advantage of that is that then you can do operations with
side-effects explicitly as separate statements, instead of having
to make them look like phony else-if cases. So for example the
CREATE SCHEMA case might be handled like

if (Matches2("CREATE", "SCHEMA"))
{
... handle possible autocompletions of CREATE SCHEMA itself here ...

/* Else, move head match point past CREATE SCHEMA */
if ((n = find_last_index_of("CREATE")) > 0)
HEAD_SHIFT(n);
}

/*
* Statements that can appear in CREATE SCHEMA should be considered here!
*/

if (Matches2("CREATE", "TABLE"))
... handle CREATE TABLE here ...

... handle other statements that can appear in CREATE SCHEMA here ...

After exhausting the possibilities for sub-statements of CREATE SCHEMA,
we could either return failure if we'd seen CREATE SCHEMA:

/*
* Fail if we saw CREATE SCHEMA; no rules below here should be considered.
*/
if (head_shift > 0)
return false;

or reset the head match point before continuing with unrelated rules:

/*
* Done considering CREATE SCHEMA sub-rules, so forget about
* whether we saw CREATE SCHEMA.
*/
HEAD_SHIFT(0);

Immediately failing seems like the right thing for CREATE SCHEMA, but
maybe in other cases we just undo the head_shift change and keep trying.
This is still order dependent, but at least the places where we change
the match basis can be made to be fairly obvious actions instead of
being disguised as just-another-candidate-match.

I don't immediately have a better idea to replace COLLAPSE, but I really
don't like that as it stands. I wonder whether we could dodge the need
for it by just increasing head_shift when deciding to skip over an
IF (NOT) EXISTS clause. Otherwise, I think what I'd want to see is
some way to explicitly undo the effects of COLLAPSE when done with
rules that could benefit from it. Or at least a coding convention
that makes it clear that you return failure once you've considered
all subsidiary rules, rather than continuing on with unrelated rules
that would have a risk of false match thanks to the data removal.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#34Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Tom Lane (#33)
5 attachment(s)
Re: IF (NOT) EXISTS in psql-completion

Thank you for looking this and for the comment.

Since the end of this CF is quite soon and this seems in
uncommittable state, feel free to move this to the next CF if any
other patch with more priority.

The attached patch is rather WIPs.

1. The same as the first one in the previous version. Just adding
suggestion and making further matching ignoring them.

2. The same as the second one in the previous version. Fix so
that the case of almost all keywords follow an input, except
attributes.

3. The same as the third one in the previous version. Changes
COMPLETE_WITH_ATTR and COMPLETE_WITH_QUERY so that all
keywords can follow an input.

== The followings are the new patches

4. Applicable on top of 3. Addressing the comments. Refactors
tab-completion.c so that it no longer needs the long-long
else-if sequences. For the readability. Getting rid of the
if(.. && false) messes.

5. A sample implement of completion code for CREATE/GRANT/REVOKE.

At Wed, 06 Apr 2016 18:40:57 -0400, Tom Lane <tgl@sss.pgh.pa.us> wrote in <4317.1459982457@sss.pgh.pa.us>

Pavel Stehule <pavel.stehule@gmail.com> writes:

1. We want this patch - it increase a functionality of autocomplete

TBH, I do not think that is an agreed-to statement. I concur with
Peter's comments upthread questioning how much use-case there is for
interactive completion of IF (NOT) EXISTS. If it were a small and
uncomplicated patch, I wouldn't object ... but this is neither.

The objective of this patch is not only suggesting "IF (NOT)
EXISTS", but also, enabling to continue completion even such
"noise" words are in command line, without providing multiple
match descriptions for both of with and without the
noise. Currently such words just prohibits continueing
completion.

So if many of us consider that completing such words is just a
crap, it is possible to remove only suggestion, but preserve
ignoring of such words.

It increases the size of tab-complete.c by nearly 10%, and would

The increase is natural since it treats additional syntax but the
amount of 10% is arguable. However there's some more "noise"
words to be addressed.

CREATE ROLE name [[WITH] option [...]]
ALTER TABLE name ... col [SET DATA] TYPE
CREATE [LOCAL|GLOBAL] .. TABLE
CREATE [OR REPLACE] [TRUSTED] [PROCEDUAL] LANGUAGE
CREATE [OR REPLACE] FUNCTION
...

increase it more than that if it were adequately documented as to
what all these new macros and variables do. (To take just the first
example, APPEND_COMP_CHARP and SET_COMP_CHARP not only lack any
documentation, but have been inserted between a comment documenting
some macros and those macros. Another complaint in the same vein is
that MatchesN() no longer means even approximately what it did before,
but the comment for those macros hasn't been changed.)

Added simple comments for them and others.

On top of that,
it seems like there's an awful lot of klugery going on here. An example
is the use of the COLLAPSE macro (hidden inside MidMatchAndRemoveN),
which seems like a seriously bad idea because it destroys data even if
the match the macro is embedded in ultimately fails. That will create
order dependencies between match rules, which is not something we want
IMO, most especially when it's not clearly marked in the match rules
what's dependent on what.

Currently some of the matching conditions (even remote each
other) have order dependencies. Altering them caused some
unexpected change of completion behavior. They are already not
order independent at all, I think. (I'm sorry not to give an
instance for now.. It might be resolved by recent changes.)

But I must admit that the usage of MidMatchAndRemoveN is actually
a bit complicated, but it is finally removed in this patch.

Seeing things like "if (something-with-side-effects && false)" doesn't
fill me with any great admiration for the cleanliness of the code, either.

This is also removed in the attached patch set.

In short, I'm not sold that we need autocomplete for IF EXISTS,
and if the price we have to pay for it is klugery on this scale,
it's no sale. I think this needs to be sent back for a considerable
amount of rethinking.

One thing that might be worth considering to get rid of this
side-effects-in-IF-conditions mess is to move the match rules into
a separate function so that after doing a successful match, we just
"return". This could be implemented in most places by adding
return statements into the COMPLETE_WITH_FOO macros. Then we would
not need the enormous else-if chain, but just simple independent
if statements, where we know a successful match will end with a
"return" instead of falling through to the next statement. The
big advantage of that is that then you can do operations with
side-effects explicitly as separate statements, instead of having
to make them look like phony else-if cases. So for example the
CREATE SCHEMA case might be handled like

if (Matches2("CREATE", "SCHEMA"))
{
... handle possible autocompletions of CREATE SCHEMA itself here ...

/* Else, move head match point past CREATE SCHEMA */
if ((n = find_last_index_of("CREATE")) > 0)
HEAD_SHIFT(n);
}

/*
* Statements that can appear in CREATE SCHEMA should be considered here!
*/

if (Matches2("CREATE", "TABLE"))
... handle CREATE TABLE here ...

... handle other statements that can appear in CREATE SCHEMA here ...

After exhausting the possibilities for sub-statements of CREATE SCHEMA,
we could either return failure if we'd seen CREATE SCHEMA:

/*
* Fail if we saw CREATE SCHEMA; no rules below here should be considered.
*/
if (head_shift > 0)
return false;

The previous patch designed so as not to make drastic changes to
psql_completion, but since it makes the function ugly I
refactored the function in this patch set.

And as an example, the last (5th) patch does this and introduces
CREATE SCHEMA completion in more matured way (of behavior, not
coding), and make the completion for GRANT/REVOKE more simplly.

or reset the head match point before continuing with unrelated rules:

/*
* Done considering CREATE SCHEMA sub-rules, so forget about
* whether we saw CREATE SCHEMA.
*/
HEAD_SHIFT(0);

Immediately failing seems like the right thing for CREATE SCHEMA, but
maybe in other cases we just undo the head_shift change and keep trying.

This patch is doing the former since the completion for "CREATE
SCHEMA" cannot does other than returning so far.

This is still order dependent, but at least the places where we change
the match basis can be made to be fairly obvious actions instead of
being disguised as just-another-candidate-match.

I don't immediately have a better idea to replace COLLAPSE, but I really
don't like that as it stands. I wonder whether we could dodge the need
for it by just increasing head_shift when deciding to skip over an
IF (NOT) EXISTS clause.

How COLLAPSE can be avoided is shown by the CREATE/GRANT/REOKE
code after applying the last patch. If this way is not bad, I'll
do this on the whole psql_completion.

Otherwise, I think what I'd want to see is
some way to explicitly undo the effects of COLLAPSE when done with
rules that could benefit from it. Or at least a coding convention
that makes it clear that you return failure once you've considered
all subsidiary rules, rather than continuing on with unrelated rules
that would have a risk of false match thanks to the data removal.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

0005-Restructure-completion-code-for-CREATE-GRANT-REVOKE-.patchtext/x-patch; charset=us-asciiDownload
From 5a3b2ba812c3a4df1f0e93a93e3fb7ea2e0ca831 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Thu, 7 Apr 2016 17:16:50 +0900
Subject: [PATCH 5/5] Restructure completion code for CREATE/GRANT/REVOKE
 commands.

Since we can write completion code more freely, this patch enhances
CREATE SCHEMA and its subcommands. Code for GRANT/REVOKE is rewritten
in maybe simpler and firm way.
---
 src/bin/psql/tab-complete-macros.h |  72 +++-----
 src/bin/psql/tab-complete.c        | 337 ++++++++++++++++++++-----------------
 2 files changed, 205 insertions(+), 204 deletions(-)

diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
index bd89fbb..889ecd9 100644
--- a/src/bin/psql/tab-complete-macros.h
+++ b/src/bin/psql/tab-complete-macros.h
@@ -25,9 +25,14 @@
 #define prev8_wd  (previous_words[7])
 #define prev9_wd  (previous_words[8])
 
+
 /* Move the position of the beginning word for matching macros.  */
 #define SHIFTHEAD(n) \
-	(head_shift += n)
+	(head_shift += (n))
+
+/* Set the position of the beginning word for matching macros.  */
+#define SETHEAD(n) \
+	(head_shift = (n))
 
 /* Return the number of stored words counting head shift */
 #define WORD_COUNT() (previous_words_count - head_shift)
@@ -42,7 +47,7 @@
  */
 #define COLLAPSE(s, n)							\
     do { \
-		memmove(previous_words + HEAD_INDEX(n), previous_words + HEAD_INDEX(0), \
+		memmove(previous_words + HEAD_INDEX((s) + n), previous_words + HEAD_INDEX((s)), \
 				sizeof(char *) * head_shift); \
 		previous_words_count -= (n); \
 	} while(0)
@@ -220,59 +225,20 @@
 	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&			\
 	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]))
 
-/*
- * Macros for matching N words from the head of the line, this is equivelant
- * to MidMatchesN from the first word in the line.
- */
 #define HeadMatches1(p1) \
-	(HEAD_INDEX(1) >=0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]))
-
+	MidMatches1(1, p1)
 #define HeadMatches2(p1, p2) \
-	(HEAD_INDEX(2) >= 0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]) &&	\
-	 word_matches(p2, previous_words[HEAD_INDEX(2)]))
-
+	MidMatches2(1, p1, p2)
 #define HeadMatches3(p1, p2, p3) \
-	(HEAD_INDEX(3) >= 0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
-	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
-	 word_matches(p3, previous_words[HEAD_INDEX(3)]))
-
+	MidMatches3(1, p1, p2, p3)
 #define HeadMatches4(p1, p2, p3, p4) \
-	(HEAD_INDEX(4) >= 0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
-	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
-	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
-	 word_matches(p4, previous_words[HEAD_INDEX(4)]))
-
+	MidMatches4(1, p1, p2, p3, p4)
 #define HeadMatches5(p1, p2, p3, p4, p5) \
-	(HEAD_INDEX(5) >= 0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
-	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
-	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
-	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
-	 word_matches(p5, previous_words[HEAD_INDEX(5)]))
-
-#define HeadMatches6(p1, p2, p3, p4, p5, p6)		\
-	(HEAD_INDEX(6) >= 0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
-	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
-	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
-	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
-	 word_matches(p5, previous_words[HEAD_INDEX(5)]) && \
-	 word_matches(p6, previous_words[HEAD_INDEX(6)]))
-
-#define HeadMatches7(p1, p2, p3, p4, p5, p6, p7)	\
-	(HEAD_INDEX(7) >= 0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
-	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
-	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
-	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
-	 word_matches(p5, previous_words[HEAD_INDEX(5)]) && \
-	 word_matches(p6, previous_words[HEAD_INDEX(6)]) && \
-	 word_matches(p7, previous_words[HEAD_INDEX(7)]))
-
+	MidMatches5(1, p1, p2, p3, p4, p5)
+#define HeadMatches6(p1, p2, p3, p4, p5, p6) \
+	MidMatches6(1, p1, p2, p3, p4, p5, p6)
+#define HeadMatches7(p1, p2, p3, p4, p5, p6, p7) \
+	MidMatches7(1, p1, p2, p3, p4, p5, p6, p7)
 
 /*
  * completion_charp is now not a simple string, but a PQExpBuffer. These
@@ -482,7 +448,7 @@ do { \
  */
 #define COMPLETE_THING(p) \
 do { \
-	const pgsql_thing_t *ent = find_thing_entry(prev_wd);	\
+	const pgsql_thing_t *ent = find_thing_entry(previous_words[-(p) - 1]);	\
 	if (ent) \
 	{ \
 		if (ent->query) \
@@ -503,10 +469,10 @@ do { \
 #define ADDLIST4(s1, s2, s3, s4) \
 	additional_kw_query(text, 4, s1, s2, s3, s4)
 #define ADDLIST13(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
-	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+	additional_kw_query(text, 13, s1, s2, s3, s4, s5, s6, s7,		\
 						s8, s9, s10, s11, s12, s13)
 #define ADDLIST15(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
-	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+	additional_kw_query(text, 15, s1, s2, s3, s4, s5, s6, s7,		\
 						s8, s9, s10, s11, s12, s13, s14, s15)
 
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 6f227b0..0c8248d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1059,30 +1059,196 @@ psql_completion_internal(const char *text, char **previous_words,
 	 */
 	if (HeadMatches2("CREATE", "SCHEMA"))
 	{
-		int n;
+		int n, ng;
 
 		/* CREATE SCHEMA <name> */
 		if (Matches2("CREATE", "SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas,
-								ADDLIST1("IF NOT EXISTS"));
-		/* Remove optional words IF NOT EXISTS */
-		if (HeadMatches2("CREATE", "SCHEMA") &&
-			MidMatches3(2, "IF", "NOT", "EXISTS"))
-			COLLAPSE(2, 3);
+								ADDLIST2("IF NOT EXISTS", "AUTHORIZATION"));
+		/* Move to just after CREATE SCHEMA */
+		SHIFTHEAD(2);
+		/* Move to just after IF NOT EXISTS if any */
+		if (Matches3("IF", "NOT", "EXISTS"))
+			SHIFTHEAD(3);
+
+		/* CREATE SCHEMA with schemas */
+		if (WORD_COUNT() == 0)
+			COMPLETE_WITH_QUERY(Query_for_list_of_schemas,
+								ADDLIST1("AUTHORIZATION"));
+
+		/* AUTHORIZATION or subcommands if the first word seems a shema name */
+		if (WORD_COUNT() == 1 && pg_strcasecmp(prev_wd, "AUTHORIZATION") != 0)
+			COMPLETE_WITH_LIST3("AUTHORIZATION", "CREATE", "GRANT");
+
+		/* AUTHORIZATION with roles*/
+		if (Matches1("AUTHORIZATION"))
+			COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+								ADDLIST2("CURRENT_USER", "SESSION_USER"));
+
+		/* Move to just after AUTHORIZATION <role> */
+		if (Matches2("AUTHORIZATION", MatchAny))
+			SHIFTHEAD(2);
+
+		/* CREATE SCHEMA [<name>] with subcommands */
+		if (WORD_COUNT() < 2)
+			COMPLETE_WITH_LIST2("CREATE", "GRANT");
 
-		if (Matches2("CREATE", "SCHEMA"))
-			COMPLETE_THING();
-		
 		/* Else, move head match point past CREATE SCHEMA and go through */
-		if ((n = find_last_index_of("CREATE",
-									previous_words, previous_words_count)) > 0)
-		SHIFTHEAD(n);
+		n = find_last_index_of("CREATE", previous_words, previous_words_count);
+		ng = find_last_index_of("GRANT", previous_words, previous_words_count);
+		if (ng > n) n = ng;
+
+		if (n == 0)
+			return NULL;
+
+		SETHEAD(n);				/* n is an absolute position */
 	}
 
 /* CREATE */
 	/* complete with something you can create */
-	if (Matches1("CREATE"))
-		return completion_matches(text, create_command_generator);
+	if (HeadMatches1("CREATE"))
+	{
+		if (Matches1("CREATE"))
+		{
+			if (head_shift == 0)
+				return completion_matches(text, create_command_generator);
+
+			/* Completion for CREATE as a subcommand of CREATE SCHEMA */
+			COMPLETE_WITH_LIST7("UNLOGGED", "TABLE", "UNIQUE", "INDEX",
+								"SEQUENCE", "TRIGGER", "VIEW");
+		}
+
+		/* CREATE TABLE  */
+		if (HeadMatches2("CREATE", "TABLE"))
+		{
+			int pos_of_thingname = -1;
+
+			/* Move to just after CREATE TABLE */
+			SHIFTHEAD(2);
+			/* CREATE TABLE with table names */
+			if (WORD_COUNT() == 0)
+				COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+										   ADDLIST1("IF NOT EXISTS"));
+			/* Move to just after IF NOT EXISTS, if any */
+			if (HeadMatches3("IF", "NOT", "EXISTS"))
+			{
+				pos_of_thingname -= 3;
+				SHIFTHEAD(3);
+			}
+			/* CREATE TABLE with table names */
+			if (WORD_COUNT() == 0)
+				COMPLETE_THING(pos_of_thingname);
+		}
+	}
+
+	if (HeadMatches1("GRANT|REVOKE"))
+	{
+		bool is_grant = HeadMatches1("GRANT");
+
+		/* Move to just after GRANT|REVOKE */
+		SHIFTHEAD(1);
+
+		/* Complete GRANT/REVOKE with a list of roles and privileges */
+		if (WORD_COUNT() == 0)
+			COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+				ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
+					"REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
+					"EXECUTE", "USAGE", "ALL"));
+
+		/* Complete GRANT/REVOKE <privilege> with "ON" */
+		if (Matches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|"
+					 "TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
+			COMPLETE_WITH_CONST("ON");
+		/* Complete GRANT/REVOKE <role> with TO/FROM */
+		if (WORD_COUNT() == 1)
+			COMPLETE_WITH_CONST(is_grant ? "TO" : "FROM");
+		
+		/* Move to just after ON/TO/FROM */
+		SHIFTHEAD(1);
+
+		/*
+		 * Complete GRANT/REVOKE <sth> ON with a list of tables, views, and
+		 * sequences along with some keywords like DATABASE.
+		 *
+		 * Note: GRANT/REVOKE can get quite complex; tab-completion as
+		 * implemented here will only work if the privilege list contains
+		 * exactly one privilege.
+		 */
+		if (Matches1("ON"))
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
+									   ADDLIST15("ALL FUNCTIONS IN SCHEMA",
+												 "ALL SEQUENCES IN SCHEMA",
+												 "ALL TABLES IN SCHEMA",
+												 "DATABASE",
+												 "DOMAIN",
+												 "FOREIGN DATA WRAPPER",
+												 "FOREIGN SERVER",
+												 "FUNCTION",
+												 "LANGUAGE",
+												 "LARGE OBJECT",
+												 "SCHEMA",
+												 "SEQUENCE",
+												 "TABLE",
+												 "TABLESPACE",
+												 "TYPE"));
+		/* Move to just after ON */
+		SHIFTHEAD(1);
+
+		/* Complete fixed words  */
+		if (Matches1("ALL"))
+			COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
+								"TABLES IN SCHEMA");
+
+		if (Matches1("FOREIGN"))
+			COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
+
+		/*
+		 * Complete object names and move to just after the name
+		 */
+		if (HeadMatches1("DATABASE|DOMAIN|FUNCTION|LANGUAGE|SCHEMA|SEQUENCE"
+						 "TABLE|TABLESPACE|TYPE"))
+		{
+			if (WORD_COUNT() == 1)
+				COMPLETE_THING(-1);
+			SHIFTHEAD(2);
+		}
+		else if(HeadMatches3("FOREIGN", "DATA", "WRAPPER"))
+		{
+			if (WORD_COUNT() == 3)
+				COMPLETE_WITH_QUERY(Query_for_list_of_fdws, "");
+			SHIFTHEAD(4);
+		}
+		else if(HeadMatches2("FOREIGN", "SERVER"))
+		{
+			if (WORD_COUNT() == 2)
+				COMPLETE_THING(-1);
+			SHIFTHEAD(3);
+		}
+		else if (HeadMatches4("ALL", MatchAny, "IN", "SCHEMA"))
+		{
+			if (WORD_COUNT() == 4)
+				COMPLETE_THING(-1);
+			SHIFTHEAD(5);
+		}
+		else
+			SHIFTHEAD(1);	/* Must be a bare tablename */
+
+		/* Complete "GRANT/REVOKE * ON *" with "TO/FROM" */
+		if (WORD_COUNT() == 0)
+			COMPLETE_WITH_CONST(is_grant ? "TO" : "FROM");
+
+		/*
+		 * Complete "GRANT/REVOKE ... TO/FROM" with username, PUBLIC,
+		 * CURRENT_USER, or SESSION_USER.
+		 */
+		if (WORD_COUNT() == 1)
+			COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
+	}
+/*
+ * Fail if we saw CREATE SCHEMA; no rules below here should be considered.
+ */
+	if (head_shift > 0)
+		return NULL;
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
@@ -1965,7 +2131,7 @@ psql_completion_internal(const char *text, char **previous_words,
 			COLLAPSE(2, 3);
 	}
 	if (Matches2("CREATE", "INDEX"))
-		COMPLETE_THING();
+		COMPLETE_THING(0);
 	/* Complete CREATE INDEX [<name>] ON with a list of tables */
 	if (Matches4("CREATE", "INDEX", MatchAny, "ON") ||
 			 Matches3("CREATE", "INDEX", "ON"))
@@ -2085,7 +2251,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COLLAPSE(2, 3);
 
 	if (Matches2("CREATE", "TABLE"))
-		COMPLETE_THING();
+		COMPLETE_THING(-1);
 /* CREATE TABLESPACE */
 	if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
@@ -2313,7 +2479,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COLLAPSE(2, 2);
 
 	if (Matches2("DROP", "VIEW"))
-		COMPLETE_THING();
+		COMPLETE_THING(0);
 	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   ADDLIST1("IF EXISTS"));
@@ -2450,14 +2616,14 @@ psql_completion_internal(const char *text, char **previous_words,
 		COLLAPSE(2, 2);
 
 	if (Matches2("DROP", MatchAny))
-		COMPLETE_THING();
+		COMPLETE_THING(0);
 	if (HeadMatches4("DROP", "TEXT", "SEARCH",
 					 "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE") &&
 		MidMatches2(4, "IF", "EXISTS"))
 		COLLAPSE(4, 2);
 
 	if (Matches4("DROP", "TEXT", "SEARCH", MatchAny))
-		COMPLETE_THING();
+		COMPLETE_THING(0);
 
 /* EXECUTE */
 	if (Matches1("EXECUTE"))
@@ -2512,137 +2678,6 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatches2("FOREIGN", "SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers, "");
 
-/* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	if (TailMatches1("GRANT|REVOKE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
-			ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
-					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
-					  "EXECUTE", "USAGE", "ALL"));
-
-	/*
-	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
-	 * TO/FROM
-	 */
-	if (TailMatches2("GRANT|REVOKE", MatchAny))
-	{
-		if (TailMatches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
-			COMPLETE_WITH_CONST("ON");
-		if (TailMatches2("GRANT", MatchAny))
-			COMPLETE_WITH_CONST("TO");
-		else
-			COMPLETE_WITH_CONST("FROM");
-	}
-
-	/*
-	 * Complete GRANT/REVOKE <sth> ON with a list of tables, views, and
-	 * sequences.
-	 *
-	 * Keywords like DATABASE, FUNCTION, LANGUAGE and SCHEMA added to query
-	 * result via UNION; seems to work intuitively.
-	 *
-	 * Note: GRANT/REVOKE can get quite complex; tab-completion as implemented
-	 * here will only work if the privilege list contains exactly one
-	 * privilege.
-	 */
-	if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
-			   ADDLIST15("ALL FUNCTIONS IN SCHEMA",
-						 "ALL SEQUENCES IN SCHEMA",
-						 "ALL TABLES IN SCHEMA",
-						 "DATABASE",
-						 "DOMAIN",
-						 "FOREIGN DATA WRAPPER",
-						 "FOREIGN SERVER",
-						 "FUNCTION",
-						 "LANGUAGE",
-						 "LARGE OBJECT",
-						 "SCHEMA",
-						 "SEQUENCE",
-						 "TABLE",
-						 "TABLESPACE",
-						 "TYPE"));
-
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
-		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
-							"TABLES IN SCHEMA");
-
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
-		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
-
-	/*
-	 * Complete "GRANT/REVOKE * ON DATABASE/DOMAIN/..." with a list of
-	 * appropriate objects.
-	 *
-	 * Complete "GRANT/REVOKE * ON *" with "TO/FROM".
-	 */
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
-	{
-		if (TailMatches1("DATABASE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
-		if (TailMatches1("DOMAIN"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, "");
-		if (TailMatches1("FUNCTION"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
-		if (TailMatches1("LANGUAGE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_languages, "");
-		if (TailMatches1("SCHEMA"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
-		if (TailMatches1("SEQUENCE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
-		if (TailMatches1("TABLE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
-		if (TailMatches1("TABLESPACE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
-		if (TailMatches1("TYPE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, "");
-		if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
-			COMPLETE_WITH_CONST("TO");
-		else
-			COMPLETE_WITH_CONST("FROM");
-	}
-
-	/*
-	 * Complete "GRANT/REVOKE ... TO/FROM" with username, PUBLIC,
-	 * CURRENT_USER, or SESSION_USER.
-	 */
-	if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
-			 (HeadMatches1("REVOKE") && TailMatches1("FROM")))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
-
-	/* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */
-	if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
-		COMPLETE_WITH_CONST("TO");
-	if (HeadMatches1("REVOKE") && TailMatches3("ON", MatchAny, MatchAny))
-		COMPLETE_WITH_CONST("FROM");
-
-	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
-	{
-		if (TailMatches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
-			COMPLETE_WITH_CONST("TO");
-		else
-			COMPLETE_WITH_CONST("FROM");
-	}
-
-	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
-	{
-		if (TailMatches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
-			COMPLETE_WITH_CONST("TO");
-		else
-			COMPLETE_WITH_CONST("FROM");
-	}
-
-	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
-	{
-		if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
-			COMPLETE_WITH_CONST("TO");
-		else
-			COMPLETE_WITH_CONST("FROM");
-	}
-
 /* GROUP BY */
 	if (TailMatches3("FROM", MatchAny, "GROUP"))
 		COMPLETE_WITH_CONST("BY");
@@ -3164,7 +3199,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * check if that was the previous word. If so, execute the query to get a
 	 * list of them.
 	 */
-	COMPLETE_THING();
+	COMPLETE_THING(0);
 }
 
 /*
-- 
1.8.3.1

0001-Suggest-IF-NOT-EXISTS-for-tab-completion-of-psql.patchtext/x-patch; charset=us-asciiDownload
From a366798c23fcf2d76819b6085fbdce15dff2eeb8 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 5 Feb 2016 16:50:35 +0900
Subject: [PATCH 1/5] Suggest IF (NOT) EXISTS for tab-completion of psql

This patch lets psql to suggest "IF (NOT) EXISTS". Addition to that,
since this patch introduces some mechanism for syntactical robustness,
it allows psql completion to omit some optional part on matching.
---
 src/bin/psql/tab-complete.c | 630 ++++++++++++++++++++++++++++++++++++--------
 1 file changed, 522 insertions(+), 108 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index cb8a06d..56776e1 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -656,6 +656,10 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "   FROM pg_catalog.pg_roles "\
 "  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"
 
+#define Query_for_list_of_rules \
+"SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules "\
+" WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"
+
 #define Query_for_list_of_grant_roles \
 " SELECT pg_catalog.quote_ident(rolname) "\
 "   FROM pg_catalog.pg_roles "\
@@ -763,6 +767,11 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "SELECT pg_catalog.quote_ident(tmplname) FROM pg_catalog.pg_ts_template "\
 " WHERE substring(pg_catalog.quote_ident(tmplname),1,%d)='%s'"
 
+#define Query_for_list_of_triggers \
+"SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger "\
+" WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s' AND "\
+"       NOT tgisinternal"
+
 #define Query_for_list_of_fdws \
 " SELECT pg_catalog.quote_ident(fdwname) "\
 "   FROM pg_catalog.pg_foreign_data_wrapper "\
@@ -907,7 +916,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
 	{"POLICY", NULL, NULL},
 	{"ROLE", Query_for_list_of_roles},
-	{"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"},
+	{"RULE", Query_for_list_of_rules},
 	{"SCHEMA", Query_for_list_of_schemas},
 	{"SEQUENCE", NULL, &Query_for_list_of_sequences},
 	{"SERVER", Query_for_list_of_servers},
@@ -916,7 +925,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"TEMP", NULL, NULL, THING_NO_DROP},		/* for CREATE TEMP TABLE ... */
 	{"TEMPLATE", Query_for_list_of_ts_templates, NULL, THING_NO_SHOW},
 	{"TEXT SEARCH", NULL, NULL},
-	{"TRIGGER", "SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s' AND NOT tgisinternal"},
+	{"TRIGGER", Query_for_list_of_triggers},
 	{"TYPE", NULL, &Query_for_list_of_datatypes},
 	{"UNIQUE", NULL, NULL, THING_NO_DROP},		/* for CREATE UNIQUE INDEX ... */
 	{"UNLOGGED", NULL, NULL, THING_NO_DROP},	/* for CREATE UNLOGGED TABLE
@@ -945,6 +954,7 @@ static char **complete_from_variables(const char *text,
 					const char *prefix, const char *suffix, bool need_value);
 static char *complete_from_files(const char *text, int state);
 
+static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
@@ -953,6 +963,7 @@ static char **get_previous_words(int point, char **buffer, int *nwords);
 
 static char *get_guctype(const char *varname);
 
+static const pgsql_thing_t *find_thing_entry(char *word);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -1108,6 +1119,9 @@ psql_completion(const char *text, int start, int end)
 	/* The number of words found on the input line. */
 	int			previous_words_count;
 
+	/* The number of prefixing words to be ignored */
+	int			head_shift = 0;
+
 	/*
 	 * For compactness, we use these macros to reference previous_words[].
 	 * Caution: do not access a previous_words[] entry without having checked
@@ -1125,31 +1139,73 @@ psql_completion(const char *text, int start, int end)
 #define prev8_wd  (previous_words[7])
 #define prev9_wd  (previous_words[8])
 
+	/* Move the position of the beginning word for matching macros.  */
+#define HEADSHIFT(n) \
+	((head_shift += n) || true)
+
+	/* Return the number of stored words counting head shift */
+#define WORD_COUNT() (previous_words_count - head_shift)
+
+	/* Return the true index in previous_words for index from the beginning */
+#define HEAD_INDEX(n) \
+	(previous_words_count - head_shift - (n))
+
+	/*
+	 * remove n words from current shifted position, see MidMatchAndRevmove2
+	 * for the reason for the return value
+	 */
+#define COLLAPSE(n) \
+	((memmove(previous_words + HEAD_INDEX(n), previous_words + HEAD_INDEX(0), \
+			 sizeof(char *) * head_shift), \
+	 previous_words_count -= (n)) && false)
+
+	/*
+	 * Find the position the specified word occurs last and shift to there.
+	 * This is used to ignore the words before there.
+	 */
+#define SHIFT_TO_LAST1(p1) \
+	(HEADSHIFT(find_last_index_of(p1, previous_words, previous_words_count))|| \
+	 true)
+
+	/*
+	 * Remove the specified words if they match from the sth word in
+	 * previous_words. This is a bit tricky, COLLAPSE is skipped when
+	 * HeadMatches failed but the last HEADSHIFT anyway should be done.
+	 */
+#define MidMatchAndRemove1(s, p1) \
+	((HEADSHIFT(s) && HeadMatches1(p1) && COLLAPSE(1)) || HEADSHIFT(-s))
+
+#define MidMatchAndRemove2(s, p1, p2) \
+	((HEADSHIFT(s) && HeadMatches2(p1, p2) && COLLAPSE(2)) || HEADSHIFT(-s))
+
+#define MidMatchAndRemove3(s, p1, p2, p3)									\
+	((HEADSHIFT(s) && HeadMatches3(p1, p2, p3) && COLLAPSE(3)) || HEADSHIFT(-s))
+
 	/* Macros for matching the last N words before point, case-insensitively. */
 #define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches(p1, prev_wd))
 
 #define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd))
 
 #define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
+	(WORD_COUNT() >= 3 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd))
 
 #define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
+	(WORD_COUNT() >= 4 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
 	 word_matches(p4, prev4_wd))
 
 #define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
+	(WORD_COUNT() >= 5 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1157,7 +1213,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p5, prev5_wd))
 
 #define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
+	(WORD_COUNT() >= 6 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1166,7 +1222,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p6, prev6_wd))
 
 #define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
+	(WORD_COUNT() >= 7 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1176,7 +1232,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p7, prev7_wd))
 
 #define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
+	(WORD_COUNT() >= 8 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1187,7 +1243,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p8, prev8_wd))
 
 #define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
+	(WORD_COUNT() >= 9 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1200,43 +1256,43 @@ psql_completion(const char *text, int start, int end)
 
 	/* Macros for matching the last N words before point, case-sensitively. */
 #define TailMatchesCS1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches_cs(p1, prev_wd))
 #define TailMatchesCS2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches_cs(p1, prev_wd) && \
 	 word_matches_cs(p2, prev2_wd))
 
 	/*
-	 * Macros for matching N words beginning at the start of the line,
+	 * Macros for matching N words exactly to the line,
 	 * case-insensitively.
 	 */
 #define Matches1(p1) \
-	(previous_words_count == 1 && \
+	(WORD_COUNT() == 1 && \
 	 TailMatches1(p1))
 #define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
+	(WORD_COUNT() == 2 && \
 	 TailMatches2(p1, p2))
 #define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
+	(WORD_COUNT() == 3 && \
 	 TailMatches3(p1, p2, p3))
 #define Matches4(p1, p2, p3, p4) \
-	(previous_words_count == 4 && \
+	(WORD_COUNT() == 4 && \
 	 TailMatches4(p1, p2, p3, p4))
 #define Matches5(p1, p2, p3, p4, p5) \
-	(previous_words_count == 5 && \
+	(WORD_COUNT() == 5 && \
 	 TailMatches5(p1, p2, p3, p4, p5))
 #define Matches6(p1, p2, p3, p4, p5, p6) \
-	(previous_words_count == 6 && \
+	(WORD_COUNT() == 6 && \
 	 TailMatches6(p1, p2, p3, p4, p5, p6))
 #define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(previous_words_count == 7 && \
+	(WORD_COUNT() == 7 && \
 	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
 #define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(previous_words_count == 8 && \
+	(WORD_COUNT() == 8 && \
 	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
 #define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(previous_words_count == 9 && \
+	(WORD_COUNT() == 9 && \
 	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
 
 	/*
@@ -1244,19 +1300,53 @@ psql_completion(const char *text, int start, int end)
 	 * what is after them, case-insensitively.
 	 */
 #define HeadMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]))
+	(HEAD_INDEX(1) >=0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]))
 
 #define HeadMatches2(p1, p2) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]))
+	(HEAD_INDEX(2) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]))
 
 #define HeadMatches3(p1, p2, p3) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]) && \
-	 word_matches(p3, previous_words[previous_words_count - 3]))
+	(HEAD_INDEX(3) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]))
+
+#define HeadMatches4(p1, p2, p3, p4) \
+	(HEAD_INDEX(4) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]))
+
+#define HeadMatches5(p1, p2, p3, p4, p5) \
+	(HEAD_INDEX(5) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]))
+
+#define HeadMatches6(p1, p2, p3, p4, p5, p6)		\
+	(HEAD_INDEX(6) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]) && \
+	 word_matches(p6, previous_words[HEAD_INDEX(6)]))
+
+#define HeadMatches7(p1, p2, p3, p4, p5, p6, p7)	\
+	(HEAD_INDEX(7) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]) && \
+	 word_matches(p6, previous_words[HEAD_INDEX(6)]) && \
+	 word_matches(p7, previous_words[HEAD_INDEX(7)]))
 
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
@@ -1328,9 +1418,16 @@ psql_completion(const char *text, int start, int end)
 	else if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
+	/*
+	 * If this is in CREATE SCHEMA, seek to the last CREATE and regard it as
+	 * current command to complete.
+	 */
+	else if (HeadMatches2("CREATE", "SCHEMA") &&
+			 SHIFT_TO_LAST1("CREATE") &&
+			 false) {} /* FALL THROUGH */
 /* CREATE */
 	/* complete with something you can create */
-	else if (TailMatches1("CREATE"))
+	else if (Matches1("CREATE"))
 		matches = completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
@@ -1343,7 +1440,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE */
 	else if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
+	/* ALTER TABLE after removing optional words IF EXISTS*/
+	else if (HeadMatches2("ALTER", "TABLE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("ALTER", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER something */
 	else if (Matches1("ALTER"))
@@ -1421,6 +1524,17 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
+	/* ALTER FOREIGN TABLE */
+	else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
+								   " UNION SELECT 'IF EXISTS'");
+
+	/* ALTER|DROP FOREIGN TABLE after removing optinal words IF EXISTS */
+	else if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+
 	/* ALTER FOREIGN TABLE <name> */
 	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
@@ -1432,10 +1546,21 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_FOREIGN_TABLE);
 	}
 
+	/* ALTER FOREIGN TABLE xxx RENAME */
+	else if (Matches5("ALTER", "FOREIGN", "TABLE", MatchAny, "RENAME"))
+		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'TO'");
+
 	/* ALTER INDEX */
 	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
+	/* ALTER INDEX after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "INDEX") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("ALTER", "INDEX"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+
 	/* ALTER INDEX <name> */
 	else if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
@@ -1464,8 +1589,15 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER MATERIALIZED VIEW */
 	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
+	/* ALTER MATERIALIZED VIEW with name after removing optional words */
+	else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("ALTER", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+
 	/* ALTER USER,ROLE <name> */
 	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
@@ -1516,8 +1648,23 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER DOMAIN <sth> DROP */
 	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
-	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	/* ALTER DOMAIN <sth> RENAME|VALIDATE CONSTRAINT */
+	else if (Matches5("ALTER", "DOMAIN", MatchAny, "RENAME|VALIDATE", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+	}
+	/* ALTER DOMAIN <sth> DROP CONSTRAINT */
+	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type
+							"UNION SELECT 'IF EXISTS'");
+	}
+	/* Try the same match after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
@@ -1532,8 +1679,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER DOMAIN <sth> SET */
 	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
-	/* ALTER SEQUENCE <name> */
-	else if (Matches3("ALTER", "SEQUENCE", MatchAny))
+	else if (Matches2("ALTER", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
+								   " UNION SELECT 'IF EXISTS'");
+	/* ALTER SEQUENCE with name after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "SEQUENCE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1556,8 +1708,14 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER SYSTEM SET|RESET <name> */
 	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
-	/* ALTER VIEW <name> */
-	else if (Matches3("ALTER", "VIEW", MatchAny))
+	/* ALTER VIEW */
+	else if (Matches2("ALTER", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   "UNION SELECT 'IF EXISTS'");
+	/* ALTER VIEW <name> with subcommands after removing optional worlds */
+	else if (HeadMatches2("ALTER", "VIEW") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
@@ -1565,11 +1723,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
-	/* ALTER POLICY <name> */
+	/* ALTER POLICY */
 	else if (Matches2("ALTER", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
-	/* ALTER POLICY <name> ON */
-	else if (Matches3("ALTER", "POLICY", MatchAny))
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies
+							"UNION SELECT 'IF EXISTS'");
+	/* ALTER POLICY <name> with ON after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "POLICY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
 	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
@@ -1693,8 +1854,10 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
-	/* ALTER TABLE xxx RENAME yyy */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
+	/* ALTER [FOREIGN] TABLE xxx RENAME yyy */
+	else if ((HeadMatches2("ALTER", "TABLE") ||
+			  HeadMatches3("ALTER", "FOREIGN", "TABLE")) &&
+			 TailMatches2("RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
@@ -1704,15 +1867,47 @@ psql_completion(const char *text, int start, int end)
 	/* If we have ALTER TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
+	/*  ALTER TABLE DROP COLUMN may take IF EXISTS */
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+	/* ALTER TABLE <name> with ADD/ALTER/DROP after removing optional words */
+	else if (HeadMatches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP") &&
+			 MidMatchAndRemove1(4, "COLUMN") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 Matches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP"))
+		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
-	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
+	 * If we have ALTER TABLE <sth> ALTER|RENAME|VALIDATE CONSTRAINT,
+	 * provide list of constraints
+	 */
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME|VALIDATE", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+	}
+	/*
+	 * If we have ALTER TABLE <sth> DROP CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table
+							"UNION SELECT 'IF EXISTS'");
+	}
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
@@ -1834,8 +2029,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 	else if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING",
-							"DROP MAPPING FOR",
-							"OWNER TO", "RENAME TO", "SET SCHEMA");
+							"DROP MAPPING",	"OWNER TO", "RENAME TO", "SET SCHEMA");
+	else if (Matches7("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny, "DROP", "MAPPING"))
+		COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR");
+	/* Remove optional words IF EXISTS */
+	else if (HeadMatches7("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny, "DROP", "MAPPING") &&
+			 MidMatchAndRemove2(7, "IF", "EXISTS") &&
+			 false) {} /* Nothing to do for now */
 
 	/* complete ALTER TYPE <foo> with actions */
 	else if (Matches3("ALTER", "TYPE", MatchAny))
@@ -1845,6 +2045,12 @@ psql_completion(const char *text, int start, int end)
 	/* complete ALTER TYPE <foo> ADD with actions */
 	else if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
+	else if (Matches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE"))
+		COMPLETE_WITH_LIST2("IF NOT EXISTS", "");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE") &&
+			 MidMatchAndRemove3(5, "IF", "NOT", "EXISTS") &&
+			 false) {} /* Nothing to do for now */
 	/* ALTER TYPE <foo> RENAME	*/
 	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "TO");
@@ -1856,10 +2062,15 @@ psql_completion(const char *text, int start, int end)
 	 * If we have ALTER TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list
 	 * of attributes
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
+	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
+	else if (Matches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE"))
+		COMPLETE_WITH_ATTR(prev3_wd, " UNION SELECT 'IF EXISTS'");
+	/* Remove optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
+			 Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* complete ALTER GROUP <foo> */
 	else if (Matches3("ALTER", "GROUP", MatchAny))
@@ -2002,6 +2213,12 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	else if (Matches2("CREATE", "EXTENSION"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions
+							" UNION SELECT 'IF NOT EXISTS'");
+	/* Try the same after removing optional words IF NOT EXISTS */
+	else if (HeadMatches2("CREATE", "EXTENSION") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
 	else if (Matches3("CREATE", "EXTENSION", MatchAny))
@@ -2017,6 +2234,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
+	/* CREATE FOREIGN TABLE */
+	else if (Matches3("CREATE", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
+			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
@@ -2025,23 +2250,38 @@ psql_completion(const char *text, int start, int end)
 	/* First off we complete CREATE UNIQUE with "INDEX" */
 	else if (TailMatches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
-	/* If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY",
+
+	/* Remove optional word UNIQUE from CREATE UNIQUE INDEX */
+	else if (HeadMatches3("CREATE", MatchAny, "INDEX") &&
+			 MidMatchAndRemove1(1, "UNIQUE") &&
+			 false) {} /* FALL THROUGH */
+	/* If we have CREATE INDEX, then add "ON", "CONCURRENTLY" or IF NOT EXISTS,
 	   and existing indexes */
-	else if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	else if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
-								   " UNION SELECT 'CONCURRENTLY'");
-	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	else if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
-			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-	/* Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing indexes */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+								   " UNION SELECT 'CONCURRENTLY'"
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and
+	 * existing indexes */
+	else if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF NOT EXISTS'"
 								   " UNION SELECT 'ON'");
-	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
-			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
+
+	/* Remove optional words "CONCURRENTLY",  "IF NOT EXISTS" */
+	else if (HeadMatches2("CREATE", "INDEX") &&
+			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
+	/* Complete CREATE INDEX [<name>] ON with a list of tables */
+	else if (Matches4("CREATE", "INDEX", MatchAny, "ON") ||
+			 Matches3("CREATE", "INDEX", "ON"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+
+	/* Complete CREATE INDEX <sth> with "ON" */
+	else if (Matches3("CREATE", "INDEX", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/*
@@ -2049,17 +2289,16 @@ psql_completion(const char *text, int start, int end)
 	 * should really be in parens)
 	 */
 	else if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-			 TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+			 TailMatches3("INDEX", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-			 TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
+	else if (Matches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
+			 Matches4("INDEX", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* same if you put in USING */
 	else if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	else if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
-			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
+	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 	else if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
@@ -2113,27 +2352,56 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-/* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	else if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
-			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
+/* Remove optional words TEMPORARY/TEMP */
+	else if (HeadMatches3("CREATE", MatchAny, "SEQUENCE") &&
+			 MidMatchAndRemove1(1, "TEMP|TEMPORARY") &&
+			 false) {} /* FALL THROUGH */
+/* CREATE SEQUENCE */
+	else if(Matches2("CREATE", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	else if(HeadMatches2("CREATE", "SEQUENCE") &&
+			MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			Matches3("CREATE", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	else if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
-		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
+	else if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
 	else if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
-/* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE SCHEMA <name> */
+	else if (Matches2("CREATE", "SCHEMA"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+							" UNION SELECT 'IF NOT EXISTS'");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches2("CREATE", "SCHEMA") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH*/
+
+/* CREATE TABLE  */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	else if (Matches2("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	else if (TailMatches2("CREATE", "UNLOGGED"))
+	else if (Matches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
+	/* CREATE TABLE with name after removing optional words */
+	else if (HeadMatches3("CREATE", MatchAny, "TABLE") &&
+			 MidMatchAndRemove1(1, "TEMP|TEMPORARY|UNLOGGED") &&
+			 false) {} /* FALL THROUGH*/
+	else if (Matches2("CREATE", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+								   " UNION SELECT 'IF NOT EXISTS'");
+
+	/* Remove optional words here */
+	else if (HeadMatches2("CREATE", "TABLE") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
@@ -2147,18 +2415,18 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
-/* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	else if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	else if (Matches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	else if (Matches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	else if (Matches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+	else if (Matches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
 	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
@@ -2215,9 +2483,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
-/* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	/* Complete CREATE VIEW <name> with AS */
-	else if (TailMatches3("CREATE", "VIEW", MatchAny))
+/* CREATE VIEW  */
+	else if (Matches2("CREATE", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* CREATE VIEW <name> with AS after removing optional words */
+	else if (HeadMatches2("CREATE", "VIEW") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
 	else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
@@ -2226,6 +2499,15 @@ psql_completion(const char *text, int start, int end)
 /* CREATE MATERIALIZED VIEW */
 	else if (Matches2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+	else if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Try the same after removing optional words IF NOT EXISTS. VIEW will be
+	 * completed afterwards */
+	else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
+			 Matches3("CREATE", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
 	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
@@ -2285,28 +2567,61 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
+	else if (Matches2("DROP", "AGGREGATE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates,
+								   " UNION SELECT 'IF EXISTS'");
+	else if (Matches2("DROP", "FUNCTION"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
 	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
+	else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 
 	/* DROP INDEX */
 	else if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF EXISTS'"
 								   " UNION SELECT 'CONCURRENTLY'");
 	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches3("DROP", "INDEX", MatchAny))
-		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	else if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */
+	else if (HeadMatches2("DROP", "INDEX") &&
+			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
 	else if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+	else if (Matches2("DROP", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Remove optional words IF EXISTS  */
+	else if (HeadMatches2("DROP", "VIEW") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
 	/* DROP OWNED BY */
@@ -2319,7 +2634,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (Matches3("DROP", "TRIGGER", MatchAny))
+	else if (Matches2("DROP", "TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_triggers
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "TRIGGER") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
@@ -2333,15 +2654,27 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	else if (Matches3("DROP", "EVENT", "TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers
+							" UNION SELECT 'IF EXISTS'");
+	/* Trye the same after removing optional words IF EXISTS */
+	else if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
-	/* DROP POLICY <name>  */
+	/* DROP POLICY */
 	else if (Matches2("DROP", "POLICY"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "POLICY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
-	/* DROP POLICY <name> ON */
+	/* DROP POLICY <name> */
 	else if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	/* DROP POLICY <name> ON <table> */
+	/* DROP POLICY <name> ON */
 	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
@@ -2349,7 +2682,13 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* DROP RULE */
-	else if (Matches3("DROP", "RULE", MatchAny))
+	else if (Matches2("DROP", "RULE"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_rules
+							"UNION SELECT 'IF EXISTS'");
+	/* DROP RULE <name>, after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "RULE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
@@ -2359,6 +2698,52 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
+	/* IF EXISTS processing for DROP everything else */
+	else if (Matches2("DROP",
+					  "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|"
+					  "GROUP|LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|"
+					  "SERVER|TABLE|TABLESPACE|TYPE|USER") ||
+			 Matches4("DROP", "TEXT", "SEARCH",
+					  "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE"))
+
+	{
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
+		char *addition = " UNION SELECT 'IF EXISTS'";
+
+		if (ent)
+		{
+			/* Completing USER needs special treat */
+			if (pg_strcasecmp(prev_wd, "USER") == 0)
+			{
+				COMPLETE_WITH_QUERY(Query_for_list_of_roles 
+									"UNION SELECT 'MAPPING' UNION SELECT 'IF EXISTS'");
+			}
+			else if (ent->query)
+			{
+				char *buf = pg_malloc(strlen(ent->query) +
+									  strlen(addition) + 1);
+				strcpy(buf, ent->query);
+				strcat(buf, addition);
+				COMPLETE_WITH_QUERY(buf);
+				free(buf);
+			}
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery,
+										   " UNION SELECT 'IF EXISTS'");
+		}
+	}
+	/* Remove optional IF EXISTS from DROP */
+	else if (HeadMatches2("DROP",
+						  "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|GROUP|"
+						  "LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|SERVER|"
+						  "TABLE|TABLESPACE|TYPE|USER") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+	else if (HeadMatches4("DROP", "TEXT", "SEARCH",
+						  "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
 /* EXECUTE */
 	else if (Matches1("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
@@ -2405,8 +2790,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /* FOREIGN TABLE */
-	else if (TailMatches2("FOREIGN", "TABLE") &&
-			 !TailMatches3("CREATE", MatchAny, MatchAny))
+	else if (TailMatches2("FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* FOREIGN SERVER */
@@ -2842,8 +3226,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
+	else if (Matches3("ALTER|CREATE", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
+	else if (Matches3("DROP", "USER", "MAPPING"))
+		COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR");
+	else if (HeadMatches3("DROP", "USER", "MAPPING") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
@@ -3071,19 +3460,14 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else
 	{
-		int			i;
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
 
-		for (i = 0; words_after_create[i].name; i++)
+		if (ent)
 		{
-			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
-			{
-				if (words_after_create[i].query)
-					COMPLETE_WITH_QUERY(words_after_create[i].query);
-				else if (words_after_create[i].squery)
-					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
-											   NULL);
-				break;
-			}
+			if (ent->query)
+				COMPLETE_WITH_QUERY(ent->query);
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, NULL);
 		}
 	}
 
@@ -3608,6 +3992,18 @@ complete_from_files(const char *text, int state)
 
 /* HELPER FUNCTIONS */
 
+/*
+ * Return the index (reverse to the index of previous_words) of the tailmost
+ * (topmost in the array) appearance of w.
+ */
+static int
+find_last_index_of(char *w, char **previous_words, int len)
+{
+	int i;
+
+	for (i = 0 ; i < len && !word_matches(w, previous_words[i]) ; i++);
+	return i < len ? (len - i - 1) : 0;
+}
 
 /*
  * Make a pg_strdup copy of s and convert the case according to
@@ -3854,6 +4250,24 @@ get_guctype(const char *varname)
 	return guctype;
 }
 
+/*
+ * Finds the entry in words_after_create[] that matches the word.
+ * NULL if not found.
+ */
+static const pgsql_thing_t *
+find_thing_entry(char *word)
+{
+	int			i;
+
+	for (i = 0; words_after_create[i].name; i++)
+	{
+		if (pg_strcasecmp(word, words_after_create[i].name) == 0)
+			return words_after_create + i;
+	}
+
+	return NULL;
+}
+
 #ifdef NOT_USED
 
 /*
-- 
1.8.3.1

0002-Make-added-keywords-for-completion-queries-follow-to.patchtext/x-patch; charset=us-asciiDownload
From 313ebf6d9e8ae41829decbc8aac877d3a5f46734 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Tue, 29 Mar 2016 19:01:13 +0900
Subject: [PATCH 2/5] Make added keywords for completion queries follow to
 input.

Currently some keyword shown along with database objects are always in
upper case. This patch changes the behavior so that the case of the
additional keywords follow the setting of COMP_KEYWORD_CASE.
Only COMPLETE_WITH_QUERY/COMPLETE_WITH_SCHEMA_QUERY are fixed.
---
 src/bin/psql/tab-complete.c | 239 +++++++++++++++++++++++++-------------------
 1 file changed, 136 insertions(+), 103 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 56776e1..c074544 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -312,6 +312,18 @@ do { \
 	COMPLETE_WITH_LIST_CS(list); \
 } while (0)
 
+#define ADDLIST1(p, s1) additional_kw_query(p, text, 1, s1)
+#define ADDLIST2(p, s1, s2) additional_kw_query(p, text, 2, s1, s2)
+#define ADDLIST3(p, s1, s2, s3) additional_kw_query(p, text, 3, s1, s2, s3)
+#define ADDLIST4(p, s1, s2, s3, s4) \
+	additional_kw_query(p, text, 4, s1, s2, s3, s4)
+#define ADDLIST13(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
+	additional_kw_query(p, text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13)
+#define ADDLIST15(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
+	additional_kw_query(p, text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13, s14, s15)
+
 /*
  * Assembly instructions for schema queries
  */
@@ -956,6 +968,7 @@ static char *complete_from_files(const char *text, int state);
 
 static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
+static char *additional_kw_query(char *prefix, const char *ref, int n, ...);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
 
@@ -1440,8 +1453,8 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE */
 	else if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   "UNION SELECT 'IF EXISTS'"
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+			ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
+
 	/* ALTER TABLE after removing optional words IF EXISTS*/
 	else if (HeadMatches2("ALTER", "TABLE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1527,7 +1540,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER FOREIGN TABLE */
 	else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 
 	/* ALTER|DROP FOREIGN TABLE after removing optinal words IF EXISTS */
 	else if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
@@ -1553,8 +1566,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER INDEX */
 	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   "UNION SELECT 'IF EXISTS'"
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+			   ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
 	/* ALTER INDEX after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "INDEX") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1589,8 +1601,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER MATERIALIZED VIEW */
 	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   "UNION SELECT 'IF EXISTS'"
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+			   ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
 
 	/* ALTER MATERIALIZED VIEW with name after removing optional words */
 	else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
@@ -1658,8 +1669,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_constraint_of_type, "IF EXISTS"));
 	}
 	/* Try the same match after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
@@ -1681,7 +1692,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	else if (Matches2("ALTER", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* ALTER SEQUENCE with name after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "SEQUENCE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1711,7 +1722,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER VIEW */
 	else if (Matches2("ALTER", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   "UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* ALTER VIEW <name> with subcommands after removing optional worlds */
 	else if (HeadMatches2("ALTER", "VIEW") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1725,8 +1736,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER POLICY */
 	else if (Matches2("ALTER", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_policies, "IF EXISTS"));
 	/* ALTER POLICY <name> with ON after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1901,8 +1912,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_constraint_of_table, "IF EXISTS"));
 	}
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
@@ -2096,7 +2107,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
 	else if (Matches1("CLUSTER"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
+								   ADDLIST1("", "VERBOSE"));
 	else if (Matches2("CLUSTER", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 	/* If we have CLUSTER <sth>, then add "USING" */
@@ -2158,7 +2170,7 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION ALL SELECT '('");
+								   ADDLIST1("", "("));
 	/* If we have COPY BINARY, complete with list of tables */
 	else if (Matches2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
@@ -2213,8 +2225,8 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	else if (Matches2("CREATE", "EXTENSION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions
-							" UNION SELECT 'IF NOT EXISTS'");
+		COMPLETE_WITH_QUERY(ADDLIST1(Query_for_list_of_available_extensions,
+									 "IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS */
 	else if (HeadMatches2("CREATE", "EXTENSION") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2237,7 +2249,7 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN TABLE */
 	else if (Matches3("CREATE", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
 			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
@@ -2259,15 +2271,12 @@ psql_completion(const char *text, int start, int end)
 	   and existing indexes */
 	else if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'ON'"
-								   " UNION SELECT 'CONCURRENTLY'"
-								   " UNION SELECT 'IF NOT EXISTS'");
+				   ADDLIST3("", "ON", "CONCURRENTLY", "IF NOT EXISTS"));
 	/* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and
 	 * existing indexes */
 	else if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'IF NOT EXISTS'"
-								   " UNION SELECT 'ON'");
+								   ADDLIST2("", "IF NOT EXISTS", "ON"));
 
 	/* Remove optional words "CONCURRENTLY",  "IF NOT EXISTS" */
 	else if (HeadMatches2("CREATE", "INDEX") &&
@@ -2359,7 +2368,7 @@ psql_completion(const char *text, int start, int end)
 /* CREATE SEQUENCE */
 	else if(Matches2("CREATE", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 	else if(HeadMatches2("CREATE", "SEQUENCE") &&
 			MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
 			Matches3("CREATE", "SEQUENCE", MatchAny))
@@ -2374,8 +2383,8 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE SCHEMA <name> */
 	else if (Matches2("CREATE", "SCHEMA"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas
-							" UNION SELECT 'IF NOT EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_schemas, "IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches2("CREATE", "SCHEMA") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2395,7 +2404,7 @@ psql_completion(const char *text, int start, int end)
 			 false) {} /* FALL THROUGH*/
 	else if (Matches2("CREATE", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 
 	/* Remove optional words here */
 	else if (HeadMatches2("CREATE", "TABLE") &&
@@ -2486,7 +2495,7 @@ psql_completion(const char *text, int start, int end)
 /* CREATE VIEW  */
 	else if (Matches2("CREATE", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 	/* CREATE VIEW <name> with AS after removing optional words */
 	else if (HeadMatches2("CREATE", "VIEW") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2501,7 +2510,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS. VIEW will be
 	 * completed afterwards */
 	else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
@@ -2569,10 +2578,10 @@ psql_completion(const char *text, int start, int end)
 	/* help completing some of the variants */
 	else if (Matches2("DROP", "AGGREGATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	else if (Matches2("DROP", "FUNCTION"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2583,8 +2592,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 	else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_fdws, "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&
 			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
@@ -2593,11 +2602,10 @@ psql_completion(const char *text, int start, int end)
 	/* DROP INDEX */
 	else if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'IF EXISTS'"
-								   " UNION SELECT 'CONCURRENTLY'");
+								   ADDLIST2("", "IF EXISTS","CONCURRENTLY"));
 	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */
 	else if (HeadMatches2("DROP", "INDEX") &&
 			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
@@ -2610,14 +2618,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches2("DROP", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Remove optional words IF EXISTS  */
 	else if (HeadMatches2("DROP", "VIEW") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 false) {} /* FALL THROUGH */
 	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
@@ -2635,8 +2643,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* DROP TRIGGER */
 	else if (Matches2("DROP", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_triggers
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_triggers, "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "TRIGGER") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2654,8 +2662,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	else if (Matches3("DROP", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_event_triggers, "IF EXISTS"));
 	/* Trye the same after removing optional words IF EXISTS */
 	else if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
@@ -2664,8 +2672,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* DROP POLICY */
 	else if (Matches2("DROP", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_policies, "IF EXISTS"));
 	/* Try the same after after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2683,8 +2691,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* DROP RULE */
 	else if (Matches2("DROP", "RULE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_rules
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_rules, "IF EXISTS"));
 	/* DROP RULE <name>, after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "RULE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2708,15 +2716,15 @@ psql_completion(const char *text, int start, int end)
 
 	{
 		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
-		char *addition = " UNION SELECT 'IF EXISTS'";
+		char *addition = ADDLIST1("", "IF EXISTS");
 
 		if (ent)
 		{
 			/* Completing USER needs special treat */
 			if (pg_strcasecmp(prev_wd, "USER") == 0)
 			{
-				COMPLETE_WITH_QUERY(Query_for_list_of_roles 
-									"UNION SELECT 'MAPPING' UNION SELECT 'IF EXISTS'");
+				COMPLETE_WITH_QUERY(
+					ADDLIST2(Query_for_list_of_roles, "MAPPING", "IF EXISTS"));
 			}
 			else if (ent->query)
 			{
@@ -2729,7 +2737,7 @@ psql_completion(const char *text, int start, int end)
 			}
 			else if (ent->squery)
 				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery,
-										   " UNION SELECT 'IF EXISTS'");
+										   ADDLIST1("", "IF EXISTS"));
 		}
 	}
 	/* Remove optional IF EXISTS from DROP */
@@ -2800,20 +2808,11 @@ psql_completion(const char *text, int start, int end)
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
 	else if (TailMatches1("GRANT|REVOKE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'SELECT'"
-							" UNION SELECT 'INSERT'"
-							" UNION SELECT 'UPDATE'"
-							" UNION SELECT 'DELETE'"
-							" UNION SELECT 'TRUNCATE'"
-							" UNION SELECT 'REFERENCES'"
-							" UNION SELECT 'TRIGGER'"
-							" UNION SELECT 'CREATE'"
-							" UNION SELECT 'CONNECT'"
-							" UNION SELECT 'TEMPORARY'"
-							" UNION SELECT 'EXECUTE'"
-							" UNION SELECT 'USAGE'"
-							" UNION SELECT 'ALL'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST13(Query_for_list_of_roles,
+					  "SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
+					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
+					  "EXECUTE", "USAGE", "ALL"));
 
 	/*
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
@@ -2842,21 +2841,22 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
-								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
-								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
-								   " UNION SELECT 'ALL TABLES IN SCHEMA'"
-								   " UNION SELECT 'DATABASE'"
-								   " UNION SELECT 'DOMAIN'"
-								   " UNION SELECT 'FOREIGN DATA WRAPPER'"
-								   " UNION SELECT 'FOREIGN SERVER'"
-								   " UNION SELECT 'FUNCTION'"
-								   " UNION SELECT 'LANGUAGE'"
-								   " UNION SELECT 'LARGE OBJECT'"
-								   " UNION SELECT 'SCHEMA'"
-								   " UNION SELECT 'SEQUENCE'"
-								   " UNION SELECT 'TABLE'"
-								   " UNION SELECT 'TABLESPACE'"
-								   " UNION SELECT 'TYPE'");
+			   ADDLIST15("",
+						 "ALL FUNCTIONS IN SCHEMA",
+						 "ALL SEQUENCES IN SCHEMA",
+						 "ALL TABLES IN SCHEMA",
+						 "DATABASE",
+						 "DOMAIN",
+						 "FOREIGN DATA WRAPPER",
+						 "FOREIGN SERVER",
+						 "FUNCTION",
+						 "LANGUAGE",
+						 "LARGE OBJECT",
+						 "SCHEMA",
+						 "SEQUENCE",
+						 "TABLE",
+						 "TABLESPACE",
+						 "TYPE"));
 
 	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
@@ -2982,7 +2982,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete LOCK [TABLE] with a list of tables */
 	else if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION SELECT 'TABLE'");
+								   ADDLIST1("", "TABLE"));
 	else if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
@@ -3048,7 +3048,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'CONCURRENTLY'");
+								   ADDLIST1("", "CONCURRENTLY"));
 	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
@@ -3124,7 +3124,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
 	else if (Matches2("SET", "CONSTRAINTS"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema,
+								   ADDLIST1("", "ALL"));
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
 	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
@@ -3136,7 +3137,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
 	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_roles, "DEFAULT"));
 	/* Complete RESET SESSION with AUTHORIZATION */
 	else if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
@@ -3162,10 +3164,11 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST(my_list);
 		}
 		else if (TailMatches2("search_path", "TO|="))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
-								" AND nspname not like 'pg\\_toast%%' "
-								" AND nspname not like 'pg\\_temp%%' "
-								" UNION SELECT 'DEFAULT' ");
+			COMPLETE_WITH_QUERY(
+				ADDLIST1(Query_for_list_of_schemas
+						 " AND nspname not like 'pg\\_toast%%' "
+						 " AND nspname not like 'pg\\_temp%%' ",
+						 "DEFAULT"));
 		else
 		{
 			/* generic, type based, GUC support */
@@ -3234,10 +3237,9 @@ psql_completion(const char *text, int start, int end)
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 false) {} /* FALL THROUGH */
 	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'CURRENT_USER'"
-							" UNION SELECT 'PUBLIC'"
-							" UNION SELECT 'USER'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST3(Query_for_list_of_roles, 
+					 "CURRENT_USER", "PUBLIC", "USER"));
 	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
 	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
@@ -3251,26 +3253,22 @@ psql_completion(const char *text, int start, int end)
  */
 	else if (Matches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'FULL'"
-								   " UNION SELECT 'FREEZE'"
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST4("", "FULL", "FREEZE", "ANALYZE", "VERBOSE"));
 	else if (Matches2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST2("", "ANALYZE", "VERBOSE"));
 	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST1("", "VERBOSE"));
 	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+		   ADDLIST1("", "ANALYZE"));
 	else if (Matches2("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+		   ADDLIST1("", "ANALYZE"));
 	else if (Matches2("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST1("", "VERBOSE"));
 	else if (HeadMatches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
@@ -4035,6 +4033,41 @@ pg_strdup_keyword_case(const char *s, const char *ref)
 	return ret;
 }
 
+/* Construct codelet to append given keywords  */
+static char *
+additional_kw_query(char *prefix, const char *ref, int n, ...)
+{
+	va_list ap;
+	static PQExpBuffer qbuf = NULL;
+	int i;
+
+	if (qbuf == NULL)
+		qbuf = createPQExpBuffer();
+	else
+		resetPQExpBuffer(qbuf);
+
+	appendPQExpBufferStr(qbuf, prefix);
+
+	/* Construct an additional queriy to append keywords */
+	appendPQExpBufferStr(qbuf, " UNION SELECT * FROM (VALUES ");
+
+	va_start(ap, n);
+	for (i = 0 ; i < n ; i++)
+	{
+		char *item = pg_strdup_keyword_case(va_arg(ap, char *), ref);
+		if (i > 0) appendPQExpBufferChar(qbuf, ',');
+		appendPQExpBufferStr(qbuf, "('");
+		appendPQExpBufferStr(qbuf, item);
+		appendPQExpBufferStr(qbuf, "')");
+		pg_free(item);
+	}
+	va_end(ap);
+
+	appendPQExpBufferStr(qbuf, ") as x");
+
+	return qbuf->data;
+}
+
 
 /*
  * escape_string - Escape argument for use as string literal.
-- 
1.8.3.1

0003-Make-COMPLETE_WITH_ATTR-to-accept-additional-keyword.patchtext/x-patch; charset=us-asciiDownload
From 831b1e3902710036e10d50a98e9af23f06247e27 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 1 Apr 2016 17:01:42 +0900
Subject: [PATCH 3/5] Make COMPLETE_WITH_ATTR to accept additional keyword
 query.

So far the previous commit, COMPLETE_WITH_ATTR cannot accept the new
style of additional keyword list. This patch does the following
things.

1. Change completion_charp from const char * to PQExpBuffer.

2. Chnage COMPLETE_WITH_QUERY and COMPLETE_WITH_ATTR to accept
   an expression instead of string literal.

3. Replace all additional keyword lists in psql_copmletion with
   ADDLISTn() expression.

This leaves keywords contained in Query_for_list_of_grant_roles and
Query_for_enum, but it is the another problem.
---
 src/bin/psql/tab-complete.c | 474 ++++++++++++++++++++++----------------------
 1 file changed, 240 insertions(+), 234 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index c074544..329b834 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -125,7 +125,7 @@ static int	completion_max_records;
  * Communication variables set by COMPLETE_WITH_FOO macros and then used by
  * the completion callback functions.  Ugly but there is no better way.
  */
-static const char *completion_charp;	/* to pass a string */
+static PQExpBuffer completion_charp = NULL;		/* to pass a string */
 static const char *const * completion_charpp;	/* to pass a list of strings */
 static const char *completion_info_charp;		/* to pass a second string */
 static const char *completion_info_charp2;		/* to pass a third string */
@@ -143,16 +143,26 @@ static bool completion_case_sensitive;	/* completion is case sensitive */
  * 5) The list of attributes of the given table (possibly schema-qualified).
  * 6/ The list of arguments to the given function (possibly schema-qualified).
  */
-#define COMPLETE_WITH_QUERY(query) \
+#define APPEND_COMP_CHARP(charp) \
+	appendPQExpBufferStr(completion_charp, charp);
+
+#define SET_COMP_CHARP(charp) \
+	resetPQExpBuffer(completion_charp);	\
+	APPEND_COMP_CHARP(charp);
+
+#define COMPLETION_CHARP (completion_charp->data)
+
+#define COMPLETE_WITH_QUERY(query, addon) \
 do { \
-	completion_charp = query; \
+	SET_COMP_CHARP(query);	\
+	APPEND_COMP_CHARP(addon); \
 	matches = completion_matches(text, complete_from_query); \
 } while (0)
 
 #define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
 do { \
 	completion_squery = &(query); \
-	completion_charp = addon; \
+	SET_COMP_CHARP(addon); \
 	matches = completion_matches(text, complete_from_schema_query); \
 } while (0)
 
@@ -172,7 +182,7 @@ do { \
 
 #define COMPLETE_WITH_CONST(string) \
 do { \
-	completion_charp = string; \
+	SET_COMP_CHARP(string);	\
 	completion_case_sensitive = false; \
 	matches = completion_matches(text, complete_from_const); \
 } while (0)
@@ -190,12 +200,14 @@ do { \
 								false, false, pset.encoding); \
 	if (_completion_table == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_attributes  addon; \
+		SET_COMP_CHARP(Query_for_list_of_attributes); \
+		APPEND_COMP_CHARP(addon);					  \
 		completion_info_charp = relation; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_attributes_with_schema  addon; \
+		SET_COMP_CHARP(Query_for_list_of_attributes_with_schema); \
+		APPEND_COMP_CHARP(addon); \
 		completion_info_charp = _completion_table; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -215,12 +227,12 @@ do { \
 								   false, false, pset.encoding); \
 	if (_completion_function == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_arguments; \
+		SET_COMP_CHARP(Query_for_list_of_arguments); \
 		completion_info_charp = function; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_arguments_with_schema; \
+		SET_COMP_CHARP(Query_for_list_of_arguments_with_schema); \
 		completion_info_charp = _completion_function; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -312,16 +324,16 @@ do { \
 	COMPLETE_WITH_LIST_CS(list); \
 } while (0)
 
-#define ADDLIST1(p, s1) additional_kw_query(p, text, 1, s1)
-#define ADDLIST2(p, s1, s2) additional_kw_query(p, text, 2, s1, s2)
-#define ADDLIST3(p, s1, s2, s3) additional_kw_query(p, text, 3, s1, s2, s3)
-#define ADDLIST4(p, s1, s2, s3, s4) \
-	additional_kw_query(p, text, 4, s1, s2, s3, s4)
-#define ADDLIST13(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
-	additional_kw_query(p, text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+#define ADDLIST1(s1) additional_kw_query(text, 1, s1)
+#define ADDLIST2(s1, s2) additional_kw_query(text, 2, s1, s2)
+#define ADDLIST3(s1, s2, s3) additional_kw_query(text, 3, s1, s2, s3)
+#define ADDLIST4(s1, s2, s3, s4) \
+	additional_kw_query(text, 4, s1, s2, s3, s4)
+#define ADDLIST13(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
+	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
 						s8, s9, s10, s11, s12, s13)
-#define ADDLIST15(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
-	additional_kw_query(p, text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+#define ADDLIST15(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
+	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
 						s8, s9, s10, s11, s12, s13, s14, s15)
 
 /*
@@ -968,7 +980,7 @@ static char *complete_from_files(const char *text, int state);
 
 static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
-static char *additional_kw_query(char *prefix, const char *ref, int n, ...);
+static char *additional_kw_query( const char *ref, int n, ...);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
 
@@ -1398,7 +1410,8 @@ psql_completion(const char *text, int start, int end)
 #endif
 
 	/* Clear a few things. */
-	completion_charp = NULL;
+	if (completion_charp == NULL)
+		completion_charp = createPQExpBuffer();
 	completion_charpp = NULL;
 	completion_info_charp = NULL;
 	completion_info_charp2 = NULL;
@@ -1453,13 +1466,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE */
 	else if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-			ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
+			ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
 
 	/* ALTER TABLE after removing optional words IF EXISTS*/
 	else if (HeadMatches2("ALTER", "TABLE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 Matches2("ALTER", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* ALTER something */
 	else if (Matches1("ALTER"))
@@ -1479,7 +1492,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("SET TABLESPACE", "OWNED BY");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY */
 	else if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY xxx */
 	else if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("SET TABLESPACE");
@@ -1515,7 +1528,7 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER EVENT TRIGGER */
 	else if (Matches3("ALTER", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* ALTER EVENT TRIGGER <name> */
 	else if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
@@ -1540,13 +1553,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER FOREIGN TABLE */
 	else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 
 	/* ALTER|DROP FOREIGN TABLE after removing optinal words IF EXISTS */
 	else if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 
 	/* ALTER FOREIGN TABLE <name> */
 	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
@@ -1561,17 +1574,17 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER FOREIGN TABLE xxx RENAME */
 	else if (Matches5("ALTER", "FOREIGN", "TABLE", MatchAny, "RENAME"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'TO'");
+		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST2("COLUMN", "TO"));
 
 	/* ALTER INDEX */
 	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-			   ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
+			   ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
 	/* ALTER INDEX after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "INDEX") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 Matches2("ALTER", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 
 	/* ALTER INDEX <name> */
 	else if (Matches3("ALTER", "INDEX", MatchAny))
@@ -1601,13 +1614,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER MATERIALIZED VIEW */
 	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-			   ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
+			   ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
 
 	/* ALTER MATERIALIZED VIEW with name after removing optional words */
 	else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 Matches3("ALTER", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 
 	/* ALTER USER,ROLE <name> */
 	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
@@ -1663,14 +1676,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "DOMAIN", MatchAny, "RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, "");
 	}
 	/* ALTER DOMAIN <sth> DROP CONSTRAINT */
 	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_constraint_of_type, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type,
+							ADDLIST1("IF EXISTS"));
 	}
 	/* Try the same match after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
@@ -1678,7 +1691,7 @@ psql_completion(const char *text, int start, int end)
 			 Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, "");
 	}
 	/* ALTER DOMAIN <sth> RENAME */
 	else if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
@@ -1692,7 +1705,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	else if (Matches2("ALTER", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* ALTER SEQUENCE with name after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "SEQUENCE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1718,11 +1731,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
 	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars, "");
 	/* ALTER VIEW */
 	else if (Matches2("ALTER", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* ALTER VIEW <name> with subcommands after removing optional worlds */
 	else if (HeadMatches2("ALTER", "VIEW") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1736,8 +1749,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER POLICY */
 	else if (Matches2("ALTER", "POLICY"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_policies, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies,
+							ADDLIST1("IF EXISTS"));
 	/* ALTER POLICY <name> with ON after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1747,14 +1760,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, "");
 	}
 	/* ALTER POLICY <name> ON <table> - show options */
 	else if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING (", "WITH CHECK (");
 	/* ALTER POLICY <name> ON <table> TO <role> */
 	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 	/* ALTER POLICY <name> ON <table> USING ( */
 	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
@@ -1770,7 +1783,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("ALTER", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, "");
 	}
 
 	/* ALTER RULE <name> ON <name> */
@@ -1784,14 +1797,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, "");
 	}
 
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
 	else if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* ALTER TRIGGER <name> ON <name> */
 	else if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
@@ -1818,22 +1831,22 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
 	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
 	{
 		completion_info_charp = prev4_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
 	{
 		completion_info_charp = prev4_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 	/* ALTER TABLE xxx INHERIT */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
@@ -1847,21 +1860,21 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 
 	/* ALTER TABLE xxx ALTER */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
+		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST2("COLUMN", "CONSTRAINT"));
 
 	/* ALTER TABLE xxx RENAME */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
+		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST3("COLUMN", "CONSTRAINT", "TO"));
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
@@ -1880,7 +1893,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/*  ALTER TABLE DROP COLUMN may take IF EXISTS */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
 	/* ALTER TABLE <name> with ADD/ALTER/DROP after removing optional words */
 	else if (HeadMatches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP") &&
 			 MidMatchAndRemove1(4, "COLUMN") &&
@@ -1889,7 +1902,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN") &&
 			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
@@ -1903,7 +1916,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, "");
 	}
 	/*
 	 * If we have ALTER TABLE <sth> DROP CONSTRAINT,
@@ -1912,8 +1925,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_constraint_of_table, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table,
+							ADDLIST1("IF EXISTS"));
 	}
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
@@ -1921,7 +1934,7 @@ psql_completion(const char *text, int start, int end)
 			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, "");
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
 	else if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
@@ -1948,7 +1961,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
 	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
@@ -1960,7 +1973,7 @@ psql_completion(const char *text, int start, int end)
 	 * tablespaces
 	 */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
 	/* If we have ALTER TABLE <sth> SET WITH provide OIDS */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
 		COMPLETE_WITH_CONST("OIDS");
@@ -2011,7 +2024,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
 	else if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
@@ -2076,7 +2089,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 	else if (Matches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE"))
-		COMPLETE_WITH_ATTR(prev3_wd, " UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
 	/* Remove optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE") &&
 			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
@@ -2091,7 +2104,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
 	else if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* BEGIN, END, ABORT */
 	else if (Matches1("BEGIN|END|ABORT"))
@@ -2108,9 +2121,9 @@ psql_completion(const char *text, int start, int end)
 /* CLUSTER */
 	else if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   ADDLIST1("", "VERBOSE"));
+								   ADDLIST1("VERBOSE"));
 	else if (Matches2("CLUSTER", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 	/* If we have CLUSTER <sth>, then add "USING" */
 	else if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
 		COMPLETE_WITH_CONST("USING");
@@ -2122,7 +2135,7 @@ psql_completion(const char *text, int start, int end)
 			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
 
 /* COMMENT */
@@ -2145,18 +2158,18 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 	else if (Matches3("COMMENT", "ON", "CONSTRAINT"))
-		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
+		COMPLETE_WITH_QUERY(Query_for_all_table_constraints, "");
 	else if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	else if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint, "");
 	}
 	else if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	else if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 	else if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
 		Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
 			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
@@ -2170,10 +2183,10 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   ADDLIST1("", "("));
+								   ADDLIST1("("));
 	/* If we have COPY BINARY, complete with list of tables */
 	else if (Matches2("COPY", "BINARY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* If we have COPY (, complete it with legal commands */
 	else if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
@@ -2185,7 +2198,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
 			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
 	{
-		completion_charp = "";
+		SET_COMP_CHARP("");
 		matches = completion_matches(text, complete_from_files);
 	}
 
@@ -2220,18 +2233,18 @@ psql_completion(const char *text, int start, int end)
 							"LC_COLLATE", "LC_CTYPE");
 
 	else if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
+		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases, "");
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	else if (Matches2("CREATE", "EXTENSION"))
-		COMPLETE_WITH_QUERY(ADDLIST1(Query_for_list_of_available_extensions,
-									 "IF NOT EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions,
+							ADDLIST1("IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS */
 	else if (HeadMatches2("CREATE", "EXTENSION") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
 			 Matches2("CREATE", "EXTENSION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions, "");
 	/* CREATE EXTENSION <name> */
 	else if (Matches3("CREATE", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
@@ -2239,7 +2252,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions, "");
 	}
 
 	/* CREATE FOREIGN */
@@ -2249,7 +2262,7 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN TABLE */
 	else if (Matches3("CREATE", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
 			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
@@ -2271,12 +2284,12 @@ psql_completion(const char *text, int start, int end)
 	   and existing indexes */
 	else if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-				   ADDLIST3("", "ON", "CONCURRENTLY", "IF NOT EXISTS"));
+				   ADDLIST3("ON", "CONCURRENTLY", "IF NOT EXISTS"));
 	/* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and
 	 * existing indexes */
 	else if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   ADDLIST2("", "IF NOT EXISTS", "ON"));
+								   ADDLIST2("IF NOT EXISTS", "ON"));
 
 	/* Remove optional words "CONCURRENTLY",  "IF NOT EXISTS" */
 	else if (HeadMatches2("CREATE", "INDEX") &&
@@ -2287,7 +2300,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete CREATE INDEX [<name>] ON with a list of tables */
 	else if (Matches4("CREATE", "INDEX", MatchAny, "ON") ||
 			 Matches3("CREATE", "INDEX", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 
 	/* Complete CREATE INDEX <sth> with "ON" */
 	else if (Matches3("CREATE", "INDEX", MatchAny))
@@ -2309,7 +2322,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete USING with an index method */
 	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, "");
 	else if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
 			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
 			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
@@ -2321,7 +2334,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
 	else if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
 	else if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING (", "WITH CHECK (");
@@ -2339,7 +2352,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("TO", "USING (", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
 	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
 	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
@@ -2359,7 +2372,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
 	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 /* Remove optional words TEMPORARY/TEMP */
 	else if (HeadMatches3("CREATE", MatchAny, "SEQUENCE") &&
@@ -2368,7 +2381,7 @@ psql_completion(const char *text, int start, int end)
 /* CREATE SEQUENCE */
 	else if(Matches2("CREATE", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 	else if(HeadMatches2("CREATE", "SEQUENCE") &&
 			MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
 			Matches3("CREATE", "SEQUENCE", MatchAny))
@@ -2383,8 +2396,8 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE SCHEMA <name> */
 	else if (Matches2("CREATE", "SCHEMA"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_schemas, "IF NOT EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas,
+							ADDLIST1("IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches2("CREATE", "SCHEMA") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2404,7 +2417,7 @@ psql_completion(const char *text, int start, int end)
 			 false) {} /* FALL THROUGH*/
 	else if (Matches2("CREATE", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 
 	/* Remove optional words here */
 	else if (HeadMatches2("CREATE", "TABLE") &&
@@ -2444,10 +2457,10 @@ psql_completion(const char *text, int start, int end)
 	 * tables
 	 */
 	else if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
 	else if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
 	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
@@ -2495,7 +2508,7 @@ psql_completion(const char *text, int start, int end)
 /* CREATE VIEW  */
 	else if (Matches2("CREATE", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 	/* CREATE VIEW <name> with AS after removing optional words */
 	else if (HeadMatches2("CREATE", "VIEW") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2510,13 +2523,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS. VIEW will be
 	 * completed afterwards */
 	else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
 			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
 			 Matches3("CREATE", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
 	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
@@ -2547,7 +2560,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
 	else if (TailMatches2("DELETE", "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete DELETE FROM <table> */
 	else if (TailMatches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
@@ -2578,10 +2591,10 @@ psql_completion(const char *text, int start, int end)
 	/* help completing some of the variants */
 	else if (Matches2("DROP", "AGGREGATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	else if (Matches2("DROP", "FUNCTION"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2592,8 +2605,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 	else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_fdws, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws,
+							ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&
 			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
@@ -2602,10 +2615,10 @@ psql_completion(const char *text, int start, int end)
 	/* DROP INDEX */
 	else if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   ADDLIST2("", "IF EXISTS","CONCURRENTLY"));
+								   ADDLIST2("IF EXISTS","CONCURRENTLY"));
 	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */
 	else if (HeadMatches2("DROP", "INDEX") &&
 			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
@@ -2618,33 +2631,33 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches2("DROP", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* Remove optional words IF EXISTS  */
 	else if (HeadMatches2("DROP", "VIEW") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 false) {} /* FALL THROUGH */
 	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 Matches3("DROP", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 
 	/* DROP OWNED BY */
 	else if (Matches2("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
 	else if (Matches3("DROP", "OWNED", "BY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 	else if (Matches3("DROP", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
 	else if (Matches2("DROP", "TRIGGER"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_triggers, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_triggers,
+							ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "TRIGGER") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2653,7 +2666,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, "");
 	}
 	else if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
@@ -2662,23 +2675,23 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	else if (Matches3("DROP", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_event_triggers, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers,
+							ADDLIST1("IF EXISTS"));
 	/* Trye the same after removing optional words IF EXISTS */
 	else if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 Matches3("DROP", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* DROP POLICY */
 	else if (Matches2("DROP", "POLICY"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_policies, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies,
+							ADDLIST1("IF EXISTS"));
 	/* Try the same after after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 Matches2("DROP", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies, "");
 	/* DROP POLICY <name> */
 	else if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
@@ -2686,13 +2699,13 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, "");
 	}
 
 	/* DROP RULE */
 	else if (Matches2("DROP", "RULE"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_rules, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_rules,
+							ADDLIST1("IF EXISTS"));
 	/* DROP RULE <name>, after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "RULE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2701,7 +2714,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, "");
 	}
 	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
@@ -2716,15 +2729,15 @@ psql_completion(const char *text, int start, int end)
 
 	{
 		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
-		char *addition = ADDLIST1("", "IF EXISTS");
+		char *addition = ADDLIST1("IF EXISTS");
 
 		if (ent)
 		{
 			/* Completing USER needs special treat */
 			if (pg_strcasecmp(prev_wd, "USER") == 0)
 			{
-				COMPLETE_WITH_QUERY(
-					ADDLIST2(Query_for_list_of_roles, "MAPPING", "IF EXISTS"));
+				COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+									ADDLIST2("MAPPING", "IF EXISTS"));
 			}
 			else if (ent->query)
 			{
@@ -2732,12 +2745,12 @@ psql_completion(const char *text, int start, int end)
 									  strlen(addition) + 1);
 				strcpy(buf, ent->query);
 				strcat(buf, addition);
-				COMPLETE_WITH_QUERY(buf);
+				COMPLETE_WITH_QUERY(buf, "");
 				free(buf);
 			}
 			else if (ent->squery)
 				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery,
-										   ADDLIST1("", "IF EXISTS"));
+										   ADDLIST1("IF EXISTS"));
 		}
 	}
 	/* Remove optional IF EXISTS from DROP */
@@ -2754,7 +2767,7 @@ psql_completion(const char *text, int start, int end)
 
 /* EXECUTE */
 	else if (Matches1("EXECUTE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
+		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements, "");
 
 /* EXPLAIN */
 
@@ -2791,7 +2804,7 @@ psql_completion(const char *text, int start, int end)
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
 	else if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
 			 !TailMatches4("CREATE", MatchAny, MatchAny, MatchAny))
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, "");
 	/* applies in CREATE SERVER */
 	else if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
 			 HeadMatches2("CREATE", "SERVER"))
@@ -2799,18 +2812,17 @@ psql_completion(const char *text, int start, int end)
 
 /* FOREIGN TABLE */
 	else if (TailMatches2("FOREIGN", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 
 /* FOREIGN SERVER */
 	else if (TailMatches2("FOREIGN", "SERVER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_servers, "");
 
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
 	else if (TailMatches1("GRANT|REVOKE"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST13(Query_for_list_of_roles,
-					  "SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+			ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
 					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
 					  "EXECUTE", "USAGE", "ALL"));
 
@@ -2841,8 +2853,7 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
-			   ADDLIST15("",
-						 "ALL FUNCTIONS IN SCHEMA",
+			   ADDLIST15("ALL FUNCTIONS IN SCHEMA",
 						 "ALL SEQUENCES IN SCHEMA",
 						 "ALL TABLES IN SCHEMA",
 						 "DATABASE",
@@ -2874,23 +2885,23 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
 		if (TailMatches1("DATABASE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+			COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 		else if (TailMatches1("DOMAIN"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, "");
 		else if (TailMatches1("FUNCTION"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 		else if (TailMatches1("LANGUAGE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
+			COMPLETE_WITH_QUERY(Query_for_list_of_languages, "");
 		else if (TailMatches1("SCHEMA"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+			COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
 		else if (TailMatches1("SEQUENCE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
 		else if (TailMatches1("TABLE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 		else if (TailMatches1("TABLESPACE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
 		else if (TailMatches1("TYPE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, "");
 		else if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
@@ -2903,7 +2914,7 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
 			 (HeadMatches1("REVOKE") && TailMatches1("FROM")))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 
 	/* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */
 	else if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
@@ -2954,7 +2965,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
 	else if (TailMatches2("INSERT", "INTO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete "INSERT INTO <table> (" with attribute names */
 	else if (TailMatches4("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
@@ -2982,7 +2993,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete LOCK [TABLE] with a list of tables */
 	else if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   ADDLIST1("", "TABLE"));
+								   ADDLIST1("TABLE"));
 	else if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
@@ -3004,7 +3015,7 @@ psql_completion(const char *text, int start, int end)
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
 	else if (TailMatches1("NOTIFY"))
-		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
+		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'", "");
 
 /* OPTIONS */
 	else if (TailMatches1("OPTIONS"))
@@ -3012,7 +3023,7 @@ psql_completion(const char *text, int start, int end)
 
 /* OWNER TO  - complete with available roles */
 	else if (TailMatches2("OWNER", "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* ORDER BY */
 	else if (TailMatches3("FROM", MatchAny, "ORDER"))
@@ -3035,11 +3046,11 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
 	else if (Matches3("REASSIGN", "OWNED", "BY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	else if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 	else if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* REFRESH MATERIALIZED VIEW */
 	else if (Matches1("REFRESH"))
@@ -3048,9 +3059,9 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   ADDLIST1("", "CONCURRENTLY"));
+								   ADDLIST1("CONCURRENTLY"));
 	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
 	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
@@ -3068,13 +3079,13 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
 	else if (Matches2("REINDEX", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 	else if (Matches2("REINDEX", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 	else if (Matches2("REINDEX", "SCHEMA"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
 	else if (Matches2("REINDEX", "SYSTEM|DATABASE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+		COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 
 /* SECURITY LABEL */
 	else if (Matches1("SECURITY"))
@@ -3103,9 +3114,9 @@ psql_completion(const char *text, int start, int end)
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
 	else if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars, "");
 	else if (Matches1("SHOW"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars, "");
 	/* Complete "SET TRANSACTION" */
 	else if (Matches2("SET|BEGIN|START", "TRANSACTION") ||
 			 Matches2("BEGIN", "WORK") ||
@@ -3125,20 +3136,20 @@ psql_completion(const char *text, int start, int end)
 	/* SET CONSTRAINTS */
 	else if (Matches2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema,
-								   ADDLIST1("", "ALL"));
+								   ADDLIST1("ALL"));
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
 	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
 	else if (Matches2("SET", "ROLE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
 	else if (Matches2("SET", "SESSION"))
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
 	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_roles, "DEFAULT"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+							ADDLIST1("DEFAULT"));
 	/* Complete RESET SESSION with AUTHORIZATION */
 	else if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
@@ -3164,11 +3175,10 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST(my_list);
 		}
 		else if (TailMatches2("search_path", "TO|="))
-			COMPLETE_WITH_QUERY(
-				ADDLIST1(Query_for_list_of_schemas
-						 " AND nspname not like 'pg\\_toast%%' "
-						 " AND nspname not like 'pg\\_temp%%' ",
-						 "DEFAULT"));
+			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+								" AND nspname not like 'pg\\_toast%%' "
+								" AND nspname not like 'pg\\_temp%%' ",
+								ADDLIST1("DEFAULT"));
 		else
 		{
 			/* generic, type based, GUC support */
@@ -3179,7 +3189,7 @@ psql_completion(const char *text, int start, int end)
 				char		querybuf[1024];
 
 				snprintf(querybuf, sizeof(querybuf), Query_for_enum, prev2_wd);
-				COMPLETE_WITH_QUERY(querybuf);
+				COMPLETE_WITH_QUERY(querybuf, "");
 			}
 			else if (guctype && strcmp(guctype, "bool") == 0)
 				COMPLETE_WITH_LIST9("on", "off", "true", "false", "yes", "no",
@@ -3198,26 +3208,26 @@ psql_completion(const char *text, int start, int end)
 
 /* TABLE, but not TABLE embedded in other commands */
 	else if (Matches1("TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, "");
 
 /* TABLESAMPLE */
 	else if (TailMatches1("TABLESAMPLE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods, "");
 	else if (TailMatches2("TABLESAMPLE", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
 	else if (Matches1("TRUNCATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 /* UNLISTEN */
 	else if (Matches1("UNLISTEN"))
-		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
+		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'", "");
 
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
 	else if (TailMatches1("UPDATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete UPDATE <table> with "SET" */
 	else if (TailMatches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
@@ -3237,11 +3247,10 @@ psql_completion(const char *text, int start, int end)
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 false) {} /* FALL THROUGH */
 	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST3(Query_for_list_of_roles, 
-					 "CURRENT_USER", "PUBLIC", "USER"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+							ADDLIST3("CURRENT_USER", "PUBLIC", "USER"));
 	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, "");
 	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("SERVER");
 	else if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
@@ -3253,24 +3262,24 @@ psql_completion(const char *text, int start, int end)
  */
 	else if (Matches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST4("", "FULL", "FREEZE", "ANALYZE", "VERBOSE"));
+		   ADDLIST4("FULL", "FREEZE", "ANALYZE", "VERBOSE"));
 	else if (Matches2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST2("", "ANALYZE", "VERBOSE"));
+		   ADDLIST2("ANALYZE", "VERBOSE"));
 	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST1("", "VERBOSE"));
+		   ADDLIST1("VERBOSE"));
 	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST1("", "ANALYZE"));
+		   ADDLIST1("ANALYZE"));
 	else if (Matches2("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST1("", "ANALYZE"));
+		   ADDLIST1("ANALYZE"));
 	else if (Matches2("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST1("", "VERBOSE"));
+		   ADDLIST1("VERBOSE"));
 	else if (HeadMatches1("VACUUM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 
 /* WITH [RECURSIVE] */
 
@@ -3284,7 +3293,7 @@ psql_completion(const char *text, int start, int end)
 /* ANALYZE */
 	/* Complete with list of tables */
 	else if (Matches1("ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, "");
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
@@ -3294,11 +3303,11 @@ psql_completion(const char *text, int start, int end)
 /* ... FROM ... */
 /* TODO: also include SRF ? */
 	else if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 
 /* ... JOIN ... */
 	else if (TailMatches1("JOIN"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
@@ -3307,80 +3316,80 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatchesCS1("\\connect|\\c"))
 	{
 		if (!recognized_connection_string(text))
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+			COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 	}
 	else if (TailMatchesCS2("\\connect|\\c", MatchAny))
 	{
 		if (!recognized_connection_string(prev_wd))
-			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+			COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	}
 	else if (TailMatchesCS1("\\da*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, "");
 	else if (TailMatchesCS1("\\db*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
 	else if (TailMatchesCS1("\\dD*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, "");
 	else if (TailMatchesCS1("\\des*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_servers, "");
 	else if (TailMatchesCS1("\\deu*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, "");
 	else if (TailMatchesCS1("\\dew*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, "");
 	else if (TailMatchesCS1("\\df*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 
 	else if (TailMatchesCS1("\\dFd*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries, "");
 	else if (TailMatchesCS1("\\dFp*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers, "");
 	else if (TailMatchesCS1("\\dFt*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates, "");
 	/* must be at end of \dF alternatives: */
 	else if (TailMatchesCS1("\\dF*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations, "");
 
 	else if (TailMatchesCS1("\\di*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 	else if (TailMatchesCS1("\\dL*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
+		COMPLETE_WITH_QUERY(Query_for_list_of_languages, "");
 	else if (TailMatchesCS1("\\dn*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
 	else if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 	else if (TailMatchesCS1("\\ds*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
 	else if (TailMatchesCS1("\\dt*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	else if (TailMatchesCS1("\\dT*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, "");
 	else if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	else if (TailMatchesCS1("\\dv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	else if (TailMatchesCS1("\\dx*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_extensions, "");
 	else if (TailMatchesCS1("\\dm*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	else if (TailMatchesCS1("\\dE*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 	else if (TailMatchesCS1("\\dy*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* must be at end of \d alternatives: */
 	else if (TailMatchesCS1("\\d*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, "");
 
 	else if (TailMatchesCS1("\\ef"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 	else if (TailMatchesCS1("\\ev"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 
 	else if (TailMatchesCS1("\\encoding"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_encodings, "");
 	else if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
 		COMPLETE_WITH_LIST(sql_commands);
 	else if (TailMatchesCS1("\\password"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	else if (TailMatchesCS1("\\pset"))
 	{
 		static const char *const my_list[] =
@@ -3440,14 +3449,14 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
 	}
 	else if (TailMatchesCS1("\\sf*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 	else if (TailMatchesCS1("\\sv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	else if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
 							"\\ir|\\include_relative|\\o|\\out|"
 							"\\s|\\w|\\write|\\lo_import"))
 	{
-		completion_charp = "\\";
+		SET_COMP_CHARP("\\");
 		matches = completion_matches(text, complete_from_files);
 	}
 
@@ -3463,9 +3472,9 @@ psql_completion(const char *text, int start, int end)
 		if (ent)
 		{
 			if (ent->query)
-				COMPLETE_WITH_QUERY(ent->query);
+				COMPLETE_WITH_QUERY(ent->query, "");
 			else if (ent->squery)
-				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, NULL);
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, "");
 		}
 	}
 
@@ -3723,13 +3732,13 @@ _complete_from_query(int is_schema_query, const char *text, int state)
 							  char_length, e_text);
 
 			/* If an addon query was provided, use it */
-			if (completion_charp)
-				appendPQExpBuffer(&query_buffer, "\n%s", completion_charp);
+			if (COMPLETION_CHARP[0])
+				appendPQExpBuffer(&query_buffer, "\n%s", COMPLETION_CHARP);
 		}
 		else
 		{
 			/* completion_charp is an sprintf-style format string */
-			appendPQExpBuffer(&query_buffer, completion_charp,
+			appendPQExpBuffer(&query_buffer, COMPLETION_CHARP,
 							  char_length, e_text,
 							  e_info_charp, e_info_charp,
 							  e_info_charp2, e_info_charp2);
@@ -3844,18 +3853,17 @@ complete_from_list(const char *text, int state)
 static char *
 complete_from_const(const char *text, int state)
 {
-	Assert(completion_charp != NULL);
 	if (state == 0)
 	{
 		if (completion_case_sensitive)
-			return pg_strdup(completion_charp);
+			return pg_strdup(COMPLETION_CHARP);
 		else
 
 			/*
 			 * If case insensitive matching was requested initially, adjust
 			 * the case according to setting.
 			 */
-			return pg_strdup_keyword_case(completion_charp, text);
+			return pg_strdup_keyword_case(COMPLETION_CHARP, text);
 	}
 	else
 		return NULL;
@@ -3956,7 +3964,7 @@ complete_from_files(const char *text, int state)
 	if (state == 0)
 	{
 		/* Initialization: stash the unquoted input. */
-		unquoted_text = strtokx(text, "", NULL, "'", *completion_charp,
+		unquoted_text = strtokx(text, "", NULL, "'", COMPLETION_CHARP[0],
 								false, true, pset.encoding);
 		/* expect a NULL return for the empty string only */
 		if (!unquoted_text)
@@ -3977,7 +3985,7 @@ complete_from_files(const char *text, int state)
 		 * bother providing a macro to simplify this.
 		 */
 		ret = quote_if_needed(unquoted_match, " \t\r\n\"`",
-							  '\'', *completion_charp, pset.encoding);
+							  '\'', COMPLETION_CHARP[0], pset.encoding);
 		if (ret)
 			free(unquoted_match);
 		else
@@ -4035,7 +4043,7 @@ pg_strdup_keyword_case(const char *s, const char *ref)
 
 /* Construct codelet to append given keywords  */
 static char *
-additional_kw_query(char *prefix, const char *ref, int n, ...)
+additional_kw_query(const char *ref, int n, ...)
 {
 	va_list ap;
 	static PQExpBuffer qbuf = NULL;
@@ -4046,8 +4054,6 @@ additional_kw_query(char *prefix, const char *ref, int n, ...)
 	else
 		resetPQExpBuffer(qbuf);
 
-	appendPQExpBufferStr(qbuf, prefix);
-
 	/* Construct an additional queriy to append keywords */
 	appendPQExpBufferStr(qbuf, " UNION SELECT * FROM (VALUES ");
 
-- 
1.8.3.1

0004-Refactoring-tab-complete-to-make-psql_completion-cod.patchtext/x-patch; charset=us-asciiDownload
From d4483280477a8206d03de562398054a2c669fcc7 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Thu, 7 Apr 2016 13:38:54 +0900
Subject: [PATCH 4/5] Refactoring tab-complete to make psql_completion code
 simpler.

The structure of psql_completion which is a long long sequence of
else-if's hinder it to be get more complex operations on
previous_words. To resolve this, this patch removes the sequence of
else-if's out of psql_completion so that every if-block can
immediately return to the caller. As the result, aall else-if's can be
turned into simple sequence of if's and any operation other than
matchings can be placed out of if-conditions.

Other than the above, most of the macros to be used for the convenient
of writing matching conditions are moved to tab-compolete-macros.h.
---
 src/bin/psql/Makefile              |    2 +
 src/bin/psql/tab-complete-macros.h |  513 ++++++++++
 src/bin/psql/tab-complete.c        | 1829 +++++++++++++++---------------------
 3 files changed, 1264 insertions(+), 1080 deletions(-)
 create mode 100644 src/bin/psql/tab-complete-macros.h

diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index d1c3b77..d8ad749 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -47,6 +47,8 @@ ifeq ($(GCC),yes)
 psqlscanslash.o: CFLAGS += -Wno-error
 endif
 
+tab-complete.o: tab-complete-macros.h
+
 distprep: sql_help.h psqlscanslash.c
 
 install: all installdirs
diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
new file mode 100644
index 0000000..bd89fbb
--- /dev/null
+++ b/src/bin/psql/tab-complete-macros.h
@@ -0,0 +1,513 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2016, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/tab-complete-macros.h
+ */
+#ifndef TAB_COMPLETE_MACROS_H
+#define TAB_COMPLETE_MACROS_H
+
+/*
+ * For compactness, we use these macros to reference previous_words[].
+ * Caution: do not access a previous_words[] entry without having checked
+ * previous_words_count to be sure it's valid.  In most cases below, that
+ * check is implicit in a TailMatches() or similar macro, but in some places
+ * we have to check it explicitly.
+ */
+#define prev_wd   (previous_words[0])
+#define prev2_wd  (previous_words[1])
+#define prev3_wd  (previous_words[2])
+#define prev4_wd  (previous_words[3])
+#define prev5_wd  (previous_words[4])
+#define prev6_wd  (previous_words[5])
+#define prev7_wd  (previous_words[6])
+#define prev8_wd  (previous_words[7])
+#define prev9_wd  (previous_words[8])
+
+/* Move the position of the beginning word for matching macros.  */
+#define SHIFTHEAD(n) \
+	(head_shift += n)
+
+/* Return the number of stored words counting head shift */
+#define WORD_COUNT() (previous_words_count - head_shift)
+
+/* Return the true index in previous_words for index from the beginning */
+#define HEAD_INDEX(n) \
+	(previous_words_count - head_shift - (n))
+
+/*
+ * remove n words from current shifted position, see MidMatchAndRevmove2 for
+ * the reason for the return value
+ */
+#define COLLAPSE(s, n)							\
+    do { \
+		memmove(previous_words + HEAD_INDEX(n), previous_words + HEAD_INDEX(0), \
+				sizeof(char *) * head_shift); \
+		previous_words_count -= (n); \
+	} while(0)
+
+/*
+ * Macros for matching the last N words before point, and after head_sift,
+ * case-insensitively.
+ */
+#define TailMatches1(p1) \
+	(WORD_COUNT() >= 1 && \
+	 word_matches(p1, prev_wd))
+
+#define TailMatches2(p2, p1) \
+	(WORD_COUNT() >= 2 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd))
+
+#define TailMatches3(p3, p2, p1) \
+	(WORD_COUNT() >= 3 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd))
+
+#define TailMatches4(p4, p3, p2, p1) \
+	(WORD_COUNT() >= 4 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd))
+
+#define TailMatches5(p5, p4, p3, p2, p1) \
+	(WORD_COUNT() >= 5 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd))
+
+#define TailMatches6(p6, p5, p4, p3, p2, p1) \
+	(WORD_COUNT() >= 6 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd))
+
+#define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
+	(WORD_COUNT() >= 7 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd))
+
+#define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
+	(WORD_COUNT() >= 8 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd) && \
+	 word_matches(p8, prev8_wd))
+
+#define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
+	(WORD_COUNT() >= 9 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd) && \
+	 word_matches(p8, prev8_wd) && \
+	 word_matches(p9, prev9_wd))
+
+	/*
+	 * Macros for matching the last N words before point, and after
+	 * head_shift, case-sensitively.
+	 */
+#define TailMatchesCS1(p1) \
+	(WORD_COUNT() >= 1 && \
+	 word_matches_cs(p1, prev_wd))
+#define TailMatchesCS2(p2, p1) \
+	(WORD_COUNT() >= 2 && \
+	 word_matches_cs(p1, prev_wd) && \
+	 word_matches_cs(p2, prev2_wd))
+
+	/*
+	 * Macros for matching N words exactly to the line, and after head_shift,
+	 * case-insensitively.
+	 */
+#define Matches1(p1) \
+	(WORD_COUNT() == 1 && \
+	 TailMatches1(p1))
+#define Matches2(p1, p2) \
+	(WORD_COUNT() == 2 && \
+	 TailMatches2(p1, p2))
+#define Matches3(p1, p2, p3) \
+	(WORD_COUNT() == 3 && \
+	 TailMatches3(p1, p2, p3))
+#define Matches4(p1, p2, p3, p4) \
+	(WORD_COUNT() == 4 && \
+	 TailMatches4(p1, p2, p3, p4))
+#define Matches5(p1, p2, p3, p4, p5) \
+	(WORD_COUNT() == 5 && \
+	 TailMatches5(p1, p2, p3, p4, p5))
+#define Matches6(p1, p2, p3, p4, p5, p6) \
+	(WORD_COUNT() == 6 && \
+	 TailMatches6(p1, p2, p3, p4, p5, p6))
+#define Matches7(p1, p2, p3, p4, p5, p6, p7) \
+	(WORD_COUNT() == 7 && \
+	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
+#define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
+	(WORD_COUNT() == 8 && \
+	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
+#define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
+	(WORD_COUNT() == 9 && \
+	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
+
+/*
+ * Macros for matching N words after head_shift + sth in the line, regardless
+ * of what is after them, case-insensitively.
+ */
+#define MidMatches1(s, p1) \
+	(HEAD_INDEX(s) >=0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]))
+
+#define MidMatches2(s, p1, p2) \
+	(HEAD_INDEX((s) + 1) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]))
+
+#define MidMatches3(s, p1, p2, p3) \
+	(HEAD_INDEX((s) + 2) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]))
+
+#define MidMatches4(s, p1, p2, p3, p4) \
+	(HEAD_INDEX((s) + 3) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]))
+
+#define MidMatches5(s, p1, p2, p3, p4, p5) \
+	(HEAD_INDEX((s) + 4) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&	\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&		\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]))
+
+#define MidMatches6(s,p1, p2, p3, p4, p5, p6)		\
+	(HEAD_INDEX((s) + 5) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]))
+
+#define MidMatches7(s,p1, p2, p3, p4, p5, p6, p7)	\
+	(HEAD_INDEX((s) + 6) >= 0 &&							\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&			\
+	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]))
+
+/*
+ * Macros for matching N words from the head of the line, this is equivelant
+ * to MidMatchesN from the first word in the line.
+ */
+#define HeadMatches1(p1) \
+	(HEAD_INDEX(1) >=0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]))
+
+#define HeadMatches2(p1, p2) \
+	(HEAD_INDEX(2) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]))
+
+#define HeadMatches3(p1, p2, p3) \
+	(HEAD_INDEX(3) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]))
+
+#define HeadMatches4(p1, p2, p3, p4) \
+	(HEAD_INDEX(4) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]))
+
+#define HeadMatches5(p1, p2, p3, p4, p5) \
+	(HEAD_INDEX(5) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]))
+
+#define HeadMatches6(p1, p2, p3, p4, p5, p6)		\
+	(HEAD_INDEX(6) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]) && \
+	 word_matches(p6, previous_words[HEAD_INDEX(6)]))
+
+#define HeadMatches7(p1, p2, p3, p4, p5, p6, p7)	\
+	(HEAD_INDEX(7) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]) && \
+	 word_matches(p6, previous_words[HEAD_INDEX(6)]) && \
+	 word_matches(p7, previous_words[HEAD_INDEX(7)]))
+
+
+/*
+ * completion_charp is now not a simple string, but a PQExpBuffer. These
+ * macros are to make the appearance of completion_charp to be simpler.
+ */
+
+/* Append a string to existing completion_charp  */
+#define APPEND_COMPLETION_CHARP(charp) \
+	appendPQExpBufferStr(completion_charp, charp);
+
+/* Set a string to completion_charp  */
+#define SET_COMPLETION_CHARP(charp) \
+	resetPQExpBuffer(completion_charp);	\
+	APPEND_COMPLETION_CHARP(charp);
+
+/* Read the completion_charp as a string */
+#define COMPLETION_CHARP (completion_charp->data)
+
+
+/*
+ * A few macros to ease typing. You can use these to complete the given
+ * string with
+ * 1) The results from a query you pass it. (Perhaps one of those below?)
+ * 2) The results from a schema query you pass it.
+ * 3) The items from a null-pointer-terminated list (with or without
+ *	  case-sensitive comparison; see also COMPLETE_WITH_LISTn, below).
+ * 4) A string constant.
+ * 5) The list of attributes of the given table (possibly schema-qualified).
+ * 6/ The list of arguments to the given function (possibly schema-qualified).
+ */
+#define COMPLETE_WITH_QUERY(query, addon) \
+do { \
+	SET_COMPLETION_CHARP(query);	\
+	APPEND_COMPLETION_CHARP(addon); \
+	return completion_matches(text, complete_from_query);	\
+} while (0)
+
+#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
+do { \
+	completion_squery = &(query); \
+	SET_COMPLETION_CHARP(addon); \
+	return completion_matches(text, complete_from_schema_query); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS(list) \
+do { \
+	completion_charpp = list; \
+	completion_case_sensitive = true; \
+	return completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST(list) \
+do { \
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	return completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_CONST(string) \
+do { \
+	SET_COMPLETION_CHARP(string);	\
+	completion_case_sensitive = false; \
+	return completion_matches(text, complete_from_const);	\
+} while (0)
+
+#define COMPLETE_WITH_ATTR(relation, addon) \
+do { \
+	char   *_completion_schema; \
+	char   *_completion_table; \
+\
+	_completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \
+								 false, false, pset.encoding); \
+	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+				   false, false, pset.encoding); \
+	_completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+								false, false, pset.encoding); \
+	if (_completion_table == NULL) \
+	{ \
+		SET_COMPLETION_CHARP(Query_for_list_of_attributes); \
+		APPEND_COMPLETION_CHARP(addon);					  \
+		completion_info_charp = relation; \
+	} \
+	else \
+	{ \
+		SET_COMPLETION_CHARP(Query_for_list_of_attributes_with_schema); \
+		APPEND_COMPLETION_CHARP(addon); \
+		completion_info_charp = _completion_table; \
+		completion_info_charp2 = _completion_schema; \
+	} \
+	return completion_matches(text, complete_from_query); \
+} while (0)
+
+#define COMPLETE_WITH_FUNCTION_ARG(function) \
+do { \
+	char   *_completion_schema; \
+	char   *_completion_function; \
+\
+	_completion_schema = strtokx(function, " \t\n\r", ".", "\"", 0, \
+								 false, false, pset.encoding); \
+	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+				   false, false, pset.encoding); \
+	_completion_function = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+								   false, false, pset.encoding); \
+	if (_completion_function == NULL) \
+	{ \
+		SET_COMPLETION_CHARP(Query_for_list_of_arguments); \
+		completion_info_charp = function; \
+	} \
+	else \
+	{ \
+		SET_COMPLETION_CHARP(Query_for_list_of_arguments_with_schema); \
+		completion_info_charp = _completion_function; \
+		completion_info_charp2 = _completion_schema; \
+	} \
+	return completion_matches(text, complete_from_query); \
+} while (0)
+
+/*
+ * These macros simplify use of COMPLETE_WITH_LIST for short, fixed lists.
+ * There is no COMPLETE_WITH_LIST1; use COMPLETE_WITH_CONST for that case.
+ */
+#define COMPLETE_WITH_LIST2(s1, s2) \
+do { \
+	static const char *const list[] = { s1, s2, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST3(s1, s2, s3) \
+do { \
+	static const char *const list[] = { s1, s2, s3, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST4(s1, s2, s3, s4) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST5(s1, s2, s3, s4, s5) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST6(s1, s2, s3, s4, s5, s6) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST7(s1, s2, s3, s4, s5, s6, s7) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST8(s1, s2, s3, s4, s5, s6, s7, s8) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST9(s1, s2, s3, s4, s5, s6, s7, s8, s9) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST10(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+/*
+ * Likewise for COMPLETE_WITH_LIST_CS.
+ */
+#define COMPLETE_WITH_LIST_CS2(s1, s2) \
+do { \
+	static const char *const list[] = { s1, s2, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS3(s1, s2, s3) \
+do { \
+	static const char *const list[] = { s1, s2, s3, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS4(s1, s2, s3, s4) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS5(s1, s2, s3, s4, s5) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+/*
+ * A macro to comlete "thing" at the pth position before from the current
+ * word. COMPLETE_THING(-1) completes the thing of the last word in
+ * previous_words.
+ */
+#define COMPLETE_THING(p) \
+do { \
+	const pgsql_thing_t *ent = find_thing_entry(prev_wd);	\
+	if (ent) \
+	{ \
+		if (ent->query) \
+			COMPLETE_WITH_QUERY(ent->query, ""); \
+		else if (ent->squery) \
+			COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, ""); \
+	} \
+	return NULL; \
+} while (0)
+
+/*
+ * Macros to construct a query to add additional keyword, additional_kw_query
+ * changes the case of the keywords following an input.
+ */
+#define ADDLIST1(s1) additional_kw_query(text, 1, s1)
+#define ADDLIST2(s1, s2) additional_kw_query(text, 2, s1, s2)
+#define ADDLIST3(s1, s2, s3) additional_kw_query(text, 3, s1, s2, s3)
+#define ADDLIST4(s1, s2, s3, s4) \
+	additional_kw_query(text, 4, s1, s2, s3, s4)
+#define ADDLIST13(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
+	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13)
+#define ADDLIST15(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
+	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13, s14, s15)
+
+
+#endif   /* TAB_COMPLETE_MACROS_H */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 329b834..6f227b0 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -34,6 +34,7 @@
 
 #include "postgres_fe.h"
 #include "tab-complete.h"
+#include "tab-complete-macros.h"
 #include "input.h"
 
 /* If we don't have this, we might as well forget about the whole thing: */
@@ -133,210 +134,6 @@ static const SchemaQuery *completion_squery;	/* to pass a SchemaQuery */
 static bool completion_case_sensitive;	/* completion is case sensitive */
 
 /*
- * A few macros to ease typing. You can use these to complete the given
- * string with
- * 1) The results from a query you pass it. (Perhaps one of those below?)
- * 2) The results from a schema query you pass it.
- * 3) The items from a null-pointer-terminated list (with or without
- *	  case-sensitive comparison; see also COMPLETE_WITH_LISTn, below).
- * 4) A string constant.
- * 5) The list of attributes of the given table (possibly schema-qualified).
- * 6/ The list of arguments to the given function (possibly schema-qualified).
- */
-#define APPEND_COMP_CHARP(charp) \
-	appendPQExpBufferStr(completion_charp, charp);
-
-#define SET_COMP_CHARP(charp) \
-	resetPQExpBuffer(completion_charp);	\
-	APPEND_COMP_CHARP(charp);
-
-#define COMPLETION_CHARP (completion_charp->data)
-
-#define COMPLETE_WITH_QUERY(query, addon) \
-do { \
-	SET_COMP_CHARP(query);	\
-	APPEND_COMP_CHARP(addon); \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
-do { \
-	completion_squery = &(query); \
-	SET_COMP_CHARP(addon); \
-	matches = completion_matches(text, complete_from_schema_query); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS(list) \
-do { \
-	completion_charpp = list; \
-	completion_case_sensitive = true; \
-	matches = completion_matches(text, complete_from_list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST(list) \
-do { \
-	completion_charpp = list; \
-	completion_case_sensitive = false; \
-	matches = completion_matches(text, complete_from_list); \
-} while (0)
-
-#define COMPLETE_WITH_CONST(string) \
-do { \
-	SET_COMP_CHARP(string);	\
-	completion_case_sensitive = false; \
-	matches = completion_matches(text, complete_from_const); \
-} while (0)
-
-#define COMPLETE_WITH_ATTR(relation, addon) \
-do { \
-	char   *_completion_schema; \
-	char   *_completion_table; \
-\
-	_completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-								false, false, pset.encoding); \
-	if (_completion_table == NULL) \
-	{ \
-		SET_COMP_CHARP(Query_for_list_of_attributes); \
-		APPEND_COMP_CHARP(addon);					  \
-		completion_info_charp = relation; \
-	} \
-	else \
-	{ \
-		SET_COMP_CHARP(Query_for_list_of_attributes_with_schema); \
-		APPEND_COMP_CHARP(addon); \
-		completion_info_charp = _completion_table; \
-		completion_info_charp2 = _completion_schema; \
-	} \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-#define COMPLETE_WITH_FUNCTION_ARG(function) \
-do { \
-	char   *_completion_schema; \
-	char   *_completion_function; \
-\
-	_completion_schema = strtokx(function, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_function = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-								   false, false, pset.encoding); \
-	if (_completion_function == NULL) \
-	{ \
-		SET_COMP_CHARP(Query_for_list_of_arguments); \
-		completion_info_charp = function; \
-	} \
-	else \
-	{ \
-		SET_COMP_CHARP(Query_for_list_of_arguments_with_schema); \
-		completion_info_charp = _completion_function; \
-		completion_info_charp2 = _completion_schema; \
-	} \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-/*
- * These macros simplify use of COMPLETE_WITH_LIST for short, fixed lists.
- * There is no COMPLETE_WITH_LIST1; use COMPLETE_WITH_CONST for that case.
- */
-#define COMPLETE_WITH_LIST2(s1, s2) \
-do { \
-	static const char *const list[] = { s1, s2, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST3(s1, s2, s3) \
-do { \
-	static const char *const list[] = { s1, s2, s3, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST4(s1, s2, s3, s4) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST5(s1, s2, s3, s4, s5) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST6(s1, s2, s3, s4, s5, s6) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST7(s1, s2, s3, s4, s5, s6, s7) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST8(s1, s2, s3, s4, s5, s6, s7, s8) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST9(s1, s2, s3, s4, s5, s6, s7, s8, s9) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST10(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-/*
- * Likewise for COMPLETE_WITH_LIST_CS.
- */
-#define COMPLETE_WITH_LIST_CS2(s1, s2) \
-do { \
-	static const char *const list[] = { s1, s2, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS3(s1, s2, s3) \
-do { \
-	static const char *const list[] = { s1, s2, s3, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS4(s1, s2, s3, s4) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS5(s1, s2, s3, s4, s5) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define ADDLIST1(s1) additional_kw_query(text, 1, s1)
-#define ADDLIST2(s1, s2) additional_kw_query(text, 2, s1, s2)
-#define ADDLIST3(s1, s2, s3) additional_kw_query(text, 3, s1, s2, s3)
-#define ADDLIST4(s1, s2, s3, s4) \
-	additional_kw_query(text, 4, s1, s2, s3, s4)
-#define ADDLIST13(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
-	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
-						s8, s9, s10, s11, s12, s13)
-#define ADDLIST15(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
-	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
-						s8, s9, s10, s11, s12, s13, s14, s15)
-
-/*
  * Assembly instructions for schema queries
  */
 
@@ -986,9 +783,11 @@ static PGresult *exec_query(const char *query);
 
 static char **get_previous_words(int point, char **buffer, int *nwords);
 
-static char *get_guctype(const char *varname);
+static bool get_guctype(char *buf, size_t len, const char *varname);
 
 static const pgsql_thing_t *find_thing_entry(char *word);
+static char **psql_completion_internal(const char *text, char **previous_words,
+										   int previous_words_count);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -1144,235 +943,60 @@ psql_completion(const char *text, int start, int end)
 	/* The number of words found on the input line. */
 	int			previous_words_count;
 
-	/* The number of prefixing words to be ignored */
-	int			head_shift = 0;
+	(void) end;					/* "end" is not used */
 
-	/*
-	 * For compactness, we use these macros to reference previous_words[].
-	 * Caution: do not access a previous_words[] entry without having checked
-	 * previous_words_count to be sure it's valid.  In most cases below, that
-	 * check is implicit in a TailMatches() or similar macro, but in some
-	 * places we have to check it explicitly.
-	 */
-#define prev_wd   (previous_words[0])
-#define prev2_wd  (previous_words[1])
-#define prev3_wd  (previous_words[2])
-#define prev4_wd  (previous_words[3])
-#define prev5_wd  (previous_words[4])
-#define prev6_wd  (previous_words[5])
-#define prev7_wd  (previous_words[6])
-#define prev8_wd  (previous_words[7])
-#define prev9_wd  (previous_words[8])
-
-	/* Move the position of the beginning word for matching macros.  */
-#define HEADSHIFT(n) \
-	((head_shift += n) || true)
-
-	/* Return the number of stored words counting head shift */
-#define WORD_COUNT() (previous_words_count - head_shift)
-
-	/* Return the true index in previous_words for index from the beginning */
-#define HEAD_INDEX(n) \
-	(previous_words_count - head_shift - (n))
+#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
+	rl_completion_append_character = ' ';
+#endif
 
-	/*
-	 * remove n words from current shifted position, see MidMatchAndRevmove2
-	 * for the reason for the return value
-	 */
-#define COLLAPSE(n) \
-	((memmove(previous_words + HEAD_INDEX(n), previous_words + HEAD_INDEX(0), \
-			 sizeof(char *) * head_shift), \
-	 previous_words_count -= (n)) && false)
+	/* Clear a few things. */
+	if (completion_charp == NULL)
+		completion_charp = createPQExpBuffer();
+	completion_charpp = NULL;
+	completion_info_charp = NULL;
+	completion_info_charp2 = NULL;
 
 	/*
-	 * Find the position the specified word occurs last and shift to there.
-	 * This is used to ignore the words before there.
+	 * Scan the input line to extract the words before our current position.
+	 * According to those we'll make some smart decisions on what the user is
+	 * probably intending to type.
 	 */
-#define SHIFT_TO_LAST1(p1) \
-	(HEADSHIFT(find_last_index_of(p1, previous_words, previous_words_count))|| \
-	 true)
+	previous_words = get_previous_words(start,
+										&words_buffer,
+										&previous_words_count);
 
-	/*
-	 * Remove the specified words if they match from the sth word in
-	 * previous_words. This is a bit tricky, COLLAPSE is skipped when
-	 * HeadMatches failed but the last HEADSHIFT anyway should be done.
-	 */
-#define MidMatchAndRemove1(s, p1) \
-	((HEADSHIFT(s) && HeadMatches1(p1) && COLLAPSE(1)) || HEADSHIFT(-s))
-
-#define MidMatchAndRemove2(s, p1, p2) \
-	((HEADSHIFT(s) && HeadMatches2(p1, p2) && COLLAPSE(2)) || HEADSHIFT(-s))
-
-#define MidMatchAndRemove3(s, p1, p2, p3)									\
-	((HEADSHIFT(s) && HeadMatches3(p1, p2, p3) && COLLAPSE(3)) || HEADSHIFT(-s))
-
-	/* Macros for matching the last N words before point, case-insensitively. */
-#define TailMatches1(p1) \
-	(WORD_COUNT() >= 1 && \
-	 word_matches(p1, prev_wd))
-
-#define TailMatches2(p2, p1) \
-	(WORD_COUNT() >= 2 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd))
-
-#define TailMatches3(p3, p2, p1) \
-	(WORD_COUNT() >= 3 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd))
-
-#define TailMatches4(p4, p3, p2, p1) \
-	(WORD_COUNT() >= 4 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd))
-
-#define TailMatches5(p5, p4, p3, p2, p1) \
-	(WORD_COUNT() >= 5 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd))
-
-#define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(WORD_COUNT() >= 6 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd))
-
-#define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(WORD_COUNT() >= 7 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd))
-
-#define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(WORD_COUNT() >= 8 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd) && \
-	 word_matches(p8, prev8_wd))
-
-#define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(WORD_COUNT() >= 9 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd) && \
-	 word_matches(p8, prev8_wd) && \
-	 word_matches(p9, prev9_wd))
-
-	/* Macros for matching the last N words before point, case-sensitively. */
-#define TailMatchesCS1(p1) \
-	(WORD_COUNT() >= 1 && \
-	 word_matches_cs(p1, prev_wd))
-#define TailMatchesCS2(p2, p1) \
-	(WORD_COUNT() >= 2 && \
-	 word_matches_cs(p1, prev_wd) && \
-	 word_matches_cs(p2, prev2_wd))
+	matches  = psql_completion_internal(text, previous_words,
+										previous_words_count);
 
-	/*
-	 * Macros for matching N words exactly to the line,
-	 * case-insensitively.
-	 */
-#define Matches1(p1) \
-	(WORD_COUNT() == 1 && \
-	 TailMatches1(p1))
-#define Matches2(p1, p2) \
-	(WORD_COUNT() == 2 && \
-	 TailMatches2(p1, p2))
-#define Matches3(p1, p2, p3) \
-	(WORD_COUNT() == 3 && \
-	 TailMatches3(p1, p2, p3))
-#define Matches4(p1, p2, p3, p4) \
-	(WORD_COUNT() == 4 && \
-	 TailMatches4(p1, p2, p3, p4))
-#define Matches5(p1, p2, p3, p4, p5) \
-	(WORD_COUNT() == 5 && \
-	 TailMatches5(p1, p2, p3, p4, p5))
-#define Matches6(p1, p2, p3, p4, p5, p6) \
-	(WORD_COUNT() == 6 && \
-	 TailMatches6(p1, p2, p3, p4, p5, p6))
-#define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(WORD_COUNT() == 7 && \
-	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
-#define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(WORD_COUNT() == 8 && \
-	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
-#define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(WORD_COUNT() == 9 && \
-	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
+	/* free storage */
+	free(previous_words);
+	free(words_buffer);
+
+	if (matches != NULL)
+		return matches;
 
 	/*
-	 * Macros for matching N words at the start of the line, regardless of
-	 * what is after them, case-insensitively.
+	 * If we still don't have anything to match we have to fabricate some sort
+	 * of default list. If we were to just return NULL, readline automatically
+	 * attempts filename completion, and that's usually no good.
 	 */
-#define HeadMatches1(p1) \
-	(HEAD_INDEX(1) >=0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]))
-
-#define HeadMatches2(p1, p2) \
-	(HEAD_INDEX(2) >= 0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]) &&	\
-	 word_matches(p2, previous_words[HEAD_INDEX(2)]))
-
-#define HeadMatches3(p1, p2, p3) \
-	(HEAD_INDEX(3) >= 0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
-	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
-	 word_matches(p3, previous_words[HEAD_INDEX(3)]))
-
-#define HeadMatches4(p1, p2, p3, p4) \
-	(HEAD_INDEX(4) >= 0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
-	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
-	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
-	 word_matches(p4, previous_words[HEAD_INDEX(4)]))
-
-#define HeadMatches5(p1, p2, p3, p4, p5) \
-	(HEAD_INDEX(5) >= 0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
-	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
-	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
-	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
-	 word_matches(p5, previous_words[HEAD_INDEX(5)]))
-
-#define HeadMatches6(p1, p2, p3, p4, p5, p6)		\
-	(HEAD_INDEX(6) >= 0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
-	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
-	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
-	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
-	 word_matches(p5, previous_words[HEAD_INDEX(5)]) && \
-	 word_matches(p6, previous_words[HEAD_INDEX(6)]))
-
-#define HeadMatches7(p1, p2, p3, p4, p5, p6, p7)	\
-	(HEAD_INDEX(7) >= 0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
-	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
-	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
-	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
-	 word_matches(p5, previous_words[HEAD_INDEX(5)]) && \
-	 word_matches(p6, previous_words[HEAD_INDEX(6)]) && \
-	 word_matches(p7, previous_words[HEAD_INDEX(7)]))
+#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
+	rl_completion_append_character = '\0';
+#endif
+	COMPLETE_WITH_CONST("");		/* This returns matches */
+}
 
+/*
+ * The completion function.
+ *
+ * Makes completion list. previous_words may be modified on the way of making
+ * the returning list. Note that COMPLETE_WITH_* macros immediately return to
+ * the caller of this function.
+ */
+static char **
+psql_completion_internal(const char *text, char **previous_words,
+						 int previous_words_count)
+{
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
@@ -1403,104 +1027,122 @@ psql_completion(const char *text, int start, int end)
 		"\\timing", "\\unset", "\\x", "\\w", "\\watch", "\\z", "\\!", NULL
 	};
 
-	(void) end;					/* "end" is not used */
-
-#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
-	rl_completion_append_character = ' ';
-#endif
-
-	/* Clear a few things. */
-	if (completion_charp == NULL)
-		completion_charp = createPQExpBuffer();
-	completion_charpp = NULL;
-	completion_info_charp = NULL;
-	completion_info_charp2 = NULL;
-
-	/*
-	 * Scan the input line to extract the words before our current position.
-	 * According to those we'll make some smart decisions on what the user is
-	 * probably intending to type.
-	 */
-	previous_words = get_previous_words(start,
-										&words_buffer,
-										&previous_words_count);
+	/* The number of prefixing words to be ignored */
+	int			head_shift = 0;
 
 	/* If current word is a backslash command, offer completions for that */
 	if (text[0] == '\\')
 		COMPLETE_WITH_LIST_CS(backslash_commands);
 
 	/* If current word is a variable interpolation, handle that case */
-	else if (text[0] == ':' && text[1] != ':')
+	if (text[0] == ':' && text[1] != ':')
 	{
+		char	  **matches = NULL;
+
 		if (text[1] == '\'')
 			matches = complete_from_variables(text, ":'", "'", true);
 		else if (text[1] == '"')
 			matches = complete_from_variables(text, ":\"", "\"", true);
 		else
 			matches = complete_from_variables(text, ":", "", true);
+
+		return matches;
 	}
 
 	/* If no previous word, suggest one of the basic sql commands */
-	else if (previous_words_count == 0)
+	if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
 	/*
 	 * If this is in CREATE SCHEMA, seek to the last CREATE and regard it as
 	 * current command to complete.
 	 */
-	else if (HeadMatches2("CREATE", "SCHEMA") &&
-			 SHIFT_TO_LAST1("CREATE") &&
-			 false) {} /* FALL THROUGH */
+	if (HeadMatches2("CREATE", "SCHEMA"))
+	{
+		int n;
+
+		/* CREATE SCHEMA <name> */
+		if (Matches2("CREATE", "SCHEMA"))
+			COMPLETE_WITH_QUERY(Query_for_list_of_schemas,
+								ADDLIST1("IF NOT EXISTS"));
+		/* Remove optional words IF NOT EXISTS */
+		if (HeadMatches2("CREATE", "SCHEMA") &&
+			MidMatches3(2, "IF", "NOT", "EXISTS"))
+			COLLAPSE(2, 3);
+
+		if (Matches2("CREATE", "SCHEMA"))
+			COMPLETE_THING();
+		
+		/* Else, move head match point past CREATE SCHEMA and go through */
+		if ((n = find_last_index_of("CREATE",
+									previous_words, previous_words_count)) > 0)
+		SHIFTHEAD(n);
+	}
+
 /* CREATE */
 	/* complete with something you can create */
-	else if (Matches1("CREATE"))
-		matches = completion_matches(text, create_command_generator);
+	if (Matches1("CREATE"))
+		return completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
-	else if (Matches1("DROP"))
-		matches = completion_matches(text, drop_command_generator);
+	if (Matches1("DROP"))
+		return completion_matches(text, drop_command_generator);
 
 /* ALTER */
 
-	/* ALTER TABLE */
-	else if (Matches2("ALTER", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-			ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
-
-	/* ALTER TABLE after removing optional words IF EXISTS*/
-	else if (HeadMatches2("ALTER", "TABLE") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches2("ALTER", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
-
 	/* ALTER something */
-	else if (Matches1("ALTER"))
+	if (Matches1("ALTER"))
 	{
 		static const char *const list_ALTER[] =
-		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
-			"EVENT TRIGGER", "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
-			"GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW", "OPERATOR",
-			"POLICY", "ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM", "TABLE",
-			"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
-		"USER", "USER MAPPING FOR", "VIEW", NULL};
+		{"AGGREGATE",
+		 "COLLATION", "CONVERSION",
+		 "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
+		 "EVENT TRIGGER", "EXTENSION",
+		 "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
+		 "GROUP",
+		 "INDEX",
+		 "LANGUAGE", "LARGE OBJECT",
+		 "MATERIALIZED VIEW", 
+		 "OPERATOR",
+		 "POLICY",
+		 "ROLE", "RULE",
+		 "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM",
+		 "TABLE", "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
+		 "USER", "USER MAPPING FOR",
+		 "VIEW",
+		 NULL};
 
 		COMPLETE_WITH_LIST(list_ALTER);
 	}
+	/* ALTER TABLE */
+	if (Matches2("ALTER", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+			ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
+
+	/* Remove optional words IF EXISTS just after ALTER TABLE */
+	if (HeadMatches2("ALTER", "TABLE") &&
+		MidMatches2(2, "IF", "EXISTS"))
+		COLLAPSE(2, 2);
+
+	/* ALTER TABLE */
+	if (Matches2("ALTER", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx */
-	else if (TailMatches4("ALL", "IN", "TABLESPACE", MatchAny))
+	if (TailMatches4("ALL", "IN", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("SET TABLESPACE", "OWNED BY");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY */
-	else if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
+	if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY xxx */
-	else if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
+	if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("SET TABLESPACE");
 	/* ALTER AGGREGATE,FUNCTION <name> */
-	else if (Matches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
+	if (Matches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION <name> (...) */
-	else if (Matches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
+	if (Matches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
 	{
 		if (ends_with(prev_wd, ')'))
 			COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
@@ -1509,60 +1151,62 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER SCHEMA <name> */
-	else if (Matches3("ALTER", "SCHEMA", MatchAny))
+	if (Matches3("ALTER", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER TO", "RENAME TO");
 
 	/* ALTER COLLATION <name> */
-	else if (Matches3("ALTER", "COLLATION", MatchAny))
+	if (Matches3("ALTER", "COLLATION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER CONVERSION <name> */
-	else if (Matches3("ALTER", "CONVERSION", MatchAny))
+	if (Matches3("ALTER", "CONVERSION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER DATABASE <name> */
-	else if (Matches3("ALTER", "DATABASE", MatchAny))
+	if (Matches3("ALTER", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST7("RESET", "SET", "OWNER TO", "RENAME TO",
 							"IS_TEMPLATE", "ALLOW_CONNECTIONS",
 							"CONNECTION LIMIT");
 
 	/* ALTER EVENT TRIGGER */
-	else if (Matches3("ALTER", "EVENT", "TRIGGER"))
+	if (Matches3("ALTER", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
+	if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST4("DISABLE", "ENABLE", "OWNER TO", "RENAME TO");
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
+	if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST2("REPLICA", "ALWAYS");
 
 	/* ALTER EXTENSION <name> */
-	else if (Matches3("ALTER", "EXTENSION", MatchAny))
+	if (Matches3("ALTER", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST4("ADD", "DROP", "UPDATE", "SET SCHEMA");
 
 	/* ALTER FOREIGN */
-	else if (Matches2("ALTER", "FOREIGN"))
+	if (Matches2("ALTER", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
 	/* ALTER FOREIGN TABLE */
-	else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
+	if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
 								   ADDLIST1("IF EXISTS"));
 
 	/* ALTER|DROP FOREIGN TABLE after removing optinal words IF EXISTS */
-	else if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
-			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
-			 Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
+	if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
+		MidMatches2(2, "IF", "EXISTS"))
+		COLLAPSE(2, 2);
+
+	if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 
 	/* ALTER FOREIGN TABLE <name> */
-	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
+	if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
 		{"ADD", "ALTER", "DISABLE TRIGGER", "DROP", "ENABLE", "INHERIT",
@@ -1573,58 +1217,62 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER FOREIGN TABLE xxx RENAME */
-	else if (Matches5("ALTER", "FOREIGN", "TABLE", MatchAny, "RENAME"))
+	if (Matches5("ALTER", "FOREIGN", "TABLE", MatchAny, "RENAME"))
 		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST2("COLUMN", "TO"));
 
 	/* ALTER INDEX */
-	else if (Matches2("ALTER", "INDEX"))
+	if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 			   ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
 	/* ALTER INDEX after removing optional words IF EXISTS */
-	else if (HeadMatches2("ALTER", "INDEX") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches2("ALTER", "INDEX"))
+	if (HeadMatches2("ALTER", "INDEX") &&
+		MidMatches2(2, "IF", "EXISTS"))
+		COLLAPSE(2, 2);
+
+	if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 
 	/* ALTER INDEX <name> */
-	else if (Matches3("ALTER", "INDEX", MatchAny))
+	if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
 	/* ALTER INDEX <name> SET */
-	else if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
+	if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
 		COMPLETE_WITH_LIST2("(", "TABLESPACE");
 	/* ALTER INDEX <name> RESET */
-	else if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
+	if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER INDEX <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
+	if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
 		COMPLETE_WITH_LIST3("fillfactor", "fastupdate",
 							"gin_pending_list_limit");
-	else if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
+	if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST3("fillfactor =", "fastupdate =",
 							"gin_pending_list_limit =");
 
 	/* ALTER LANGUAGE <name> */
-	else if (Matches3("ALTER", "LANGUAGE", MatchAny))
+	if (Matches3("ALTER", "LANGUAGE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER_TO", "RENAME TO");
 
 	/* ALTER LARGE OBJECT <oid> */
-	else if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
+	if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
 		COMPLETE_WITH_CONST("OWNER TO");
 
 	/* ALTER MATERIALIZED VIEW */
-	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
+	if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 			   ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
 
 	/* ALTER MATERIALIZED VIEW with name after removing optional words */
-	else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
-			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
-			 Matches3("ALTER", "MATERIALIZED", "VIEW"))
+	if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
+		MidMatches2(3, "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+
+	if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 
 	/* ALTER USER,ROLE <name> */
-	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
-			 !TailMatches2("USER", "MAPPING"))
+	if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
+		!TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_ALTERUSER[] =
 		{"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -1638,7 +1286,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER USER,ROLE <name> WITH */
-	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
+	if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_ALTERUSER_WITH[] =
@@ -1653,63 +1301,67 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
-	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	if (Matches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* ALTER DEFAULT PRIVILEGES */
-	else if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
+	if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
 		COMPLETE_WITH_LIST3("FOR ROLE", "FOR USER", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR */
-	else if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
+	if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
 		COMPLETE_WITH_LIST2("ROLE", "USER");
 	/* ALTER DEFAULT PRIVILEGES { FOR ROLE ... | IN SCHEMA ... } */
-	else if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER", MatchAny) ||
+	if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER", MatchAny) ||
 		Matches6("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("GRANT", "REVOKE");
 	/* ALTER DOMAIN <name> */
-	else if (Matches3("ALTER", "DOMAIN", MatchAny))
+	if (Matches3("ALTER", "DOMAIN", MatchAny))
 		COMPLETE_WITH_LIST6("ADD", "DROP", "OWNER TO", "RENAME", "SET",
 							"VALIDATE CONSTRAINT");
 	/* ALTER DOMAIN <sth> DROP */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
 	/* ALTER DOMAIN <sth> RENAME|VALIDATE CONSTRAINT */
-	else if (Matches5("ALTER", "DOMAIN", MatchAny, "RENAME|VALIDATE", "CONSTRAINT"))
+	if (Matches5("ALTER", "DOMAIN", MatchAny, "RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, "");
 	}
 	/* ALTER DOMAIN <sth> DROP CONSTRAINT */
-	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
+	if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type,
 							ADDLIST1("IF EXISTS"));
 	}
 	/* Try the same match after removing optional words IF EXISTS */
-	else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
-			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
-			 Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
+	if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
+		MidMatches2(5, "IF", "EXISTS"))
+		COLLAPSE(5, 2);
+
+	if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, "");
 	}
 	/* ALTER DOMAIN <sth> RENAME */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("CONSTRAINT", "TO");
 	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
-	else if (Matches6("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
+	if (Matches6("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER DOMAIN <sth> SET */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
-	else if (Matches2("ALTER", "SEQUENCE"))
+	if (Matches2("ALTER", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
 								   ADDLIST1("IF EXISTS"));
 	/* ALTER SEQUENCE with name after removing optional words IF EXISTS */
-	else if (HeadMatches2("ALTER", "SEQUENCE") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches3("ALTER", "SEQUENCE", MatchAny))
+	if (HeadMatches2("ALTER", "SEQUENCE") &&
+		MidMatches2(2, "IF", "EXISTS"))
+		COLLAPSE(2, 2);
+
+	if (Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1718,83 +1370,87 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
 	/* ALTER SEQUENCE <name> NO */
-	else if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
+	if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 	/* ALTER SERVER <name> */
-	else if (Matches3("ALTER", "SERVER", MatchAny))
+	if (Matches3("ALTER", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST4("VERSION", "OPTIONS", "OWNER TO", "RENAME TO");
 	/* ALTER SERVER <name> VERSION <version>*/
-	else if (Matches5("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
+	if (Matches5("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
 		COMPLETE_WITH_CONST("OPTIONS");
 	/* ALTER SYSTEM SET, RESET, RESET ALL */
-	else if (Matches2("ALTER", "SYSTEM"))
+	if (Matches2("ALTER", "SYSTEM"))
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
-	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
+	if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars, "");
 	/* ALTER VIEW */
-	else if (Matches2("ALTER", "VIEW"))
+	if (Matches2("ALTER", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
 								   ADDLIST1("IF EXISTS"));
 	/* ALTER VIEW <name> with subcommands after removing optional worlds */
-	else if (HeadMatches2("ALTER", "VIEW") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches3("ALTER", "VIEW", MatchAny))
+	if (HeadMatches2("ALTER", "VIEW") &&
+		MidMatches2(2, "IF", "EXISTS"))
+		COLLAPSE(2, 2);
+
+	if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
-	else if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
 	/* ALTER POLICY */
-	else if (Matches2("ALTER", "POLICY"))
+	if (Matches2("ALTER", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies,
 							ADDLIST1("IF EXISTS"));
 	/* ALTER POLICY <name> with ON after removing optional words IF EXISTS */
-	else if (HeadMatches2("ALTER", "POLICY") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches3("ALTER", "POLICY", MatchAny))
+	if (HeadMatches2("ALTER", "POLICY") &&
+		MidMatches2(2, "IF", "EXISTS"))
+		COLLAPSE(2, 2);
+
+	if (Matches3("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
-	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
+	if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, "");
 	}
 	/* ALTER POLICY <name> ON <table> - show options */
-	else if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING (", "WITH CHECK (");
 	/* ALTER POLICY <name> ON <table> TO <role> */
-	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 	/* ALTER POLICY <name> ON <table> USING ( */
-	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
-	else if (Matches7("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
+	if (Matches7("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
 		COMPLETE_WITH_CONST("(");
 
 	/* ALTER RULE <name>, add ON */
-	else if (Matches3("ALTER", "RULE", MatchAny))
+	if (Matches3("ALTER", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
-	else if (Matches4("ALTER", "RULE", MatchAny, "ON"))
+	if (Matches4("ALTER", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, "");
 	}
 
 	/* ALTER RULE <name> ON <name> */
-	else if (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/* ALTER TRIGGER <name>, add ON */
-	else if (Matches3("ALTER", "TRIGGER", MatchAny))
+	if (Matches3("ALTER", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
-	else if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
+	if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, "");
@@ -1803,17 +1459,17 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
-	else if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
+	if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* ALTER TRIGGER <name> ON <name> */
-	else if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/*
 	 * If we detect ALTER TABLE <name>, suggest sub commands
 	 */
-	else if (Matches3("ALTER", "TABLE", MatchAny))
+	if (Matches3("ALTER", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER2[] =
 		{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
@@ -1823,97 +1479,103 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
 	/* ALTER TABLE xxx ENABLE */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST5("ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE",
 							"TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
 		COMPLETE_WITH_LIST2("RULE", "TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 	/* ALTER TABLE xxx INHERIT */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx NO INHERIT */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx DISABLE */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
 		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 
 	/* ALTER TABLE xxx ALTER */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
 		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST2("COLUMN", "CONSTRAINT"));
 
 	/* ALTER TABLE xxx RENAME */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
 		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST3("COLUMN", "CONSTRAINT", "TO"));
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/* ALTER [FOREIGN] TABLE xxx RENAME yyy */
-	else if ((HeadMatches2("ALTER", "TABLE") ||
+	if ((HeadMatches2("ALTER", "TABLE") ||
 			  HeadMatches3("ALTER", "FOREIGN", "TABLE")) &&
 			 TailMatches2("RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
-	else if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
+	if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* If we have ALTER TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/*  ALTER TABLE DROP COLUMN may take IF EXISTS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
 	/* ALTER TABLE <name> with ADD/ALTER/DROP after removing optional words */
-	else if (HeadMatches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP") &&
-			 MidMatchAndRemove1(4, "COLUMN") &&
-			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
-			 Matches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP"))
+	if (HeadMatches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP"))
+	{
+		if (MidMatches1(4, "COLUMN"))
+			COLLAPSE(4, 1);
+		if (MidMatches2(4, "IF", "EXISTS"))
+			COLLAPSE(4, 2);
+	}
+	if (Matches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
-	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN") &&
-			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
-			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+	if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN") &&
+		MidMatches2(5, "IF", "EXISTS"))
+		COLLAPSE(5, 2);
+
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME|VALIDATE", "CONSTRAINT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, "");
@@ -1922,49 +1584,51 @@ psql_completion(const char *text, int start, int end)
 	 * If we have ALTER TABLE <sth> DROP CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table,
 							ADDLIST1("IF EXISTS"));
 	}
 	/* Try the same after removing optional words IF EXISTS */
-	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
-			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
-			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
+	if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
+		MidMatches2(5, "IF", "EXISTS"))
+		COLLAPSE(5, 2);
+
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, "");
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
+	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
 			 Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
 		COMPLETE_WITH_LIST4("TYPE", "SET", "RESET", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
 			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
 		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
+	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
 		 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
+	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
 	Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
 		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
 			 Matches8("ALTER", "TABLE", MatchAny, "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("DEFAULT", "NOT NULL");
-	else if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
 	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
 		COMPLETE_WITH_LIST7("(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED",
 							"WITH", "WITHOUT");
 
@@ -1972,19 +1636,19 @@ psql_completion(const char *text, int start, int end)
 	 * If we have ALTER TABLE <sth> SET TABLESPACE provide a list of
 	 * tablespaces
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
 	/* If we have ALTER TABLE <sth> SET WITH provide OIDS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
 		COMPLETE_WITH_CONST("OIDS");
 	/* If we have ALTER TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
 		COMPLETE_WITH_LIST2("CLUSTER", "OIDS");
 	/* ALTER TABLE <foo> RESET */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLE <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
 	{
 		static const char *const list_TABLEOPTIONS[] =
 		{
@@ -2021,117 +1685,111 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
-	else if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
+	if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
 		COMPLETE_WITH_LIST4("FULL", "NOTHING", "DEFAULT", "USING");
-	else if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
 		COMPLETE_WITH_CONST("IDENTITY");
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
-	else if (Matches3("ALTER", "TABLESPACE", MatchAny))
+	if (Matches3("ALTER", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "OWNER TO", "SET", "RESET");
 	/* ALTER TABLESPACE <foo> SET|RESET */
-	else if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
+	if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
+	if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
 		COMPLETE_WITH_LIST3("seq_page_cost", "random_page_cost",
 							"effective_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
-	else if (Matches3("ALTER", "TEXT", "SEARCH"))
+	if (Matches3("ALTER", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
 		COMPLETE_WITH_LIST2("RENAME TO", "SET SCHEMA");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING",
 							"DROP MAPPING",	"OWNER TO", "RENAME TO", "SET SCHEMA");
-	else if (Matches7("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny, "DROP", "MAPPING"))
+	if (Matches7("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny, "DROP", "MAPPING"))
 		COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR");
-	/* Remove optional words IF EXISTS */
-	else if (HeadMatches7("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny, "DROP", "MAPPING") &&
-			 MidMatchAndRemove2(7, "IF", "EXISTS") &&
-			 false) {} /* Nothing to do for now */
 
 	/* complete ALTER TYPE <foo> with actions */
-	else if (Matches3("ALTER", "TYPE", MatchAny))
+	if (Matches3("ALTER", "TYPE", MatchAny))
 		COMPLETE_WITH_LIST7("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE",
 							"DROP ATTRIBUTE",
 							"OWNER TO", "RENAME", "SET SCHEMA");
 	/* complete ALTER TYPE <foo> ADD with actions */
-	else if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
+	if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
-	else if (Matches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE"))
+	if (Matches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE"))
 		COMPLETE_WITH_LIST2("IF NOT EXISTS", "");
-	/* Remove optional words IF NOT EXISTS */
-	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE") &&
-			 MidMatchAndRemove3(5, "IF", "NOT", "EXISTS") &&
-			 false) {} /* Nothing to do for now */
 	/* ALTER TYPE <foo> RENAME	*/
-	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "TO");
 	/* ALTER TYPE xxx RENAME ATTRIBUTE yyy */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE", MatchAny))
+	if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/*
 	 * If we have ALTER TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list
 	 * of attributes
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|RENAME", "ATTRIBUTE"))
+	if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
-	else if (Matches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE"))
+	if (Matches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
 	/* Remove optional words IF EXISTS */
-	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE") &&
-			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+	if (HeadMatches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE") &&
+		MidMatches2(5, "IF", "EXISTS"))
+		COLLAPSE(5, 2);
+
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-			 Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
+	if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* complete ALTER GROUP <foo> */
-	else if (Matches3("ALTER", "GROUP", MatchAny))
+	if (Matches3("ALTER", "GROUP", MatchAny))
 		COMPLETE_WITH_LIST3("ADD USER", "DROP USER", "RENAME TO");
 	/* complete ALTER GROUP <foo> ADD|DROP with USER */
-	else if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
+	if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
 		COMPLETE_WITH_CONST("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
-	else if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
+	if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* BEGIN, END, ABORT */
-	else if (Matches1("BEGIN|END|ABORT"))
+	if (Matches1("BEGIN|END|ABORT"))
 		COMPLETE_WITH_LIST2("WORK", "TRANSACTION");
 /* COMMIT */
-	else if (Matches1("COMMIT"))
+	if (Matches1("COMMIT"))
 		COMPLETE_WITH_LIST3("WORK", "TRANSACTION", "PREPARED");
 /* RELEASE SAVEPOINT */
-	else if (Matches1("RELEASE"))
+	if (Matches1("RELEASE"))
 		COMPLETE_WITH_CONST("SAVEPOINT");
 /* ROLLBACK */
-	else if (Matches1("ROLLBACK"))
+	if (Matches1("ROLLBACK"))
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
-	else if (Matches1("CLUSTER"))
+	if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   ADDLIST1("VERBOSE"));
-	else if (Matches2("CLUSTER", "VERBOSE"))
+	if (Matches2("CLUSTER", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 	/* If we have CLUSTER <sth>, then add "USING" */
-	else if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
+	if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	else if (Matches3("CLUSTER", "VERBOSE", MatchAny))
+	if (Matches3("CLUSTER", "VERBOSE", MatchAny))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER <sth> USING, then add the index as well */
-	else if (Matches3("CLUSTER", MatchAny, "USING") ||
+	if (Matches3("CLUSTER", MatchAny, "USING") ||
 			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
@@ -2139,9 +1797,9 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* COMMENT */
-	else if (Matches1("COMMENT"))
+	if (Matches1("COMMENT"))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches2("COMMENT", "ON"))
+	if (Matches2("COMMENT", "ON"))
 	{
 		static const char *const list_COMMENT[] =
 		{"CAST", "COLLATION", "CONVERSION", "DATABASE", "EVENT TRIGGER", "EXTENSION",
@@ -2153,24 +1811,24 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
-	else if (Matches3("COMMENT", "ON", "FOREIGN"))
+	if (Matches3("COMMENT", "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
-	else if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
+	if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches3("COMMENT", "ON", "CONSTRAINT"))
+	if (Matches3("COMMENT", "ON", "CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_all_table_constraints, "");
-	else if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
+	if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
+	if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint, "");
 	}
-	else if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
+	if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
-	else if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
+	if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
-	else if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
+	if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
 		Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
 			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
 		COMPLETE_WITH_CONST("IS");
@@ -2181,274 +1839,278 @@ psql_completion(const char *text, int start, int end)
 	 * If we have COPY, offer list of tables or "(" (Also cover the analogous
 	 * backslash command).
 	 */
-	else if (Matches1("COPY|\\copy"))
+	if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   ADDLIST1("("));
 	/* If we have COPY BINARY, complete with list of tables */
-	else if (Matches2("COPY", "BINARY"))
+	if (Matches2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* If we have COPY (, complete it with legal commands */
-	else if (Matches2("COPY|\\copy", "("))
+	if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
 	/* If we have COPY [BINARY] <sth>, complete it with "TO" or "FROM" */
-	else if (Matches2("COPY|\\copy", MatchAny) ||
+	if (Matches2("COPY|\\copy", MatchAny) ||
 			 Matches3("COPY", "BINARY", MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "TO");
 	/* If we have COPY [BINARY] <sth> FROM|TO, complete with filename */
-	else if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
+	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
 			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
 	{
-		SET_COMP_CHARP("");
-		matches = completion_matches(text, complete_from_files);
+		SET_COMPLETION_CHARP("");
+		return completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename */
-	else if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
+	if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
 			 Matches5("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
 		COMPLETE_WITH_LIST6("BINARY", "OIDS", "DELIMITER", "NULL", "CSV",
 							"ENCODING");
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename CSV */
-	else if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
+	if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
 			 Matches6("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny, "CSV"))
 		COMPLETE_WITH_LIST5("HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE",
 							"FORCE NOT NULL");
 
 	/* CREATE ACCESS METHOD */
 	/* Complete "CREATE ACCESS METHOD <name>" */
-	else if (Matches4("CREATE", "ACCESS", "METHOD", MatchAny))
+	if (Matches4("CREATE", "ACCESS", "METHOD", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE" */
-	else if (Matches5("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
+	if (Matches5("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
 		COMPLETE_WITH_CONST("INDEX");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE <type>" */
-	else if (Matches6("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
+	if (Matches6("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
 		COMPLETE_WITH_CONST("HANDLER");
 
 	/* CREATE DATABASE */
-	else if (Matches3("CREATE", "DATABASE", MatchAny))
+	if (Matches3("CREATE", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST9("OWNER", "TEMPLATE", "ENCODING", "TABLESPACE",
 							"IS_TEMPLATE",
 							"ALLOW_CONNECTIONS", "CONNECTION LIMIT",
 							"LC_COLLATE", "LC_CTYPE");
 
-	else if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
+	if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases, "");
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
-	else if (Matches2("CREATE", "EXTENSION"))
+	if (Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions,
 							ADDLIST1("IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS */
-	else if (HeadMatches2("CREATE", "EXTENSION") &&
-			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
-			 Matches2("CREATE", "EXTENSION"))
+	if (HeadMatches2("CREATE", "EXTENSION") &&
+		MidMatches3(2, "IF", "NOT", "EXISTS"))
+		COLLAPSE(2, 3);
+
+	if (Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions, "");
 	/* CREATE EXTENSION <name> */
-	else if (Matches3("CREATE", "EXTENSION", MatchAny))
+	if (Matches3("CREATE", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
 	/* CREATE EXTENSION <name> VERSION */
-	else if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
+	if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions, "");
 	}
 
 	/* CREATE FOREIGN */
-	else if (Matches2("CREATE", "FOREIGN"))
+	if (Matches2("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* CREATE FOREIGN TABLE */
-	else if (Matches3("CREATE", "FOREIGN", "TABLE"))
+	if (Matches3("CREATE", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
 								   ADDLIST1("IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
-	else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
-			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
-			 false) {} /* FALL THROUGH */
+	if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
+		MidMatches3(3, "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+
+	if (Matches3("CREATE", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 	/* CREATE FOREIGN DATA WRAPPER */
-	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	else if (TailMatches2("CREATE", "UNIQUE"))
+	if (TailMatches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
-	/* Remove optional word UNIQUE from CREATE UNIQUE INDEX */
-	else if (HeadMatches3("CREATE", MatchAny, "INDEX") &&
-			 MidMatchAndRemove1(1, "UNIQUE") &&
-			 false) {} /* FALL THROUGH */
-	/* If we have CREATE INDEX, then add "ON", "CONCURRENTLY" or IF NOT EXISTS,
-	   and existing indexes */
-	else if (Matches2("CREATE", "INDEX"))
+	/*
+	 * Complete CREATE INDEX with "ON", "CONCURRENTLY" or IF NOT EXISTS, and
+	 * existing indexes, after removing optional words
+	 */
+	if (HeadMatches2("CREATE", "UNIQUE") &&
+		MidMatches1(1, "UNIQUE"))
+		COLLAPSE(1, 1);
+
+	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 				   ADDLIST3("ON", "CONCURRENTLY", "IF NOT EXISTS"));
 	/* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and
 	 * existing indexes */
-	else if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
+	if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   ADDLIST2("IF NOT EXISTS", "ON"));
 
-	/* Remove optional words "CONCURRENTLY",  "IF NOT EXISTS" */
-	else if (HeadMatches2("CREATE", "INDEX") &&
-			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
-			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
-			 false) {} /* FALL THROUGH */
-
+	/* Complete CREATE INDEX after removing optional words */
+	if (HeadMatches2("CREATE", "INDEX"))
+	{
+		if (MidMatches1(2, "CONCURRENTLY"))
+			COLLAPSE(2, 1);
+		if (MidMatches3(2, "IF", "NOT", "EXISTS"))
+			COLLAPSE(2, 3);
+	}
+	if (Matches2("CREATE", "INDEX"))
+		COMPLETE_THING();
 	/* Complete CREATE INDEX [<name>] ON with a list of tables */
-	else if (Matches4("CREATE", "INDEX", MatchAny, "ON") ||
+	if (Matches4("CREATE", "INDEX", MatchAny, "ON") ||
 			 Matches3("CREATE", "INDEX", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 
 	/* Complete CREATE INDEX <sth> with "ON" */
-	else if (Matches3("CREATE", "INDEX", MatchAny))
+	if (Matches3("CREATE", "INDEX", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/*
 	 * Complete INDEX <name> ON <table> with a list of table columns (which
 	 * should really be in parens)
 	 */
-	else if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
+	if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
 			 TailMatches3("INDEX", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	else if (Matches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
+	if (Matches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
 			 Matches4("INDEX", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* same if you put in USING */
-	else if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
+	if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
+	if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, "");
-	else if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
+	if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
 			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
 			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-	else if (Matches3("CREATE", "POLICY", MatchAny))
+	if (Matches3("CREATE", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
-	else if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
+	if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
-	else if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
+	if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING (", "WITH CHECK (");
 	/* CREATE POLICY <name> ON <table> FOR ALL|SELECT|INSERT|UPDATE|DELETE */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
 		COMPLETE_WITH_LIST5("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
 	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
 		COMPLETE_WITH_LIST2("TO", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> FOR SELECT|DELETE TO|USING" */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
 		COMPLETE_WITH_LIST2("TO", "USING (");
 	/* CREATE POLICY <name> ON <table> FOR ALL|UPDATE TO|USING|WITH CHECK */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
 		COMPLETE_WITH_LIST3("TO", "USING (", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS ON" */
-	else if (Matches3("CREATE", "RULE", MatchAny))
+	if (Matches3("CREATE", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("AS ON");
 	/* Complete "CREATE RULE <sth> AS" with "ON" */
-	else if (Matches4("CREATE", "RULE", MatchAny, "AS"))
+	if (Matches4("CREATE", "RULE", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE RULE <sth> AS ON" with SELECT|UPDATE|INSERT|DELETE */
-	else if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
+	if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
 	/* Complete "AS ON SELECT|UPDATE|INSERT|DELETE" with a "TO" */
-	else if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
+	if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
-	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
+	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
-/* Remove optional words TEMPORARY/TEMP */
-	else if (HeadMatches3("CREATE", MatchAny, "SEQUENCE") &&
-			 MidMatchAndRemove1(1, "TEMP|TEMPORARY") &&
-			 false) {} /* FALL THROUGH */
-/* CREATE SEQUENCE */
-	else if(Matches2("CREATE", "SEQUENCE"))
+
+/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
+	if (Matches2("CREATE", "TEMP|TEMPORARY"))
+		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
+/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
+	if (Matches2("CREATE", "UNLOGGED"))
+		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
+
+/* CREATE SEQUCNE after removing optional words TEMPORARY/TEMP */
+	if (HeadMatches1("CREATE") &&
+		MidMatches1(1, "TEMP|TEMPORARY|UNLOGGED"))
+		COLLAPSE(1, 1);
+
+	if (Matches2("CREATE", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
 								   ADDLIST1("IF NOT EXISTS"));
-	else if(HeadMatches2("CREATE", "SEQUENCE") &&
-			MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
-			Matches3("CREATE", "SEQUENCE", MatchAny))
+	if(HeadMatches2("CREATE", "SEQUENCE") &&
+	   MidMatches3(2, "IF", "NOT", "EXISTS"))
+		COLLAPSE(2, 3);
+
+	if (Matches3("CREATE", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	else if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO"))
+	if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
-	else if (Matches3("CREATE", "SERVER", MatchAny))
+	if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
-/* CREATE SCHEMA <name> */
-	else if (Matches2("CREATE", "SCHEMA"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas,
-							ADDLIST1("IF NOT EXISTS"));
-	/* Remove optional words IF NOT EXISTS */
-	else if (HeadMatches2("CREATE", "SCHEMA") &&
-			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
-			 false) {} /* FALL THROUGH*/
-
 /* CREATE TABLE  */
-	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (Matches2("CREATE", "TEMP|TEMPORARY"))
-		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
-	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	else if (Matches2("CREATE", "UNLOGGED"))
-		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
-
+	/* Optional words TEMP|TEMPORARY|UNLOGGED is already removed */
 	/* CREATE TABLE with name after removing optional words */
-	else if (HeadMatches3("CREATE", MatchAny, "TABLE") &&
-			 MidMatchAndRemove1(1, "TEMP|TEMPORARY|UNLOGGED") &&
-			 false) {} /* FALL THROUGH*/
-	else if (Matches2("CREATE", "TABLE"))
+	if (Matches2("CREATE", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   ADDLIST1("IF NOT EXISTS"));
+	/* CREATE TABLE after removing optional words here */
+	if (HeadMatches2("CREATE", "TABLE") &&
+		MidMatches3(2, "IF", "NOT", "EXISTS"))
+		COLLAPSE(2, 3);
 
-	/* Remove optional words here */
-	else if (HeadMatches2("CREATE", "TABLE") &&
-			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
-			 false) {} /* FALL THROUGH */
-
+	if (Matches2("CREATE", "TABLE"))
+		COMPLETE_THING();
 /* CREATE TABLESPACE */
-	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
+	if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
 	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
-	else if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
+	if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
 		COMPLETE_WITH_CONST("LOCATION");
 
 /* CREATE TEXT SEARCH */
-	else if (Matches3("CREATE", "TEXT", "SEARCH"))
+	if (Matches3("CREATE", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	else if (Matches3("CREATE", "TRIGGER", MatchAny))
+	if (Matches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (Matches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	if (Matches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (Matches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	if (Matches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if (Matches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+	if (Matches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
 	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
@@ -2456,17 +2118,17 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
 	 * tables
 	 */
-	else if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
+	if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
-	else if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
+	if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
 
 /* CREATE ROLE,USER,GROUP <name> */
-	else if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
+	if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_CREATEROLE[] =
@@ -2481,7 +2143,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE ROLE,USER,GROUP <name> WITH */
-	else if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
+	if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_CREATEROLE_WITH[] =
@@ -2499,84 +2161,88 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
 	 * PASSWORD
 	 */
-	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
-	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
+	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
 /* CREATE VIEW  */
-	else if (Matches2("CREATE", "VIEW"))
+	if (Matches2("CREATE", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
 								   ADDLIST1("IF NOT EXISTS"));
 	/* CREATE VIEW <name> with AS after removing optional words */
-	else if (HeadMatches2("CREATE", "VIEW") &&
-			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
-			 Matches3("CREATE", "VIEW", MatchAny))
+	if (HeadMatches2("CREATE", "VIEW") &&
+		MidMatches3(2, "IF", "NOT", "EXISTS"))
+		COLLAPSE(2, 3);
+
+	if (Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
+	if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches2("CREATE", "MATERIALIZED"))
+	if (Matches2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
+	if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   ADDLIST1("IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS. VIEW will be
 	 * completed afterwards */
-	else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
-			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
-			 Matches3("CREATE", "MATERIALIZED", "VIEW"))
+	if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
+		MidMatches3(3, "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+
+	if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE EVENT TRIGGER */
-	else if (Matches2("CREATE", "EVENT"))
+	if (Matches2("CREATE", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	/* Complete CREATE EVENT TRIGGER <name> with ON */
-	else if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
+	if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
-	else if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
+	if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_LIST3("ddl_command_start", "ddl_command_end", "sql_drop");
 
 /* DECLARE */
-	else if (Matches2("DECLARE", MatchAny))
+	if (Matches2("DECLARE", MatchAny))
 		COMPLETE_WITH_LIST5("BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL",
 							"CURSOR");
-	else if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
+	if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
 		COMPLETE_WITH_LIST3("WITH HOLD", "WITHOUT HOLD", "FOR");
 
 /* DELETE --- can be inside EXPLAIN, RULE, etc */
 	/* ... despite which, only complete DELETE with FROM at start of line */
-	else if (Matches1("DELETE"))
+	if (Matches1("DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
-	else if (TailMatches2("DELETE", "FROM"))
+	if (TailMatches2("DELETE", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete DELETE FROM <table> */
-	else if (TailMatches3("DELETE", "FROM", MatchAny))
+	if (TailMatches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
 	/* XXX: implement tab completion for DELETE ... USING */
 
 /* DISCARD */
-	else if (Matches1("DISCARD"))
+	if (Matches1("DISCARD"))
 		COMPLETE_WITH_LIST4("ALL", "PLANS", "SEQUENCES", "TEMP");
 
 /* DO */
-	else if (Matches1("DO"))
+	if (Matches1("DO"))
 		COMPLETE_WITH_CONST("LANGUAGE");
 
 /* DROP */
 	/* Complete DROP object with CASCADE / RESTRICT */
-	else if (Matches3("DROP",
+	if (Matches3("DROP",
 					  "COLLATION|CONVERSION|DOMAIN|EXTENSION|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW",
 					  MatchAny) ||
 			 Matches4("DROP", "ACCESS", "METHOD", MatchAny) ||
@@ -2589,138 +2255,160 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (Matches2("DROP", "AGGREGATE"))
+	if (Matches2("DROP", "AGGREGATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates,
 								   ADDLIST1("IF EXISTS"));
-	else if (Matches2("DROP", "FUNCTION"))
+	if (Matches2("DROP", "FUNCTION"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions,
 								   ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
-	else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
+	if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
+		MidMatches2(2, "IF", "EXISTS"))
+		COLLAPSE(2, 2);
+
+	if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
-	else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
+	if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
-	else if (Matches2("DROP", "FOREIGN"))
+	if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
-	else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
+	if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws,
 							ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
-	else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&
-			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
-			 false) {} /* FALL THROUGH */
+	if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&	
+		MidMatches2(4, "IF", "EXISTS"))
+		COLLAPSE(4, 2);
+
+	if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, "");
 
 	/* DROP INDEX */
-	else if (Matches2("DROP", "INDEX"))
+	if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   ADDLIST2("IF EXISTS","CONCURRENTLY"));
-	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
+	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   ADDLIST1("IF EXISTS"));
 	/* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */
-	else if (HeadMatches2("DROP", "INDEX") &&
-			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches3("DROP", "INDEX", MatchAny))
+	if (HeadMatches2("DROP", "INDEX"))
+	{
+		if (MidMatches1(2, "CONCURRENTLY"))
+			COLLAPSE(2, 1);
+		if (MidMatches2(2, "IF", "EXISTS"))
+			COLLAPSE(2, 2);
+	}
+	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
-	else if (Matches2("DROP", "MATERIALIZED"))
+	if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches2("DROP", "VIEW"))
+	if (Matches2("DROP", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
 								   ADDLIST1("IF EXISTS"));
 	/* Remove optional words IF EXISTS  */
-	else if (HeadMatches2("DROP", "VIEW") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 false) {} /* FALL THROUGH */
-	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+	if (HeadMatches2("DROP", "VIEW") &&
+		MidMatches2(2, "IF", "EXISTS"))
+		COLLAPSE(2, 2);
+
+	if (Matches2("DROP", "VIEW"))
+		COMPLETE_THING();
+	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
-	else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
-			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
-			 Matches3("DROP", "MATERIALIZED", "VIEW"))
+	if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
+		MidMatches2(3, "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+
+	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 
 	/* DROP OWNED BY */
-	else if (Matches2("DROP", "OWNED"))
+	if (Matches2("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (Matches3("DROP", "OWNED", "BY"))
+	if (Matches3("DROP", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
-	else if (Matches3("DROP", "TEXT", "SEARCH"))
+	if (Matches3("DROP", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (Matches2("DROP", "TRIGGER"))
+	if (Matches2("DROP", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_triggers,
 							ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
-	else if (HeadMatches2("DROP", "TRIGGER") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches3("DROP", "TRIGGER", MatchAny))
+	if (HeadMatches2("DROP", "TRIGGER") &&
+		MidMatches2(2, "IF", "EXISTS"))
+		COLLAPSE(2, 2);
+
+	if (Matches3("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
+	if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, "");
 	}
-	else if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
+	if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP EVENT TRIGGER */
-	else if (Matches2("DROP", "EVENT"))
+	if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
-	else if (Matches3("DROP", "EVENT", "TRIGGER"))
+	if (Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers,
 							ADDLIST1("IF EXISTS"));
 	/* Trye the same after removing optional words IF EXISTS */
-	else if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
-			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
-			 Matches3("DROP", "EVENT", "TRIGGER"))
+	if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
+		MidMatches2(3, "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+
+	if (Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* DROP POLICY */
-	else if (Matches2("DROP", "POLICY"))
+	if (Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies,
 							ADDLIST1("IF EXISTS"));
 	/* Try the same after after removing optional words IF EXISTS */
-	else if (HeadMatches2("DROP", "POLICY") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches2("DROP", "POLICY"))
+	if (HeadMatches2("DROP", "POLICY") &&
+		MidMatches2(2, "IF", "EXISTS"))
+		COLLAPSE(2, 2);
+
+	if (Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies, "");
 	/* DROP POLICY <name> */
-	else if (Matches3("DROP", "POLICY", MatchAny))
+	if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* DROP POLICY <name> ON */
-	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
+	if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, "");
 	}
 
 	/* DROP RULE */
-	else if (Matches2("DROP", "RULE"))
+	if (Matches2("DROP", "RULE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_rules,
 							ADDLIST1("IF EXISTS"));
 	/* DROP RULE <name>, after removing optional words IF EXISTS */
-	else if (HeadMatches2("DROP", "RULE") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches3("DROP", "RULE", MatchAny))
+	if (HeadMatches2("DROP", "RULE") &&
+		MidMatches2(2, "IF", "EXISTS"))
+		COLLAPSE(2, 2);
+
+	if (Matches3("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
+	if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, "");
 	}
-	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
+	if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* IF EXISTS processing for DROP everything else */
-	else if (Matches2("DROP",
+	if (Matches2("DROP",
 					  "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|"
 					  "GROUP|LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|"
 					  "SERVER|TABLE|TABLESPACE|TYPE|USER") ||
@@ -2754,19 +2442,25 @@ psql_completion(const char *text, int start, int end)
 		}
 	}
 	/* Remove optional IF EXISTS from DROP */
-	else if (HeadMatches2("DROP",
-						  "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|GROUP|"
-						  "LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|SERVER|"
-						  "TABLE|TABLESPACE|TYPE|USER") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 false) {} /* FALL THROUGH */
-	else if (HeadMatches4("DROP", "TEXT", "SEARCH",
-						  "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE") &&
-			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
-			 false) {} /* FALL THROUGH */
+	if (HeadMatches2("DROP",
+					 "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|GROUP|"
+					 "LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|SERVER|"
+					 "TABLE|TABLESPACE|TYPE|USER") &&
+		MidMatches2(2, "IF", "EXISTS"))
+		COLLAPSE(2, 2);
+
+	if (Matches2("DROP", MatchAny))
+		COMPLETE_THING();
+	if (HeadMatches4("DROP", "TEXT", "SEARCH",
+					 "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE") &&
+		MidMatches2(4, "IF", "EXISTS"))
+		COLLAPSE(4, 2);
+
+	if (Matches4("DROP", "TEXT", "SEARCH", MatchAny))
+		COMPLETE_THING();
 
 /* EXECUTE */
-	else if (Matches1("EXECUTE"))
+	if (Matches1("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements, "");
 
 /* EXPLAIN */
@@ -2774,22 +2468,22 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
-	else if (Matches1("EXPLAIN"))
+	if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"ANALYZE", "VERBOSE");
-	else if (Matches2("EXPLAIN", "ANALYZE"))
+	if (Matches2("EXPLAIN", "ANALYZE"))
 		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"VERBOSE");
-	else if (Matches2("EXPLAIN", "VERBOSE") ||
+	if (Matches2("EXPLAIN", "VERBOSE") ||
 			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
 		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-	else if (Matches1("FETCH|MOVE"))
+	if (Matches1("FETCH|MOVE"))
 		COMPLETE_WITH_LIST4("ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE");
 	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
-	else if (Matches2("FETCH|MOVE", MatchAny))
+	if (Matches2("FETCH|MOVE", MatchAny))
 		COMPLETE_WITH_LIST3("ALL", "NEXT", "PRIOR");
 
 	/*
@@ -2797,30 +2491,30 @@ psql_completion(const char *text, int start, int end)
 	 * but we may as well tab-complete both: perhaps some users prefer one
 	 * variant or the other.
 	 */
-	else if (Matches3("FETCH|MOVE", MatchAny, MatchAny))
+	if (Matches3("FETCH|MOVE", MatchAny, MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "IN");
 
 /* FOREIGN DATA WRAPPER */
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
-	else if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
+	if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
 			 !TailMatches4("CREATE", MatchAny, MatchAny, MatchAny))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, "");
 	/* applies in CREATE SERVER */
-	else if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
+	if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
 			 HeadMatches2("CREATE", "SERVER"))
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /* FOREIGN TABLE */
-	else if (TailMatches2("FOREIGN", "TABLE"))
+	if (TailMatches2("FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 
 /* FOREIGN SERVER */
-	else if (TailMatches2("FOREIGN", "SERVER"))
+	if (TailMatches2("FOREIGN", "SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers, "");
 
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	else if (TailMatches1("GRANT|REVOKE"))
+	if (TailMatches1("GRANT|REVOKE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
 			ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
 					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
@@ -2830,11 +2524,11 @@ psql_completion(const char *text, int start, int end)
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	else if (TailMatches2("GRANT|REVOKE", MatchAny))
+	if (TailMatches2("GRANT|REVOKE", MatchAny))
 	{
 		if (TailMatches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
-		else if (TailMatches2("GRANT", MatchAny))
+		if (TailMatches2("GRANT", MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2851,7 +2545,7 @@ psql_completion(const char *text, int start, int end)
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
 	 */
-	else if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
+	if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
 			   ADDLIST15("ALL FUNCTIONS IN SCHEMA",
 						 "ALL SEQUENCES IN SCHEMA",
@@ -2869,11 +2563,11 @@ psql_completion(const char *text, int start, int end)
 						 "TABLESPACE",
 						 "TYPE"));
 
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
 							"TABLES IN SCHEMA");
 
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/*
@@ -2882,27 +2576,27 @@ psql_completion(const char *text, int start, int end)
 	 *
 	 * Complete "GRANT/REVOKE * ON *" with "TO/FROM".
 	 */
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
 		if (TailMatches1("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
-		else if (TailMatches1("DOMAIN"))
+		if (TailMatches1("DOMAIN"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, "");
-		else if (TailMatches1("FUNCTION"))
+		if (TailMatches1("FUNCTION"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
-		else if (TailMatches1("LANGUAGE"))
+		if (TailMatches1("LANGUAGE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_languages, "");
-		else if (TailMatches1("SCHEMA"))
+		if (TailMatches1("SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
-		else if (TailMatches1("SEQUENCE"))
+		if (TailMatches1("SEQUENCE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
-		else if (TailMatches1("TABLE"))
+		if (TailMatches1("TABLE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
-		else if (TailMatches1("TABLESPACE"))
+		if (TailMatches1("TABLESPACE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
-		else if (TailMatches1("TYPE"))
+		if (TailMatches1("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, "");
-		else if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
+		if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2912,18 +2606,18 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "GRANT/REVOKE ... TO/FROM" with username, PUBLIC,
 	 * CURRENT_USER, or SESSION_USER.
 	 */
-	else if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
+	if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
 			 (HeadMatches1("REVOKE") && TailMatches1("FROM")))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 
 	/* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */
-	else if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
+	if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (HeadMatches1("REVOKE") && TailMatches3("ON", MatchAny, MatchAny))
+	if (HeadMatches1("REVOKE") && TailMatches3("ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("FROM");
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	else if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
+	if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
 	{
 		if (TailMatches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2932,7 +2626,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	else if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 	{
 		if (TailMatches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2941,7 +2635,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	else if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
+	if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
 	{
 		if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2950,62 +2644,62 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* GROUP BY */
-	else if (TailMatches3("FROM", MatchAny, "GROUP"))
+	if (TailMatches3("FROM", MatchAny, "GROUP"))
 		COMPLETE_WITH_CONST("BY");
 
 /* IMPORT FOREIGN SCHEMA */
-	else if (Matches1("IMPORT"))
+	if (Matches1("IMPORT"))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
-	else if (Matches2("IMPORT", "FOREIGN"))
+	if (Matches2("IMPORT", "FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT --- can be inside EXPLAIN, RULE, etc */
 	/* Complete INSERT with "INTO" */
-	else if (TailMatches1("INSERT"))
+	if (TailMatches1("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
-	else if (TailMatches2("INSERT", "INTO"))
+	if (TailMatches2("INSERT", "INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete "INSERT INTO <table> (" with attribute names */
-	else if (TailMatches4("INSERT", "INTO", MatchAny, "("))
+	if (TailMatches4("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES"
 	 */
-	else if (TailMatches3("INSERT", "INTO", MatchAny))
+	if (TailMatches3("INSERT", "INTO", MatchAny))
 		COMPLETE_WITH_LIST5("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES");
 
 	/*
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE"
 	 */
-	else if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
+	if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
 			 ends_with(prev_wd, ')'))
 		COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES");
 
 	/* Insert an open parenthesis after "VALUES" */
-	else if (TailMatches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
+	if (TailMatches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
 		COMPLETE_WITH_CONST("(");
 
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
-	else if (Matches1("LOCK"))
+	if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   ADDLIST1("TABLE"));
-	else if (Matches2("LOCK", "TABLE"))
+	if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* For the following, handle the case of a single table only for now */
 
 	/* Complete LOCK [TABLE] <table> with "IN" */
-	else if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
+	if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
 			 Matches3("LOCK", "TABLE", MatchAny))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	else if (Matches3("LOCK", MatchAny, "IN") ||
+	if (Matches3("LOCK", MatchAny, "IN") ||
 			 Matches4("LOCK", "TABLE", MatchAny, "IN"))
 		COMPLETE_WITH_LIST8("ACCESS SHARE MODE",
 							"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
@@ -3014,25 +2708,25 @@ psql_completion(const char *text, int start, int end)
 							"EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE");
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
-	else if (TailMatches1("NOTIFY"))
+	if (TailMatches1("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'", "");
 
 /* OPTIONS */
-	else if (TailMatches1("OPTIONS"))
+	if (TailMatches1("OPTIONS"))
 		COMPLETE_WITH_CONST("(");
 
 /* OWNER TO  - complete with available roles */
-	else if (TailMatches2("OWNER", "TO"))
+	if (TailMatches2("OWNER", "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* ORDER BY */
-	else if (TailMatches3("FROM", MatchAny, "ORDER"))
+	if (TailMatches3("FROM", MatchAny, "ORDER"))
 		COMPLETE_WITH_CONST("BY");
-	else if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
+	if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 /* PREPARE xx AS */
-	else if (Matches3("PREPARE", MatchAny, "AS"))
+	if (Matches3("PREPARE", MatchAny, "AS"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
 
 /*
@@ -3041,60 +2735,60 @@ psql_completion(const char *text, int start, int end)
  */
 
 /* REASSIGN OWNED BY xxx TO yyy */
-	else if (Matches1("REASSIGN"))
+	if (Matches1("REASSIGN"))
 		COMPLETE_WITH_CONST("OWNED BY");
-	else if (Matches2("REASSIGN", "OWNED"))
+	if (Matches2("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (Matches3("REASSIGN", "OWNED", "BY"))
+	if (Matches3("REASSIGN", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
-	else if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
+	if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
+	if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* REFRESH MATERIALIZED VIEW */
-	else if (Matches1("REFRESH"))
+	if (Matches1("REFRESH"))
 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
-	else if (Matches2("REFRESH", "MATERIALIZED"))
+	if (Matches2("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
+	if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   ADDLIST1("CONCURRENTLY"));
-	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
+	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
-	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
+	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
+	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
-	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
+	if (Matches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
-	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
+	if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
-	else if (Matches7("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
+	if (Matches7("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
 
 /* REINDEX */
-	else if (Matches1("REINDEX"))
+	if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
-	else if (Matches2("REINDEX", "TABLE"))
+	if (Matches2("REINDEX", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
-	else if (Matches2("REINDEX", "INDEX"))
+	if (Matches2("REINDEX", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
-	else if (Matches2("REINDEX", "SCHEMA"))
+	if (Matches2("REINDEX", "SCHEMA"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
-	else if (Matches2("REINDEX", "SYSTEM|DATABASE"))
+	if (Matches2("REINDEX", "SYSTEM|DATABASE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 
 /* SECURITY LABEL */
-	else if (Matches1("SECURITY"))
+	if (Matches1("SECURITY"))
 		COMPLETE_WITH_CONST("LABEL");
-	else if (Matches2("SECURITY", "LABEL"))
+	if (Matches2("SECURITY", "LABEL"))
 		COMPLETE_WITH_LIST2("ON", "FOR");
-	else if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
+	if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches3("SECURITY", "LABEL", "ON") ||
+	if (Matches3("SECURITY", "LABEL", "ON") ||
 			 Matches5("SECURITY", "LABEL", "FOR", MatchAny, "ON"))
 	{
 		static const char *const list_SECURITY_LABEL[] =
@@ -3105,7 +2799,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_SECURITY_LABEL);
 	}
-	else if (Matches5("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
+	if (Matches5("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("IS");
 
 /* SELECT */
@@ -3113,55 +2807,55 @@ psql_completion(const char *text, int start, int end)
 
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
-	else if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
+	if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars, "");
-	else if (Matches1("SHOW"))
+	if (Matches1("SHOW"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars, "");
 	/* Complete "SET TRANSACTION" */
-	else if (Matches2("SET|BEGIN|START", "TRANSACTION") ||
+	if (Matches2("SET|BEGIN|START", "TRANSACTION") ||
 			 Matches2("BEGIN", "WORK") ||
 		  Matches5("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
 		COMPLETE_WITH_LIST2("ISOLATION LEVEL", "READ");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
 			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
 		COMPLETE_WITH_CONST("LEVEL");
-	else if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL"))
+	if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL"))
 		COMPLETE_WITH_LIST3("READ", "REPEATABLE READ", "SERIALIZABLE");
-	else if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ"))
+	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ"))
 		COMPLETE_WITH_LIST2("UNCOMMITTED", "COMMITTED");
-	else if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE"))
+	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE"))
 		COMPLETE_WITH_CONST("READ");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ"))
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ"))
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
-	else if (Matches2("SET", "CONSTRAINTS"))
+	if (Matches2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema,
 								   ADDLIST1("ALL"));
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
-	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
+	if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
-	else if (Matches2("SET", "ROLE"))
+	if (Matches2("SET", "ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
-	else if (Matches2("SET", "SESSION"))
+	if (Matches2("SET", "SESSION"))
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
-	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
+	if (Matches3("SET", "SESSION", "AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
 							ADDLIST1("DEFAULT"));
 	/* Complete RESET SESSION with AUTHORIZATION */
-	else if (Matches2("RESET", "SESSION"))
+	if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
 	/* Complete SET <var> with "TO" */
-	else if (Matches2("SET", MatchAny))
+	if (Matches2("SET", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete ALTER DATABASE|FUNCTION|ROLE|USER ... SET <name> */
-	else if (HeadMatches2("ALTER", "DATABASE|FUNCTION|ROLE|USER") &&
+	if (HeadMatches2("ALTER", "DATABASE|FUNCTION|ROLE|USER") &&
 			 TailMatches2("SET", MatchAny))
 		COMPLETE_WITH_LIST2("FROM CURRENT", "TO");
 	/* Suggest possible variable values */
-	else if (TailMatches3("SET", MatchAny, "TO|="))
+	if (TailMatches3("SET", MatchAny, "TO|="))
 	{
 		/* special cased code for individual GUCs */
 		if (TailMatches2("DateStyle", "TO|="))
@@ -3174,7 +2868,7 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST(my_list);
 		}
-		else if (TailMatches2("search_path", "TO|="))
+		if (TailMatches2("search_path", "TO|="))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
 								" AND nspname not like 'pg\\_toast%%' "
 								" AND nspname not like 'pg\\_temp%%' ",
@@ -3182,103 +2876,108 @@ psql_completion(const char *text, int start, int end)
 		else
 		{
 			/* generic, type based, GUC support */
-			char	   *guctype = get_guctype(prev2_wd);
+			char		guctype[6];
 
-			if (guctype && strcmp(guctype, "enum") == 0)
+			/*
+			 * The value guc_guctype returned is 
+			 */
+			if (get_guctype(guctype, 6, prev2_wd))
 			{
-				char		querybuf[1024];
+				if (strcmp(guctype, "enum") == 0)
+				{
+					char		querybuf[1024];
 
-				snprintf(querybuf, sizeof(querybuf), Query_for_enum, prev2_wd);
-				COMPLETE_WITH_QUERY(querybuf, "");
+					snprintf(querybuf, sizeof(querybuf),
+							 Query_for_enum, prev2_wd);
+					COMPLETE_WITH_QUERY(querybuf, "");
+				}
+				if (strcmp(guctype, "bool") == 0)
+					COMPLETE_WITH_LIST9("on", "off", "true", "false",
+										"yes", "no", "1", "0", "DEFAULT");
 			}
-			else if (guctype && strcmp(guctype, "bool") == 0)
-				COMPLETE_WITH_LIST9("on", "off", "true", "false", "yes", "no",
-									"1", "0", "DEFAULT");
 			else
 				COMPLETE_WITH_CONST("DEFAULT");
-
-			if (guctype)
-				free(guctype);
 		}
 	}
 
 /* START TRANSACTION */
-	else if (Matches1("START"))
+	if (Matches1("START"))
 		COMPLETE_WITH_CONST("TRANSACTION");
 
 /* TABLE, but not TABLE embedded in other commands */
-	else if (Matches1("TABLE"))
+	if (Matches1("TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, "");
 
 /* TABLESAMPLE */
-	else if (TailMatches1("TABLESAMPLE"))
+	if (TailMatches1("TABLESAMPLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods, "");
-	else if (TailMatches2("TABLESAMPLE", MatchAny))
+	if (TailMatches2("TABLESAMPLE", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
-	else if (Matches1("TRUNCATE"))
+	if (Matches1("TRUNCATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 /* UNLISTEN */
-	else if (Matches1("UNLISTEN"))
+	if (Matches1("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'", "");
 
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
-	else if (TailMatches1("UPDATE"))
+	if (TailMatches1("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete UPDATE <table> with "SET" */
-	else if (TailMatches2("UPDATE", MatchAny))
+	if (TailMatches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
 	/* Complete UPDATE <table> SET with list of attributes */
-	else if (TailMatches3("UPDATE", MatchAny, "SET"))
+	if (TailMatches3("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* UPDATE <table> SET <attr> = */
-	else if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
+	if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if (Matches3("ALTER|CREATE", "USER", "MAPPING"))
+	if (Matches3("ALTER|CREATE", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
-	else if (Matches3("DROP", "USER", "MAPPING"))
+	if (Matches3("DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR");
-	else if (HeadMatches3("DROP", "USER", "MAPPING") &&
-			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
-			 false) {} /* FALL THROUGH */
-	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
+	if (HeadMatches3("DROP", "USER", "MAPPING") &&
+		MidMatches2(3, "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+
+	if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
 							ADDLIST3("CURRENT_USER", "PUBLIC", "USER"));
-	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
+	if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, "");
-	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
+	if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("SERVER");
-	else if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
+	if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /*
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
-	else if (Matches1("VACUUM"))
+	if (Matches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 		   ADDLIST4("FULL", "FREEZE", "ANALYZE", "VERBOSE"));
-	else if (Matches2("VACUUM", "FULL|FREEZE"))
+	if (Matches2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 		   ADDLIST2("ANALYZE", "VERBOSE"));
-	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
+	if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 		   ADDLIST1("VERBOSE"));
-	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
+	if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 		   ADDLIST1("ANALYZE"));
-	else if (Matches2("VACUUM", "VERBOSE"))
+	if (Matches2("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 		   ADDLIST1("ANALYZE"));
-	else if (Matches2("VACUUM", "ANALYZE"))
+	if (Matches2("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 		   ADDLIST1("VERBOSE"));
-	else if (HeadMatches1("VACUUM"))
+	if (HeadMatches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 
 /* WITH [RECURSIVE] */
@@ -3287,110 +2986,110 @@ psql_completion(const char *text, int start, int end)
 	 * Only match when WITH is the first word, as WITH may appear in many
 	 * other contexts.
 	 */
-	else if (Matches1("WITH"))
+	if (Matches1("WITH"))
 		COMPLETE_WITH_CONST("RECURSIVE");
 
 /* ANALYZE */
 	/* Complete with list of tables */
-	else if (Matches1("ANALYZE"))
+	if (Matches1("ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, "");
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
-	else if (TailMatches2(MatchAny, "WHERE"))
+	if (TailMatches2(MatchAny, "WHERE"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
-	else if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
+	if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 
 /* ... JOIN ... */
-	else if (TailMatches1("JOIN"))
+	if (TailMatches1("JOIN"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
-	else if (TailMatchesCS1("\\?"))
+	if (TailMatchesCS1("\\?"))
 		COMPLETE_WITH_LIST_CS3("commands", "options", "variables");
-	else if (TailMatchesCS1("\\connect|\\c"))
+	if (TailMatchesCS1("\\connect|\\c"))
 	{
 		if (!recognized_connection_string(text))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 	}
-	else if (TailMatchesCS2("\\connect|\\c", MatchAny))
+	if (TailMatchesCS2("\\connect|\\c", MatchAny))
 	{
 		if (!recognized_connection_string(prev_wd))
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	}
-	else if (TailMatchesCS1("\\da*"))
+	if (TailMatchesCS1("\\da*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, "");
-	else if (TailMatchesCS1("\\db*"))
+	if (TailMatchesCS1("\\db*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
-	else if (TailMatchesCS1("\\dD*"))
+	if (TailMatchesCS1("\\dD*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, "");
-	else if (TailMatchesCS1("\\des*"))
+	if (TailMatchesCS1("\\des*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers, "");
-	else if (TailMatchesCS1("\\deu*"))
+	if (TailMatchesCS1("\\deu*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, "");
-	else if (TailMatchesCS1("\\dew*"))
+	if (TailMatchesCS1("\\dew*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, "");
-	else if (TailMatchesCS1("\\df*"))
+	if (TailMatchesCS1("\\df*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 
-	else if (TailMatchesCS1("\\dFd*"))
+	if (TailMatchesCS1("\\dFd*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries, "");
-	else if (TailMatchesCS1("\\dFp*"))
+	if (TailMatchesCS1("\\dFp*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers, "");
-	else if (TailMatchesCS1("\\dFt*"))
+	if (TailMatchesCS1("\\dFt*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates, "");
 	/* must be at end of \dF alternatives: */
-	else if (TailMatchesCS1("\\dF*"))
+	if (TailMatchesCS1("\\dF*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations, "");
 
-	else if (TailMatchesCS1("\\di*"))
+	if (TailMatchesCS1("\\di*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
-	else if (TailMatchesCS1("\\dL*"))
+	if (TailMatchesCS1("\\dL*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages, "");
-	else if (TailMatchesCS1("\\dn*"))
+	if (TailMatchesCS1("\\dn*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
-	else if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
+	if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
-	else if (TailMatchesCS1("\\ds*"))
+	if (TailMatchesCS1("\\ds*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
-	else if (TailMatchesCS1("\\dt*"))
+	if (TailMatchesCS1("\\dt*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
-	else if (TailMatchesCS1("\\dT*"))
+	if (TailMatchesCS1("\\dT*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, "");
-	else if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
+	if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
-	else if (TailMatchesCS1("\\dv*"))
+	if (TailMatchesCS1("\\dv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
-	else if (TailMatchesCS1("\\dx*"))
+	if (TailMatchesCS1("\\dx*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions, "");
-	else if (TailMatchesCS1("\\dm*"))
+	if (TailMatchesCS1("\\dm*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
-	else if (TailMatchesCS1("\\dE*"))
+	if (TailMatchesCS1("\\dE*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
-	else if (TailMatchesCS1("\\dy*"))
+	if (TailMatchesCS1("\\dy*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* must be at end of \d alternatives: */
-	else if (TailMatchesCS1("\\d*"))
+	if (TailMatchesCS1("\\d*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, "");
 
-	else if (TailMatchesCS1("\\ef"))
+	if (TailMatchesCS1("\\ef"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
-	else if (TailMatchesCS1("\\ev"))
+	if (TailMatchesCS1("\\ev"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 
-	else if (TailMatchesCS1("\\encoding"))
+	if (TailMatchesCS1("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings, "");
-	else if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
+	if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
 		COMPLETE_WITH_LIST(sql_commands);
-	else if (TailMatchesCS1("\\password"))
+	if (TailMatchesCS1("\\password"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
-	else if (TailMatchesCS1("\\pset"))
+	if (TailMatchesCS1("\\pset"))
 	{
 		static const char *const my_list[] =
 		{"border", "columns", "expanded", "fieldsep", "fieldsep_zero",
@@ -3401,7 +3100,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (TailMatchesCS2("\\pset", MatchAny))
+	if (TailMatchesCS2("\\pset", MatchAny))
 	{
 		if (TailMatchesCS1("format"))
 		{
@@ -3411,53 +3110,53 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (TailMatchesCS1("linestyle"))
+		if (TailMatchesCS1("linestyle"))
 			COMPLETE_WITH_LIST_CS3("ascii", "old-ascii", "unicode");
-		else if (TailMatchesCS1("unicode_border_linestyle|"
+		if (TailMatchesCS1("unicode_border_linestyle|"
 								"unicode_column_linestyle|"
 								"unicode_header_linestyle"))
 			COMPLETE_WITH_LIST_CS2("single", "double");
 	}
-	else if (TailMatchesCS1("\\unset"))
+	if (TailMatchesCS1("\\unset"))
 	{
-		matches = complete_from_variables(text, "", "", true);
+		return complete_from_variables(text, "", "", true);
 	}
-	else if (TailMatchesCS1("\\set"))
+	if (TailMatchesCS1("\\set"))
 	{
-		matches = complete_from_variables(text, "", "", false);
+		return complete_from_variables(text, "", "", false);
 	}
-	else if (TailMatchesCS2("\\set", MatchAny))
+	if (TailMatchesCS2("\\set", MatchAny))
 	{
 		if (TailMatchesCS1("AUTOCOMMIT|ON_ERROR_STOP|QUIET|"
 						   "SINGLELINE|SINGLESTEP"))
 			COMPLETE_WITH_LIST_CS2("on", "off");
-		else if (TailMatchesCS1("COMP_KEYWORD_CASE"))
+		if (TailMatchesCS1("COMP_KEYWORD_CASE"))
 			COMPLETE_WITH_LIST_CS4("lower", "upper",
 								   "preserve-lower", "preserve-upper");
-		else if (TailMatchesCS1("ECHO"))
+		if (TailMatchesCS1("ECHO"))
 			COMPLETE_WITH_LIST_CS4("errors", "queries", "all", "none");
-		else if (TailMatchesCS1("ECHO_HIDDEN"))
+		if (TailMatchesCS1("ECHO_HIDDEN"))
 			COMPLETE_WITH_LIST_CS3("noexec", "off", "on");
-		else if (TailMatchesCS1("HISTCONTROL"))
+		if (TailMatchesCS1("HISTCONTROL"))
 			COMPLETE_WITH_LIST_CS4("ignorespace", "ignoredups",
 								   "ignoreboth", "none");
-		else if (TailMatchesCS1("ON_ERROR_ROLLBACK"))
+		if (TailMatchesCS1("ON_ERROR_ROLLBACK"))
 			COMPLETE_WITH_LIST_CS3("on", "off", "interactive");
-		else if (TailMatchesCS1("SHOW_CONTEXT"))
+		if (TailMatchesCS1("SHOW_CONTEXT"))
 			COMPLETE_WITH_LIST_CS3("never", "errors", "always");
-		else if (TailMatchesCS1("VERBOSITY"))
+		if (TailMatchesCS1("VERBOSITY"))
 			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
 	}
-	else if (TailMatchesCS1("\\sf*"))
+	if (TailMatchesCS1("\\sf*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
-	else if (TailMatchesCS1("\\sv*"))
+	if (TailMatchesCS1("\\sv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
-	else if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
+	if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
 							"\\ir|\\include_relative|\\o|\\out|"
 							"\\s|\\w|\\write|\\lo_import"))
 	{
-		SET_COMP_CHARP("\\");
-		matches = completion_matches(text, complete_from_files);
+		SET_COMPLETION_CHARP("\\");
+		return completion_matches(text, complete_from_files);
 	}
 
 	/*
@@ -3465,41 +3164,9 @@ psql_completion(const char *text, int start, int end)
 	 * check if that was the previous word. If so, execute the query to get a
 	 * list of them.
 	 */
-	else
-	{
-		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
-
-		if (ent)
-		{
-			if (ent->query)
-				COMPLETE_WITH_QUERY(ent->query, "");
-			else if (ent->squery)
-				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, "");
-		}
-	}
-
-	/*
-	 * If we still don't have anything to match we have to fabricate some sort
-	 * of default list. If we were to just return NULL, readline automatically
-	 * attempts filename completion, and that's usually no good.
-	 */
-	if (matches == NULL)
-	{
-		COMPLETE_WITH_CONST("");
-#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
-		rl_completion_append_character = '\0';
-#endif
-	}
-
-	/* free storage */
-	free(previous_words);
-	free(words_buffer);
-
-	/* Return our Grand List O' Matches */
-	return matches;
+	COMPLETE_THING();
 }
 
-
 /*
  * GENERATOR FUNCTIONS
  *
@@ -4261,14 +3928,15 @@ get_previous_words(int point, char **buffer, int *nwords)
  * Returns NULL if the variable is unknown. Otherwise the returned string,
  * containing the type, has to be freed.
  */
-static char *
-get_guctype(const char *varname)
+static bool
+get_guctype(char *buf, size_t len, const char *varname)
 {
 	PQExpBufferData query_buffer;
 	char	   *e_varname;
 	PGresult   *result;
-	char	   *guctype = NULL;
+	bool		success = true;
 
+	memset(buf, 0, len);
 	e_varname = escape_string(varname);
 
 	initPQExpBuffer(&query_buffer);
@@ -4282,11 +3950,12 @@ get_guctype(const char *varname)
 	free(e_varname);
 
 	if (PQresultStatus(result) == PGRES_TUPLES_OK && PQntuples(result) > 0)
-		guctype = pg_strdup(PQgetvalue(result, 0, 0));
-
+		strncpy(buf, PQgetvalue(result, 0, 0), len - 1);
+	else
+		success = false;
 	PQclear(result);
 
-	return guctype;
+	return success;
 }
 
 /*
-- 
1.8.3.1

#35Robert Haas
robertmhaas@gmail.com
In reply to: Kyotaro HORIGUCHI (#34)
Re: IF (NOT) EXISTS in psql-completion

On Thu, Apr 7, 2016 at 8:19 AM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

Thank you for looking this and for the comment.

Since the end of this CF is quite soon and this seems in
uncommittable state, feel free to move this to the next CF if any
other patch with more priority.

Moved. It's appropriate to consider this "needs review" at this
point, I think so added to 2016-09 that way.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#36Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#26)
Re: IF (NOT) EXISTS in psql-completion

Hi

This patch needs rebase.

Regards

Pavel

#37Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Pavel Stehule (#36)
Re: IF (NOT) EXISTS in psql-completion

Hello,

At Sun, 4 Sep 2016 12:54:57 +0200, Pavel Stehule <pavel.stehule@gmail.com> wrote in <CAFj8pRDWjv8Ahd=71vHmCT=DU6eor+Hwudh30b2f8cPyYH0eug@mail.gmail.com>

This patch needs rebase.

Thank you. I'll rebase the following patch and repost as soon as
possible.

/messages/by-id/20160407.211917.147996130.horiguchi.kyotaro@lab.ntt.co.jp

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#38Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#37)
4 attachment(s)
Re: IF (NOT) EXISTS in psql-completion

Hello, this is the new version of this patch. Rebased on the
current master.

At Tue, 06 Sep 2016 13:06:51 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <20160906.130651.171572544.horiguchi.kyotaro@lab.ntt.co.jp>

Thank you. I'll rebase the following patch and repost as soon as
possible.

/messages/by-id/20160407.211917.147996130.horiguchi.kyotaro@lab.ntt.co.jp

This patch consists of the following files. Since these files are
splitted in strange criteria and order for historical reasons,
I'll reorganize this and post them later.

- 0001-Suggest-IF-NOT-EXISTS-for-tab-completion-of-psql.patch

Add suggestion of IF (NOT) EXISTS on master. This should be the
last patch in this patchset.

- 0002-Make-added-keywords-for-completion-queries-follow-to.patch

Current suggestion mechanism doesn't distinguish object names
and keywords, which should be differently handled in
determining letter cases. This patch fixes that.

- 0003-Make-COMPLETE_WITH_ATTR-to-accept-additional-keyword.patch

This patch apply the 0002 fix to COMPLET_WITH_ATTR.

- 0004-Refactoring-tab-complete-to-make-psql_completion-cod.patch

By Tom's suggestion, in order to modify previous_words in more
sane way, transforming the else-if sequence in psql_completion
into a simple if sequence.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

0001-Suggest-IF-NOT-EXISTS-for-tab-completion-of-psql.patchtext/x-patch; charset=us-asciiDownload
From c23641753c8c65cf838eaa75d570064412c6ab27 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 5 Feb 2016 16:50:35 +0900
Subject: [PATCH 1/4] Suggest IF (NOT) EXISTS for tab-completion of psql

This patch lets psql to suggest "IF (NOT) EXISTS". Addition to that,
since this patch introduces some mechanism for syntactical robustness,
it allows psql completion to omit some optional part on matching.
---
 src/bin/psql/tab-complete.c | 637 ++++++++++++++++++++++++++++++++++++--------
 1 file changed, 522 insertions(+), 115 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 019f75a..d957a35 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -656,6 +656,10 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "   FROM pg_catalog.pg_roles "\
 "  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"
 
+#define Query_for_list_of_rules \
+"SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules "\
+" WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"
+
 #define Query_for_list_of_grant_roles \
 " SELECT pg_catalog.quote_ident(rolname) "\
 "   FROM pg_catalog.pg_roles "\
@@ -763,6 +767,11 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "SELECT pg_catalog.quote_ident(tmplname) FROM pg_catalog.pg_ts_template "\
 " WHERE substring(pg_catalog.quote_ident(tmplname),1,%d)='%s'"
 
+#define Query_for_list_of_triggers \
+"SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger "\
+" WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s' AND "\
+"       NOT tgisinternal"
+
 #define Query_for_list_of_fdws \
 " SELECT pg_catalog.quote_ident(fdwname) "\
 "   FROM pg_catalog.pg_foreign_data_wrapper "\
@@ -914,7 +923,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
 	{"POLICY", NULL, NULL},
 	{"ROLE", Query_for_list_of_roles},
-	{"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"},
+	{"RULE", Query_for_list_of_rules},
 	{"SCHEMA", Query_for_list_of_schemas},
 	{"SEQUENCE", NULL, &Query_for_list_of_sequences},
 	{"SERVER", Query_for_list_of_servers},
@@ -923,7 +932,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"TEMP", NULL, NULL, THING_NO_DROP},		/* for CREATE TEMP TABLE ... */
 	{"TEMPLATE", Query_for_list_of_ts_templates, NULL, THING_NO_SHOW},
 	{"TEXT SEARCH", NULL, NULL},
-	{"TRIGGER", "SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s' AND NOT tgisinternal"},
+	{"TRIGGER", Query_for_list_of_triggers},
 	{"TYPE", NULL, &Query_for_list_of_datatypes},
 	{"UNIQUE", NULL, NULL, THING_NO_DROP},		/* for CREATE UNIQUE INDEX ... */
 	{"UNLOGGED", NULL, NULL, THING_NO_DROP},	/* for CREATE UNLOGGED TABLE
@@ -952,6 +961,7 @@ static char **complete_from_variables(const char *text,
 					const char *prefix, const char *suffix, bool need_value);
 static char *complete_from_files(const char *text, int state);
 
+static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
@@ -960,6 +970,7 @@ static char **get_previous_words(int point, char **buffer, int *nwords);
 
 static char *get_guctype(const char *varname);
 
+static const pgsql_thing_t *find_thing_entry(char *word);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -1115,6 +1126,9 @@ psql_completion(const char *text, int start, int end)
 	/* The number of words found on the input line. */
 	int			previous_words_count;
 
+	/* The number of prefixing words to be ignored */
+	int			head_shift = 0;
+
 	/*
 	 * For compactness, we use these macros to reference previous_words[].
 	 * Caution: do not access a previous_words[] entry without having checked
@@ -1132,31 +1146,73 @@ psql_completion(const char *text, int start, int end)
 #define prev8_wd  (previous_words[7])
 #define prev9_wd  (previous_words[8])
 
+	/* Move the position of the beginning word for matching macros.  */
+#define HEADSHIFT(n) \
+	((head_shift += n) || true)
+
+	/* Return the number of stored words counting head shift */
+#define WORD_COUNT() (previous_words_count - head_shift)
+
+	/* Return the true index in previous_words for index from the beginning */
+#define HEAD_INDEX(n) \
+	(previous_words_count - head_shift - (n))
+
+	/*
+	 * remove n words from current shifted position, see MidMatchAndRevmove2
+	 * for the reason for the return value
+	 */
+#define COLLAPSE(n) \
+	((memmove(previous_words + HEAD_INDEX(n), previous_words + HEAD_INDEX(0), \
+			 sizeof(char *) * head_shift), \
+	 previous_words_count -= (n)) && false)
+
+	/*
+	 * Find the position the specified word occurs last and shift to there.
+	 * This is used to ignore the words before there.
+	 */
+#define SHIFT_TO_LAST1(p1) \
+	(HEADSHIFT(find_last_index_of(p1, previous_words, previous_words_count))|| \
+	 true)
+
+	/*
+	 * Remove the specified words if they match from the sth word in
+	 * previous_words. This is a bit tricky, COLLAPSE is skipped when
+	 * HeadMatches failed but the last HEADSHIFT anyway should be done.
+	 */
+#define MidMatchAndRemove1(s, p1) \
+	((HEADSHIFT(s) && HeadMatches1(p1) && COLLAPSE(1)) || HEADSHIFT(-s))
+
+#define MidMatchAndRemove2(s, p1, p2) \
+	((HEADSHIFT(s) && HeadMatches2(p1, p2) && COLLAPSE(2)) || HEADSHIFT(-s))
+
+#define MidMatchAndRemove3(s, p1, p2, p3)									\
+	((HEADSHIFT(s) && HeadMatches3(p1, p2, p3) && COLLAPSE(3)) || HEADSHIFT(-s))
+
 	/* Macros for matching the last N words before point, case-insensitively. */
 #define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches(p1, prev_wd))
 
 #define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd))
 
 #define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
+	(WORD_COUNT() >= 3 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd))
 
 #define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
+	(WORD_COUNT() >= 4 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
 	 word_matches(p4, prev4_wd))
 
 #define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
+	(WORD_COUNT() >= 5 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1164,7 +1220,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p5, prev5_wd))
 
 #define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
+	(WORD_COUNT() >= 6 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1173,7 +1229,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p6, prev6_wd))
 
 #define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
+	(WORD_COUNT() >= 7 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1183,7 +1239,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p7, prev7_wd))
 
 #define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
+	(WORD_COUNT() >= 8 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1194,7 +1250,7 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p8, prev8_wd))
 
 #define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
+	(WORD_COUNT() >= 9 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -1207,43 +1263,43 @@ psql_completion(const char *text, int start, int end)
 
 	/* Macros for matching the last N words before point, case-sensitively. */
 #define TailMatchesCS1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches_cs(p1, prev_wd))
 #define TailMatchesCS2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches_cs(p1, prev_wd) && \
 	 word_matches_cs(p2, prev2_wd))
 
 	/*
-	 * Macros for matching N words beginning at the start of the line,
+	 * Macros for matching N words exactly to the line,
 	 * case-insensitively.
 	 */
 #define Matches1(p1) \
-	(previous_words_count == 1 && \
+	(WORD_COUNT() == 1 && \
 	 TailMatches1(p1))
 #define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
+	(WORD_COUNT() == 2 && \
 	 TailMatches2(p1, p2))
 #define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
+	(WORD_COUNT() == 3 && \
 	 TailMatches3(p1, p2, p3))
 #define Matches4(p1, p2, p3, p4) \
-	(previous_words_count == 4 && \
+	(WORD_COUNT() == 4 && \
 	 TailMatches4(p1, p2, p3, p4))
 #define Matches5(p1, p2, p3, p4, p5) \
-	(previous_words_count == 5 && \
+	(WORD_COUNT() == 5 && \
 	 TailMatches5(p1, p2, p3, p4, p5))
 #define Matches6(p1, p2, p3, p4, p5, p6) \
-	(previous_words_count == 6 && \
+	(WORD_COUNT() == 6 && \
 	 TailMatches6(p1, p2, p3, p4, p5, p6))
 #define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(previous_words_count == 7 && \
+	(WORD_COUNT() == 7 && \
 	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
 #define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(previous_words_count == 8 && \
+	(WORD_COUNT() == 8 && \
 	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
 #define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(previous_words_count == 9 && \
+	(WORD_COUNT() == 9 && \
 	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
 
 	/*
@@ -1251,19 +1307,53 @@ psql_completion(const char *text, int start, int end)
 	 * what is after them, case-insensitively.
 	 */
 #define HeadMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]))
+	(HEAD_INDEX(1) >=0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]))
 
 #define HeadMatches2(p1, p2) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]))
+	(HEAD_INDEX(2) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]))
 
 #define HeadMatches3(p1, p2, p3) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]) && \
-	 word_matches(p3, previous_words[previous_words_count - 3]))
+	(HEAD_INDEX(3) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]))
+
+#define HeadMatches4(p1, p2, p3, p4) \
+	(HEAD_INDEX(4) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]))
+
+#define HeadMatches5(p1, p2, p3, p4, p5) \
+	(HEAD_INDEX(5) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]))
+
+#define HeadMatches6(p1, p2, p3, p4, p5, p6)		\
+	(HEAD_INDEX(6) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]) && \
+	 word_matches(p6, previous_words[HEAD_INDEX(6)]))
+
+#define HeadMatches7(p1, p2, p3, p4, p5, p6, p7)	\
+	(HEAD_INDEX(7) >= 0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
+	 word_matches(p5, previous_words[HEAD_INDEX(5)]) && \
+	 word_matches(p6, previous_words[HEAD_INDEX(6)]) && \
+	 word_matches(p7, previous_words[HEAD_INDEX(7)]))
 
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
@@ -1336,9 +1426,16 @@ psql_completion(const char *text, int start, int end)
 	else if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
+	/*
+	 * If this is in CREATE SCHEMA, seek to the last CREATE and regard it as
+	 * current command to complete.
+	 */
+	else if (HeadMatches2("CREATE", "SCHEMA") &&
+			 SHIFT_TO_LAST1("CREATE") &&
+			 false) {} /* FALL THROUGH */
 /* CREATE */
 	/* complete with something you can create */
-	else if (TailMatches1("CREATE"))
+	else if (Matches1("CREATE"))
 		matches = completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
@@ -1351,7 +1448,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE */
 	else if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
+	/* ALTER TABLE after removing optional words IF EXISTS*/
+	else if (HeadMatches2("ALTER", "TABLE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("ALTER", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER something */
 	else if (Matches1("ALTER"))
@@ -1443,6 +1546,17 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
+	/* ALTER FOREIGN TABLE */
+	else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
+								   " UNION SELECT 'IF EXISTS'");
+
+	/* ALTER|DROP FOREIGN TABLE after removing optinal words IF EXISTS */
+	else if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+
 	/* ALTER FOREIGN TABLE <name> */
 	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
@@ -1454,10 +1568,21 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_FOREIGN_TABLE);
 	}
 
+	/* ALTER FOREIGN TABLE xxx RENAME */
+	else if (Matches5("ALTER", "FOREIGN", "TABLE", MatchAny, "RENAME"))
+		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'TO'");
+
 	/* ALTER INDEX */
 	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
+	/* ALTER INDEX after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "INDEX") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("ALTER", "INDEX"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+
 	/* ALTER INDEX <name> */
 	else if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
@@ -1486,8 +1611,15 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER MATERIALIZED VIEW */
 	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   "UNION SELECT 'IF EXISTS'"
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
+	/* ALTER MATERIALIZED VIEW with name after removing optional words */
+	else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("ALTER", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+
 	/* ALTER USER,ROLE <name> */
 	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
@@ -1538,8 +1670,23 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER DOMAIN <sth> DROP */
 	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
-	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	/* ALTER DOMAIN <sth> RENAME|VALIDATE CONSTRAINT */
+	else if (Matches5("ALTER", "DOMAIN", MatchAny, "RENAME|VALIDATE", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+	}
+	/* ALTER DOMAIN <sth> DROP CONSTRAINT */
+	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type
+							"UNION SELECT 'IF EXISTS'");
+	}
+	/* Try the same match after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
@@ -1554,8 +1701,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER DOMAIN <sth> SET */
 	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
-	/* ALTER SEQUENCE <name> */
-	else if (Matches3("ALTER", "SEQUENCE", MatchAny))
+	else if (Matches2("ALTER", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
+								   " UNION SELECT 'IF EXISTS'");
+	/* ALTER SEQUENCE with name after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "SEQUENCE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1578,8 +1730,14 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER SYSTEM SET|RESET <name> */
 	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
-	/* ALTER VIEW <name> */
-	else if (Matches3("ALTER", "VIEW", MatchAny))
+	/* ALTER VIEW */
+	else if (Matches2("ALTER", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   "UNION SELECT 'IF EXISTS'");
+	/* ALTER VIEW <name> with subcommands after removing optional worlds */
+	else if (HeadMatches2("ALTER", "VIEW") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
@@ -1587,11 +1745,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
-	/* ALTER POLICY <name> */
+	/* ALTER POLICY */
 	else if (Matches2("ALTER", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
-	/* ALTER POLICY <name> ON */
-	else if (Matches3("ALTER", "POLICY", MatchAny))
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies
+							"UNION SELECT 'IF EXISTS'");
+	/* ALTER POLICY <name> with ON after removing optional words IF EXISTS */
+	else if (HeadMatches2("ALTER", "POLICY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
 	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
@@ -1715,8 +1876,10 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
-	/* ALTER TABLE xxx RENAME yyy */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
+	/* ALTER [FOREIGN] TABLE xxx RENAME yyy */
+	else if ((HeadMatches2("ALTER", "TABLE") ||
+			  HeadMatches3("ALTER", "FOREIGN", "TABLE")) &&
+			 TailMatches2("RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
@@ -1726,15 +1889,47 @@ psql_completion(const char *text, int start, int end)
 	/* If we have ALTER TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
+	/*  ALTER TABLE DROP COLUMN may take IF EXISTS */
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+	/* ALTER TABLE <name> with ADD/ALTER/DROP after removing optional words */
+	else if (HeadMatches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP") &&
+			 MidMatchAndRemove1(4, "COLUMN") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 Matches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP"))
+		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
-	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
+	 * If we have ALTER TABLE <sth> ALTER|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME|VALIDATE", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+	}
+	/*
+	 * If we have ALTER TABLE <sth> DROP CONSTRAINT,
+	 * provide list of constraints
+	 */
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table
+							"UNION SELECT 'IF EXISTS'");
+	}
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
@@ -1857,8 +2052,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 	else if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING",
-							"DROP MAPPING FOR",
-							"OWNER TO", "RENAME TO", "SET SCHEMA");
+							"DROP MAPPING",	"OWNER TO", "RENAME TO", "SET SCHEMA");
+	else if (Matches7("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny, "DROP", "MAPPING"))
+		COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR");
+	/* Remove optional words IF EXISTS */
+	else if (HeadMatches7("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny, "DROP", "MAPPING") &&
+			 MidMatchAndRemove2(7, "IF", "EXISTS") &&
+			 false) {} /* Nothing to do for now */
 
 	/* complete ALTER TYPE <foo> with actions */
 	else if (Matches3("ALTER", "TYPE", MatchAny))
@@ -1868,6 +2068,12 @@ psql_completion(const char *text, int start, int end)
 	/* complete ALTER TYPE <foo> ADD with actions */
 	else if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
+	else if (Matches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE"))
+		COMPLETE_WITH_LIST2("IF NOT EXISTS", "");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE") &&
+			 MidMatchAndRemove3(5, "IF", "NOT", "EXISTS") &&
+			 false) {} /* Nothing to do for now */
 	/* ALTER TYPE <foo> RENAME	*/
 	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "TO");
@@ -1879,10 +2085,15 @@ psql_completion(const char *text, int start, int end)
 	 * If we have ALTER TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list
 	 * of attributes
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
+	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
+	else if (Matches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE"))
+		COMPLETE_WITH_ATTR(prev3_wd, " UNION SELECT 'IF EXISTS'");
+	/* Remove optional words IF EXISTS */
+	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE") &&
+			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
+			 Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* complete ALTER GROUP <foo> */
 	else if (Matches3("ALTER", "GROUP", MatchAny))
@@ -2031,6 +2242,12 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	else if (Matches2("CREATE", "EXTENSION"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions
+							" UNION SELECT 'IF NOT EXISTS'");
+	/* Try the same after removing optional words IF NOT EXISTS */
+	else if (HeadMatches2("CREATE", "EXTENSION") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
 	else if (Matches3("CREATE", "EXTENSION", MatchAny))
@@ -2046,6 +2263,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
+	/* CREATE FOREIGN TABLE */
+	else if (Matches3("CREATE", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
+			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
@@ -2055,29 +2280,37 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
-	/*
-	 * If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY", and
-	 * existing indexes
-	 */
-	else if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	/* Remove optional word UNIQUE from CREATE UNIQUE INDEX */
+	else if (HeadMatches3("CREATE", MatchAny, "INDEX") &&
+			 MidMatchAndRemove1(1, "UNIQUE") &&
+			 false) {} /* FALL THROUGH */
+	/* If we have CREATE INDEX, then add "ON", "CONCURRENTLY" or IF NOT EXISTS,
+	   and existing indexes */
+	else if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
-								   " UNION SELECT 'CONCURRENTLY'");
-	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	else if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
-			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-
-	/*
-	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
-	 * indexes
-	 */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+								   " UNION SELECT 'CONCURRENTLY'"
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and
+	 * existing indexes */
+	else if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF NOT EXISTS'"
 								   " UNION SELECT 'ON'");
-	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
-			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
+
+	/* Remove optional words "CONCURRENTLY",  "IF NOT EXISTS" */
+	else if (HeadMatches2("CREATE", "INDEX") &&
+			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
+	/* Complete CREATE INDEX [<name>] ON with a list of tables */
+	else if (Matches4("CREATE", "INDEX", MatchAny, "ON") ||
+			 Matches3("CREATE", "INDEX", "ON"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+
+	/* Complete CREATE INDEX <sth> with "ON" */
+	else if (Matches3("CREATE", "INDEX", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/*
@@ -2085,17 +2318,16 @@ psql_completion(const char *text, int start, int end)
 	 * should really be in parens)
 	 */
 	else if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-			 TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+			 TailMatches3("INDEX", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-			 TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
+	else if (Matches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
+			 Matches4("INDEX", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* same if you put in USING */
 	else if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	else if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
-			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
+	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 	else if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
@@ -2149,27 +2381,56 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-/* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	else if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
-			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
+/* Remove optional words TEMPORARY/TEMP */
+	else if (HeadMatches3("CREATE", MatchAny, "SEQUENCE") &&
+			 MidMatchAndRemove1(1, "TEMP|TEMPORARY") &&
+			 false) {} /* FALL THROUGH */
+/* CREATE SEQUENCE */
+	else if(Matches2("CREATE", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	else if(HeadMatches2("CREATE", "SEQUENCE") &&
+			MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			Matches3("CREATE", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	else if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
-		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
+	else if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
 	else if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
-/* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE SCHEMA <name> */
+	else if (Matches2("CREATE", "SCHEMA"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+							" UNION SELECT 'IF NOT EXISTS'");
+	/* Remove optional words IF NOT EXISTS */
+	else if (HeadMatches2("CREATE", "SCHEMA") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH*/
+
+/* CREATE TABLE  */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	else if (Matches2("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	else if (TailMatches2("CREATE", "UNLOGGED"))
+	else if (Matches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
+	/* CREATE TABLE with name after removing optional words */
+	else if (HeadMatches3("CREATE", MatchAny, "TABLE") &&
+			 MidMatchAndRemove1(1, "TEMP|TEMPORARY|UNLOGGED") &&
+			 false) {} /* FALL THROUGH*/
+	else if (Matches2("CREATE", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+								   " UNION SELECT 'IF NOT EXISTS'");
+
+	/* Remove optional words here */
+	else if (HeadMatches2("CREATE", "TABLE") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
@@ -2183,18 +2444,18 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
-/* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	else if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	else if (Matches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	else if (Matches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	else if (Matches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+	else if (Matches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
 	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
@@ -2251,9 +2512,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
-/* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	/* Complete CREATE VIEW <name> with AS */
-	else if (TailMatches3("CREATE", "VIEW", MatchAny))
+/* CREATE VIEW  */
+	else if (Matches2("CREATE", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* CREATE VIEW <name> with AS after removing optional words */
+	else if (HeadMatches2("CREATE", "VIEW") &&
+			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
+			 Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
 	else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
@@ -2262,6 +2528,15 @@ psql_completion(const char *text, int start, int end)
 /* CREATE MATERIALIZED VIEW */
 	else if (Matches2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+	else if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   " UNION SELECT 'IF NOT EXISTS'");
+	/* Try the same after removing optional words IF NOT EXISTS. VIEW will be
+	 * completed afterwards */
+	else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
+			 Matches3("CREATE", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
 	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
@@ -2321,28 +2596,61 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
+	else if (Matches2("DROP", "AGGREGATE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates,
+								   " UNION SELECT 'IF EXISTS'");
+	else if (Matches2("DROP", "FUNCTION"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
 	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
+	else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 
 	/* DROP INDEX */
 	else if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF EXISTS'"
 								   " UNION SELECT 'CONCURRENTLY'");
 	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches3("DROP", "INDEX", MatchAny))
-		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	else if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */
+	else if (HeadMatches2("DROP", "INDEX") &&
+			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
 	else if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+	else if (Matches2("DROP", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Remove optional words IF EXISTS  */
+	else if (HeadMatches2("DROP", "VIEW") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   " UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
 	/* DROP OWNED BY */
@@ -2355,7 +2663,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (Matches3("DROP", "TRIGGER", MatchAny))
+	else if (Matches2("DROP", "TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_triggers
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "TRIGGER") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
@@ -2375,15 +2689,27 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	else if (Matches3("DROP", "EVENT", "TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers
+							" UNION SELECT 'IF EXISTS'");
+	/* Trye the same after removing optional words IF EXISTS */
+	else if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
-	/* DROP POLICY <name>  */
+	/* DROP POLICY */
 	else if (Matches2("DROP", "POLICY"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies
+							" UNION SELECT 'IF EXISTS'");
+	/* Try the same after after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "POLICY") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
-	/* DROP POLICY <name> ON */
+	/* DROP POLICY <name> */
 	else if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	/* DROP POLICY <name> ON <table> */
+	/* DROP POLICY <name> ON */
 	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
@@ -2391,7 +2717,13 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* DROP RULE */
-	else if (Matches3("DROP", "RULE", MatchAny))
+	else if (Matches2("DROP", "RULE"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_rules
+							"UNION SELECT 'IF EXISTS'");
+	/* DROP RULE <name>, after removing optional words IF EXISTS */
+	else if (HeadMatches2("DROP", "RULE") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 Matches3("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
@@ -2401,6 +2733,52 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
+	/* IF EXISTS processing for DROP everything else */
+	else if (Matches2("DROP",
+					  "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|"
+					  "GROUP|LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|"
+					  "SERVER|TABLE|TABLESPACE|TYPE|USER") ||
+			 Matches4("DROP", "TEXT", "SEARCH",
+					  "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE"))
+
+	{
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
+		char *addition = " UNION SELECT 'IF EXISTS'";
+
+		if (ent)
+		{
+			/* Completing USER needs special treat */
+			if (pg_strcasecmp(prev_wd, "USER") == 0)
+			{
+				COMPLETE_WITH_QUERY(
+					ADDLIST2(Query_for_list_of_roles, "MAPPING", "IF EXISTS"));
+			}
+			else if (ent->query)
+			{
+				char *buf = pg_malloc(strlen(ent->query) +
+									  strlen(addition) + 1);
+				strcpy(buf, ent->query);
+				strcat(buf, addition);
+				COMPLETE_WITH_QUERY(buf);
+				free(buf);
+			}
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery,
+										   " UNION SELECT 'IF EXISTS'");
+		}
+	}
+	/* Remove optional IF EXISTS from DROP */
+	else if (HeadMatches2("DROP",
+						  "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|GROUP|"
+						  "LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|SERVER|"
+						  "TABLE|TABLESPACE|TYPE|USER") &&
+			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+	else if (HeadMatches4("DROP", "TEXT", "SEARCH",
+						  "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE") &&
+			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
+
 /* EXECUTE */
 	else if (Matches1("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
@@ -2447,8 +2825,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /* FOREIGN TABLE */
-	else if (TailMatches2("FOREIGN", "TABLE") &&
-			 !TailMatches3("CREATE", MatchAny, MatchAny))
+	else if (TailMatches2("FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* FOREIGN SERVER */
@@ -2900,8 +3277,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
+	else if (Matches3("ALTER|CREATE", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
+	else if (Matches3("DROP", "USER", "MAPPING"))
+		COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR");
+	else if (HeadMatches3("DROP", "USER", "MAPPING") &&
+			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
+			 false) {} /* FALL THROUGH */
 	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
@@ -3133,19 +3515,14 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else
 	{
-		int			i;
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
 
-		for (i = 0; words_after_create[i].name; i++)
+		if (ent)
 		{
-			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
-			{
-				if (words_after_create[i].query)
-					COMPLETE_WITH_QUERY(words_after_create[i].query);
-				else if (words_after_create[i].squery)
-					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
-											   NULL);
-				break;
-			}
+			if (ent->query)
+				COMPLETE_WITH_QUERY(ent->query);
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, NULL);
 		}
 	}
 
@@ -3674,6 +4051,18 @@ complete_from_files(const char *text, int state)
 
 /* HELPER FUNCTIONS */
 
+/*
+ * Return the index (reverse to the index of previous_words) of the tailmost
+ * (topmost in the array) appearance of w.
+ */
+static int
+find_last_index_of(char *w, char **previous_words, int len)
+{
+	int i;
+
+	for (i = 0 ; i < len && !word_matches(w, previous_words[i]) ; i++);
+	return i < len ? (len - i - 1) : 0;
+}
 
 /*
  * Make a pg_strdup copy of s and convert the case according to
@@ -3920,6 +4309,24 @@ get_guctype(const char *varname)
 	return guctype;
 }
 
+/*
+ * Finds the entry in words_after_create[] that matches the word.
+ * NULL if not found.
+ */
+static const pgsql_thing_t *
+find_thing_entry(char *word)
+{
+	int			i;
+
+	for (i = 0; words_after_create[i].name; i++)
+	{
+		if (pg_strcasecmp(word, words_after_create[i].name) == 0)
+			return words_after_create + i;
+	}
+
+	return NULL;
+}
+
 #ifdef NOT_USED
 
 /*
-- 
2.9.2

0002-Make-added-keywords-for-completion-queries-follow-to.patchtext/x-patch; charset=us-asciiDownload
From 116c23b5326b937a4c39ae47c43b61aa74a7da9c Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Tue, 29 Mar 2016 19:01:13 +0900
Subject: [PATCH 2/4] Make added keywords for completion queries follow to
 input.

Currently some keyword shown along with database objects are always in
upper case. This patch changes the behavior so that the case of the
additional keywords follow the setting of COMP_KEYWORD_CASE.
Only COMPLETE_WITH_QUERY/COMPLETE_WITH_SCHEMA_QUERY are fixed.
---
 src/bin/psql/tab-complete.c | 235 +++++++++++++++++++++++++-------------------
 1 file changed, 134 insertions(+), 101 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d957a35..c530fcb 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -312,6 +312,18 @@ do { \
 	COMPLETE_WITH_LIST_CS(list); \
 } while (0)
 
+#define ADDLIST1(p, s1) additional_kw_query(p, text, 1, s1)
+#define ADDLIST2(p, s1, s2) additional_kw_query(p, text, 2, s1, s2)
+#define ADDLIST3(p, s1, s2, s3) additional_kw_query(p, text, 3, s1, s2, s3)
+#define ADDLIST4(p, s1, s2, s3, s4) \
+	additional_kw_query(p, text, 4, s1, s2, s3, s4)
+#define ADDLIST13(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
+	additional_kw_query(p, text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13)
+#define ADDLIST15(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
+	additional_kw_query(p, text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13, s14, s15)
+
 /*
  * Assembly instructions for schema queries
  */
@@ -963,6 +975,7 @@ static char *complete_from_files(const char *text, int state);
 
 static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
+static char *additional_kw_query(char *prefix, const char *ref, int n, ...);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
 
@@ -1448,8 +1461,8 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE */
 	else if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   "UNION SELECT 'IF EXISTS'"
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+			ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
+
 	/* ALTER TABLE after removing optional words IF EXISTS*/
 	else if (HeadMatches2("ALTER", "TABLE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1549,7 +1562,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER FOREIGN TABLE */
 	else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 
 	/* ALTER|DROP FOREIGN TABLE after removing optinal words IF EXISTS */
 	else if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
@@ -1575,8 +1588,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER INDEX */
 	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   "UNION SELECT 'IF EXISTS'"
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+			   ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
 	/* ALTER INDEX after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "INDEX") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1611,8 +1623,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER MATERIALIZED VIEW */
 	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   "UNION SELECT 'IF EXISTS'"
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+			   ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
 
 	/* ALTER MATERIALIZED VIEW with name after removing optional words */
 	else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
@@ -1680,8 +1691,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_constraint_of_type, "IF EXISTS"));
 	}
 	/* Try the same match after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
@@ -1703,7 +1714,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	else if (Matches2("ALTER", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* ALTER SEQUENCE with name after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "SEQUENCE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1733,7 +1744,7 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER VIEW */
 	else if (Matches2("ALTER", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   "UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* ALTER VIEW <name> with subcommands after removing optional worlds */
 	else if (HeadMatches2("ALTER", "VIEW") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1747,8 +1758,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER POLICY */
 	else if (Matches2("ALTER", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_policies, "IF EXISTS"));
 	/* ALTER POLICY <name> with ON after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1923,8 +1934,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_constraint_of_table, "IF EXISTS"));
 	}
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
@@ -2122,7 +2133,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
 	else if (Matches1("CLUSTER"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
+								   ADDLIST1("", "VERBOSE"));
 	else if (Matches2("CLUSTER", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 	/* If we have CLUSTER <sth>, then add "USING" */
@@ -2187,7 +2199,7 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION ALL SELECT '('");
+								   ADDLIST1("", "("));
 	/* If we have COPY BINARY, complete with list of tables */
 	else if (Matches2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
@@ -2242,8 +2254,8 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	else if (Matches2("CREATE", "EXTENSION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions
-							" UNION SELECT 'IF NOT EXISTS'");
+		COMPLETE_WITH_QUERY(ADDLIST1(Query_for_list_of_available_extensions,
+									 "IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS */
 	else if (HeadMatches2("CREATE", "EXTENSION") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2266,7 +2278,7 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN TABLE */
 	else if (Matches3("CREATE", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
 			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
@@ -2288,15 +2300,12 @@ psql_completion(const char *text, int start, int end)
 	   and existing indexes */
 	else if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'ON'"
-								   " UNION SELECT 'CONCURRENTLY'"
-								   " UNION SELECT 'IF NOT EXISTS'");
+				   ADDLIST3("", "ON", "CONCURRENTLY", "IF NOT EXISTS"));
 	/* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and
 	 * existing indexes */
 	else if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'IF NOT EXISTS'"
-								   " UNION SELECT 'ON'");
+								   ADDLIST2("", "IF NOT EXISTS", "ON"));
 
 	/* Remove optional words "CONCURRENTLY",  "IF NOT EXISTS" */
 	else if (HeadMatches2("CREATE", "INDEX") &&
@@ -2388,7 +2397,7 @@ psql_completion(const char *text, int start, int end)
 /* CREATE SEQUENCE */
 	else if(Matches2("CREATE", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 	else if(HeadMatches2("CREATE", "SEQUENCE") &&
 			MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
 			Matches3("CREATE", "SEQUENCE", MatchAny))
@@ -2403,8 +2412,8 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE SCHEMA <name> */
 	else if (Matches2("CREATE", "SCHEMA"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas
-							" UNION SELECT 'IF NOT EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_schemas, "IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches2("CREATE", "SCHEMA") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2424,7 +2433,7 @@ psql_completion(const char *text, int start, int end)
 			 false) {} /* FALL THROUGH*/
 	else if (Matches2("CREATE", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 
 	/* Remove optional words here */
 	else if (HeadMatches2("CREATE", "TABLE") &&
@@ -2515,7 +2524,7 @@ psql_completion(const char *text, int start, int end)
 /* CREATE VIEW  */
 	else if (Matches2("CREATE", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 	/* CREATE VIEW <name> with AS after removing optional words */
 	else if (HeadMatches2("CREATE", "VIEW") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2530,7 +2539,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'IF NOT EXISTS'");
+								   ADDLIST1("", "IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS. VIEW will be
 	 * completed afterwards */
 	else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
@@ -2598,10 +2607,10 @@ psql_completion(const char *text, int start, int end)
 	/* help completing some of the variants */
 	else if (Matches2("DROP", "AGGREGATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	else if (Matches2("DROP", "FUNCTION"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2612,8 +2621,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 	else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_fdws, "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&
 			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
@@ -2622,11 +2631,10 @@ psql_completion(const char *text, int start, int end)
 	/* DROP INDEX */
 	else if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'IF EXISTS'"
-								   " UNION SELECT 'CONCURRENTLY'");
+								   ADDLIST2("", "IF EXISTS","CONCURRENTLY"));
 	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */
 	else if (HeadMatches2("DROP", "INDEX") &&
 			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
@@ -2639,14 +2647,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches2("DROP", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Remove optional words IF EXISTS  */
 	else if (HeadMatches2("DROP", "VIEW") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 false) {} /* FALL THROUGH */
 	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'IF EXISTS'");
+								   ADDLIST1("", "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
@@ -2664,8 +2672,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* DROP TRIGGER */
 	else if (Matches2("DROP", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_triggers
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_triggers, "IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "TRIGGER") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2689,8 +2697,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	else if (Matches3("DROP", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_event_triggers, "IF EXISTS"));
 	/* Trye the same after removing optional words IF EXISTS */
 	else if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
@@ -2699,8 +2707,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* DROP POLICY */
 	else if (Matches2("DROP", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies
-							" UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_policies, "IF EXISTS"));
 	/* Try the same after after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2718,8 +2726,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* DROP RULE */
 	else if (Matches2("DROP", "RULE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_rules
-							"UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_rules, "IF EXISTS"));
 	/* DROP RULE <name>, after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "RULE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2743,7 +2751,7 @@ psql_completion(const char *text, int start, int end)
 
 	{
 		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
-		char *addition = " UNION SELECT 'IF EXISTS'";
+		char *addition = ADDLIST1("", "IF EXISTS");
 
 		if (ent)
 		{
@@ -2764,7 +2772,7 @@ psql_completion(const char *text, int start, int end)
 			}
 			else if (ent->squery)
 				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery,
-										   " UNION SELECT 'IF EXISTS'");
+										   ADDLIST1("", "IF EXISTS"));
 		}
 	}
 	/* Remove optional IF EXISTS from DROP */
@@ -2835,20 +2843,11 @@ psql_completion(const char *text, int start, int end)
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
 	else if (TailMatches1("GRANT|REVOKE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'SELECT'"
-							" UNION SELECT 'INSERT'"
-							" UNION SELECT 'UPDATE'"
-							" UNION SELECT 'DELETE'"
-							" UNION SELECT 'TRUNCATE'"
-							" UNION SELECT 'REFERENCES'"
-							" UNION SELECT 'TRIGGER'"
-							" UNION SELECT 'CREATE'"
-							" UNION SELECT 'CONNECT'"
-							" UNION SELECT 'TEMPORARY'"
-							" UNION SELECT 'EXECUTE'"
-							" UNION SELECT 'USAGE'"
-							" UNION SELECT 'ALL'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST13(Query_for_list_of_roles,
+					  "SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
+					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
+					  "EXECUTE", "USAGE", "ALL"));
 
 	/*
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
@@ -2877,21 +2876,22 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
-								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
-								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
-								   " UNION SELECT 'ALL TABLES IN SCHEMA'"
-								   " UNION SELECT 'DATABASE'"
-								   " UNION SELECT 'DOMAIN'"
-								   " UNION SELECT 'FOREIGN DATA WRAPPER'"
-								   " UNION SELECT 'FOREIGN SERVER'"
-								   " UNION SELECT 'FUNCTION'"
-								   " UNION SELECT 'LANGUAGE'"
-								   " UNION SELECT 'LARGE OBJECT'"
-								   " UNION SELECT 'SCHEMA'"
-								   " UNION SELECT 'SEQUENCE'"
-								   " UNION SELECT 'TABLE'"
-								   " UNION SELECT 'TABLESPACE'"
-								   " UNION SELECT 'TYPE'");
+			   ADDLIST15("",
+						 "ALL FUNCTIONS IN SCHEMA",
+						 "ALL SEQUENCES IN SCHEMA",
+						 "ALL TABLES IN SCHEMA",
+						 "DATABASE",
+						 "DOMAIN",
+						 "FOREIGN DATA WRAPPER",
+						 "FOREIGN SERVER",
+						 "FUNCTION",
+						 "LANGUAGE",
+						 "LARGE OBJECT",
+						 "SCHEMA",
+						 "SEQUENCE",
+						 "TABLE",
+						 "TABLESPACE",
+						 "TYPE"));
 
 	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
@@ -3017,7 +3017,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete LOCK [TABLE] with a list of tables */
 	else if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION SELECT 'TABLE'");
+								   ADDLIST1("", "TABLE"));
 	else if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
@@ -3083,7 +3083,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'CONCURRENTLY'");
+								   ADDLIST1("", "CONCURRENTLY"));
 	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
@@ -3175,7 +3175,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
 	else if (Matches2("SET", "CONSTRAINTS"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema,
+								   ADDLIST1("", "ALL"));
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
 	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
@@ -3187,7 +3188,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
 	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST1(Query_for_list_of_roles, "DEFAULT"));
 	/* Complete RESET SESSION with AUTHORIZATION */
 	else if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
@@ -3213,10 +3215,11 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST(my_list);
 		}
 		else if (TailMatches2("search_path", "TO|="))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
-								" AND nspname not like 'pg\\_toast%%' "
-								" AND nspname not like 'pg\\_temp%%' "
-								" UNION SELECT 'DEFAULT' ");
+			COMPLETE_WITH_QUERY(
+				ADDLIST1(Query_for_list_of_schemas
+						 " AND nspname not like 'pg\\_toast%%' "
+						 " AND nspname not like 'pg\\_temp%%' ",
+						 "DEFAULT"));
 		else
 		{
 			/* generic, type based, GUC support */
@@ -3285,10 +3288,9 @@ psql_completion(const char *text, int start, int end)
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 false) {} /* FALL THROUGH */
 	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'CURRENT_USER'"
-							" UNION SELECT 'PUBLIC'"
-							" UNION SELECT 'USER'");
+		COMPLETE_WITH_QUERY(
+			ADDLIST3(Query_for_list_of_roles, 
+					 "CURRENT_USER", "PUBLIC", "USER"));
 	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
 	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
@@ -3302,26 +3304,22 @@ psql_completion(const char *text, int start, int end)
  */
 	else if (Matches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'FULL'"
-								   " UNION SELECT 'FREEZE'"
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST4("", "FULL", "FREEZE", "ANALYZE", "VERBOSE"));
 	else if (Matches2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST2("", "ANALYZE", "VERBOSE"));
 	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST1("", "VERBOSE"));
 	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+		   ADDLIST1("", "ANALYZE"));
 	else if (Matches2("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+		   ADDLIST1("", "ANALYZE"));
 	else if (Matches2("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+		   ADDLIST1("", "VERBOSE"));
 	else if (HeadMatches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
@@ -4094,6 +4092,41 @@ pg_strdup_keyword_case(const char *s, const char *ref)
 	return ret;
 }
 
+/* Construct codelet to append given keywords  */
+static char *
+additional_kw_query(char *prefix, const char *ref, int n, ...)
+{
+	va_list ap;
+	static PQExpBuffer qbuf = NULL;
+	int i;
+
+	if (qbuf == NULL)
+		qbuf = createPQExpBuffer();
+	else
+		resetPQExpBuffer(qbuf);
+
+	appendPQExpBufferStr(qbuf, prefix);
+
+	/* Construct an additional queriy to append keywords */
+	appendPQExpBufferStr(qbuf, " UNION SELECT * FROM (VALUES ");
+
+	va_start(ap, n);
+	for (i = 0 ; i < n ; i++)
+	{
+		char *item = pg_strdup_keyword_case(va_arg(ap, char *), ref);
+		if (i > 0) appendPQExpBufferChar(qbuf, ',');
+		appendPQExpBufferStr(qbuf, "('");
+		appendPQExpBufferStr(qbuf, item);
+		appendPQExpBufferStr(qbuf, "')");
+		pg_free(item);
+	}
+	va_end(ap);
+
+	appendPQExpBufferStr(qbuf, ") as x");
+
+	return qbuf->data;
+}
+
 
 /*
  * escape_string - Escape argument for use as string literal.
-- 
2.9.2

0003-Make-COMPLETE_WITH_ATTR-to-accept-additional-keyword.patchtext/x-patch; charset=us-asciiDownload
From f5a77cba0e550a122f355ca158833ae9d8e2690d Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 1 Apr 2016 17:01:42 +0900
Subject: [PATCH 3/4] Make COMPLETE_WITH_ATTR to accept additional keyword
 query.

So far the previous commit, COMPLETE_WITH_ATTR cannot accept the new
style of additional keyword list. This patch does the following
things.

1. Change completion_charp from const char * to PQExpBuffer.

2. Chnage COMPLETE_WITH_QUERY and COMPLETE_WITH_ATTR to accept
   an expression instead of string literal.

3. Replace all additional keyword lists in psql_copmletion with
   ADDLISTn() expression.

This leaves keywords contained in Query_for_list_of_grant_roles and
Query_for_enum, but it is the another problem.
---
 src/bin/psql/tab-complete.c | 486 ++++++++++++++++++++++----------------------
 1 file changed, 246 insertions(+), 240 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index c530fcb..832bcd7 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -125,7 +125,7 @@ static int	completion_max_records;
  * Communication variables set by COMPLETE_WITH_FOO macros and then used by
  * the completion callback functions.  Ugly but there is no better way.
  */
-static const char *completion_charp;	/* to pass a string */
+static PQExpBuffer completion_charp = NULL;		/* to pass a string */
 static const char *const * completion_charpp;	/* to pass a list of strings */
 static const char *completion_info_charp;		/* to pass a second string */
 static const char *completion_info_charp2;		/* to pass a third string */
@@ -143,16 +143,26 @@ static bool completion_case_sensitive;	/* completion is case sensitive */
  * 5) The list of attributes of the given table (possibly schema-qualified).
  * 6/ The list of arguments to the given function (possibly schema-qualified).
  */
-#define COMPLETE_WITH_QUERY(query) \
+#define APPEND_COMP_CHARP(charp) \
+	appendPQExpBufferStr(completion_charp, charp);
+
+#define SET_COMP_CHARP(charp) \
+	resetPQExpBuffer(completion_charp);	\
+	APPEND_COMP_CHARP(charp);
+
+#define COMPLETION_CHARP (completion_charp->data)
+
+#define COMPLETE_WITH_QUERY(query, addon) \
 do { \
-	completion_charp = query; \
+	SET_COMP_CHARP(query);	\
+	APPEND_COMP_CHARP(addon); \
 	matches = completion_matches(text, complete_from_query); \
 } while (0)
 
 #define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
 do { \
 	completion_squery = &(query); \
-	completion_charp = addon; \
+	SET_COMP_CHARP(addon); \
 	matches = completion_matches(text, complete_from_schema_query); \
 } while (0)
 
@@ -172,7 +182,7 @@ do { \
 
 #define COMPLETE_WITH_CONST(string) \
 do { \
-	completion_charp = string; \
+	SET_COMP_CHARP(string);	\
 	completion_case_sensitive = false; \
 	matches = completion_matches(text, complete_from_const); \
 } while (0)
@@ -190,12 +200,14 @@ do { \
 								false, false, pset.encoding); \
 	if (_completion_table == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_attributes  addon; \
+		SET_COMP_CHARP(Query_for_list_of_attributes); \
+		APPEND_COMP_CHARP(addon);					  \
 		completion_info_charp = relation; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_attributes_with_schema  addon; \
+		SET_COMP_CHARP(Query_for_list_of_attributes_with_schema); \
+		APPEND_COMP_CHARP(addon); \
 		completion_info_charp = _completion_table; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -215,12 +227,12 @@ do { \
 								   false, false, pset.encoding); \
 	if (_completion_function == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_arguments; \
+		SET_COMP_CHARP(Query_for_list_of_arguments); \
 		completion_info_charp = function; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_arguments_with_schema; \
+		SET_COMP_CHARP(Query_for_list_of_arguments_with_schema); \
 		completion_info_charp = _completion_function; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -312,16 +324,16 @@ do { \
 	COMPLETE_WITH_LIST_CS(list); \
 } while (0)
 
-#define ADDLIST1(p, s1) additional_kw_query(p, text, 1, s1)
-#define ADDLIST2(p, s1, s2) additional_kw_query(p, text, 2, s1, s2)
-#define ADDLIST3(p, s1, s2, s3) additional_kw_query(p, text, 3, s1, s2, s3)
-#define ADDLIST4(p, s1, s2, s3, s4) \
-	additional_kw_query(p, text, 4, s1, s2, s3, s4)
-#define ADDLIST13(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
-	additional_kw_query(p, text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+#define ADDLIST1(s1) additional_kw_query(text, 1, s1)
+#define ADDLIST2(s1, s2) additional_kw_query(text, 2, s1, s2)
+#define ADDLIST3(s1, s2, s3) additional_kw_query(text, 3, s1, s2, s3)
+#define ADDLIST4(s1, s2, s3, s4) \
+	additional_kw_query(text, 4, s1, s2, s3, s4)
+#define ADDLIST13(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
+	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
 						s8, s9, s10, s11, s12, s13)
-#define ADDLIST15(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
-	additional_kw_query(p, text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+#define ADDLIST15(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
+	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
 						s8, s9, s10, s11, s12, s13, s14, s15)
 
 /*
@@ -975,7 +987,7 @@ static char *complete_from_files(const char *text, int state);
 
 static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
-static char *additional_kw_query(char *prefix, const char *ref, int n, ...);
+static char *additional_kw_query( const char *ref, int n, ...);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
 
@@ -1406,7 +1418,8 @@ psql_completion(const char *text, int start, int end)
 #endif
 
 	/* Clear a few things. */
-	completion_charp = NULL;
+	if (completion_charp == NULL)
+		completion_charp = createPQExpBuffer();
 	completion_charpp = NULL;
 	completion_info_charp = NULL;
 	completion_info_charp2 = NULL;
@@ -1461,13 +1474,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE */
 	else if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-			ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
+			ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
 
 	/* ALTER TABLE after removing optional words IF EXISTS*/
 	else if (HeadMatches2("ALTER", "TABLE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 Matches2("ALTER", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* ALTER something */
 	else if (Matches1("ALTER"))
@@ -1487,7 +1500,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("SET TABLESPACE", "OWNED BY");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY */
 	else if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY xxx */
 	else if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("SET TABLESPACE");
@@ -1523,7 +1536,7 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER EVENT TRIGGER */
 	else if (Matches3("ALTER", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* ALTER EVENT TRIGGER <name> */
 	else if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
@@ -1541,14 +1554,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("ALTER", "EXTENSION", MatchAny, "UPDATE"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions_with_TO);
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions_with_TO,"");
 	}
 
 	/* ALTER EXTENSION <name> UPDATE TO */
 	else if (Matches5("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions, "");
 	}
 
 	/* ALTER FOREIGN */
@@ -1562,13 +1575,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER FOREIGN TABLE */
 	else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 
 	/* ALTER|DROP FOREIGN TABLE after removing optinal words IF EXISTS */
 	else if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 
 	/* ALTER FOREIGN TABLE <name> */
 	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
@@ -1583,17 +1596,17 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER FOREIGN TABLE xxx RENAME */
 	else if (Matches5("ALTER", "FOREIGN", "TABLE", MatchAny, "RENAME"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'TO'");
+		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST2("COLUMN", "TO"));
 
 	/* ALTER INDEX */
 	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-			   ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
+			   ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
 	/* ALTER INDEX after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "INDEX") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 Matches2("ALTER", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 
 	/* ALTER INDEX <name> */
 	else if (Matches3("ALTER", "INDEX", MatchAny))
@@ -1623,13 +1636,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER MATERIALIZED VIEW */
 	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-			   ADDLIST2("", "IF EXISTS", "ALL IN TABLESPACE"));
+			   ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
 
 	/* ALTER MATERIALIZED VIEW with name after removing optional words */
 	else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 Matches3("ALTER", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 
 	/* ALTER USER,ROLE <name> */
 	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
@@ -1685,14 +1698,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "DOMAIN", MatchAny, "RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, "");
 	}
 	/* ALTER DOMAIN <sth> DROP CONSTRAINT */
 	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_constraint_of_type, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type,
+							ADDLIST1("IF EXISTS"));
 	}
 	/* Try the same match after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
@@ -1700,7 +1713,7 @@ psql_completion(const char *text, int start, int end)
 			 Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, "");
 	}
 	/* ALTER DOMAIN <sth> RENAME */
 	else if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
@@ -1714,7 +1727,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	else if (Matches2("ALTER", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* ALTER SEQUENCE with name after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "SEQUENCE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1740,11 +1753,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
 	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars, "");
 	/* ALTER VIEW */
 	else if (Matches2("ALTER", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* ALTER VIEW <name> with subcommands after removing optional worlds */
 	else if (HeadMatches2("ALTER", "VIEW") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1758,8 +1771,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER POLICY */
 	else if (Matches2("ALTER", "POLICY"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_policies, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies,
+							ADDLIST1("IF EXISTS"));
 	/* ALTER POLICY <name> with ON after removing optional words IF EXISTS */
 	else if (HeadMatches2("ALTER", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -1769,14 +1782,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, "");
 	}
 	/* ALTER POLICY <name> ON <table> - show options */
 	else if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING (", "WITH CHECK (");
 	/* ALTER POLICY <name> ON <table> TO <role> */
 	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 	/* ALTER POLICY <name> ON <table> USING ( */
 	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
@@ -1792,7 +1805,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("ALTER", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, "");
 	}
 
 	/* ALTER RULE <name> ON <name> */
@@ -1806,14 +1819,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, "");
 	}
 
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
 	else if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* ALTER TRIGGER <name> ON <name> */
 	else if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
@@ -1840,22 +1853,22 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
 	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
 	{
 		completion_info_charp = prev4_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
 	{
 		completion_info_charp = prev4_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 	/* ALTER TABLE xxx INHERIT */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
@@ -1869,21 +1882,21 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 
 	/* ALTER TABLE xxx ALTER */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
+		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST2("COLUMN", "CONSTRAINT"));
 
 	/* ALTER TABLE xxx RENAME */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
+		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST3("COLUMN", "CONSTRAINT", "TO"));
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
@@ -1902,7 +1915,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/*  ALTER TABLE DROP COLUMN may take IF EXISTS */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
 	/* ALTER TABLE <name> with ADD/ALTER/DROP after removing optional words */
 	else if (HeadMatches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP") &&
 			 MidMatchAndRemove1(4, "COLUMN") &&
@@ -1911,7 +1924,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN") &&
 			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
@@ -1925,7 +1938,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, "");
 	}
 	/*
 	 * If we have ALTER TABLE <sth> DROP CONSTRAINT,
@@ -1934,8 +1947,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_constraint_of_table, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table,
+							ADDLIST1("IF EXISTS"));
 	}
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
@@ -1943,7 +1956,7 @@ psql_completion(const char *text, int start, int end)
 			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, "");
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
 	else if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
@@ -1970,7 +1983,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
 	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
 	else if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
@@ -1982,7 +1995,7 @@ psql_completion(const char *text, int start, int end)
 	 * tablespaces
 	 */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
 	/* If we have ALTER TABLE <sth> SET WITH provide OIDS */
 	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
 		COMPLETE_WITH_CONST("OIDS");
@@ -2034,7 +2047,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
 	else if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
@@ -2099,7 +2112,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 	else if (Matches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE"))
-		COMPLETE_WITH_ATTR(prev3_wd, " UNION SELECT 'IF EXISTS'");
+		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
 	/* Remove optional words IF EXISTS */
 	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE") &&
 			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
@@ -2114,7 +2127,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
 	else if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* BEGIN */
 	else if (Matches1("BEGIN"))
@@ -2134,9 +2147,9 @@ psql_completion(const char *text, int start, int end)
 /* CLUSTER */
 	else if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   ADDLIST1("", "VERBOSE"));
+								   ADDLIST1("VERBOSE"));
 	else if (Matches2("CLUSTER", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 	/* If we have CLUSTER <sth>, then add "USING" */
 	else if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
 		COMPLETE_WITH_CONST("USING");
@@ -2148,7 +2161,7 @@ psql_completion(const char *text, int start, int end)
 			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
 
 /* COMMENT */
@@ -2168,24 +2181,24 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
 	else if (Matches4("COMMENT", "ON", "ACCESS", "METHOD"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, "");
 	else if (Matches3("COMMENT", "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 	else if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 	else if (Matches3("COMMENT", "ON", "CONSTRAINT"))
-		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
+		COMPLETE_WITH_QUERY(Query_for_all_table_constraints, "");
 	else if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	else if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint, "");
 	}
 	else if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	else if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 	else if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
 		Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
 			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
@@ -2199,10 +2212,10 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   ADDLIST1("", "("));
+								   ADDLIST1("("));
 	/* If we have COPY BINARY, complete with list of tables */
 	else if (Matches2("COPY", "BINARY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* If we have COPY (, complete it with legal commands */
 	else if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
@@ -2214,7 +2227,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
 			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
 	{
-		completion_charp = "";
+		SET_COMP_CHARP("");
 		matches = completion_matches(text, complete_from_files);
 	}
 
@@ -2249,18 +2262,18 @@ psql_completion(const char *text, int start, int end)
 							"LC_COLLATE", "LC_CTYPE");
 
 	else if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
+		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases, "");
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	else if (Matches2("CREATE", "EXTENSION"))
-		COMPLETE_WITH_QUERY(ADDLIST1(Query_for_list_of_available_extensions,
-									 "IF NOT EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions,
+							ADDLIST1("IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS */
 	else if (HeadMatches2("CREATE", "EXTENSION") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
 			 Matches2("CREATE", "EXTENSION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions, "");
 	/* CREATE EXTENSION <name> */
 	else if (Matches3("CREATE", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
@@ -2268,7 +2281,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions, "");
 	}
 
 	/* CREATE FOREIGN */
@@ -2278,7 +2291,7 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE FOREIGN TABLE */
 	else if (Matches3("CREATE", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
 			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
@@ -2300,12 +2313,12 @@ psql_completion(const char *text, int start, int end)
 	   and existing indexes */
 	else if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-				   ADDLIST3("", "ON", "CONCURRENTLY", "IF NOT EXISTS"));
+				   ADDLIST3("ON", "CONCURRENTLY", "IF NOT EXISTS"));
 	/* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and
 	 * existing indexes */
 	else if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   ADDLIST2("", "IF NOT EXISTS", "ON"));
+								   ADDLIST2("IF NOT EXISTS", "ON"));
 
 	/* Remove optional words "CONCURRENTLY",  "IF NOT EXISTS" */
 	else if (HeadMatches2("CREATE", "INDEX") &&
@@ -2316,7 +2329,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete CREATE INDEX [<name>] ON with a list of tables */
 	else if (Matches4("CREATE", "INDEX", MatchAny, "ON") ||
 			 Matches3("CREATE", "INDEX", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 
 	/* Complete CREATE INDEX <sth> with "ON" */
 	else if (Matches3("CREATE", "INDEX", MatchAny))
@@ -2338,7 +2351,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete USING with an index method */
 	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, "");
 	else if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
 			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
 			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
@@ -2350,7 +2363,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
 	else if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
 	else if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING (", "WITH CHECK (");
@@ -2368,7 +2381,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("TO", "USING (", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
 	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
 	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
@@ -2388,7 +2401,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
 	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 /* Remove optional words TEMPORARY/TEMP */
 	else if (HeadMatches3("CREATE", MatchAny, "SEQUENCE") &&
@@ -2397,7 +2410,7 @@ psql_completion(const char *text, int start, int end)
 /* CREATE SEQUENCE */
 	else if(Matches2("CREATE", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 	else if(HeadMatches2("CREATE", "SEQUENCE") &&
 			MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
 			Matches3("CREATE", "SEQUENCE", MatchAny))
@@ -2412,8 +2425,8 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE SCHEMA <name> */
 	else if (Matches2("CREATE", "SCHEMA"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_schemas, "IF NOT EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas,
+							ADDLIST1("IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
 	else if (HeadMatches2("CREATE", "SCHEMA") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2433,7 +2446,7 @@ psql_completion(const char *text, int start, int end)
 			 false) {} /* FALL THROUGH*/
 	else if (Matches2("CREATE", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 
 	/* Remove optional words here */
 	else if (HeadMatches2("CREATE", "TABLE") &&
@@ -2473,10 +2486,10 @@ psql_completion(const char *text, int start, int end)
 	 * tables
 	 */
 	else if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
 	else if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
 	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
@@ -2524,7 +2537,7 @@ psql_completion(const char *text, int start, int end)
 /* CREATE VIEW  */
 	else if (Matches2("CREATE", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 	/* CREATE VIEW <name> with AS after removing optional words */
 	else if (HeadMatches2("CREATE", "VIEW") &&
 			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
@@ -2539,13 +2552,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   ADDLIST1("", "IF NOT EXISTS"));
+								   ADDLIST1("IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS. VIEW will be
 	 * completed afterwards */
 	else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
 			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
 			 Matches3("CREATE", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
 	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
@@ -2576,7 +2589,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
 	else if (TailMatches2("DELETE", "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete DELETE FROM <table> */
 	else if (TailMatches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
@@ -2607,10 +2620,10 @@ psql_completion(const char *text, int start, int end)
 	/* help completing some of the variants */
 	else if (Matches2("DROP", "AGGREGATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	else if (Matches2("DROP", "FUNCTION"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2621,8 +2634,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 	else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_fdws, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws,
+							ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&
 			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
@@ -2631,10 +2644,10 @@ psql_completion(const char *text, int start, int end)
 	/* DROP INDEX */
 	else if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   ADDLIST2("", "IF EXISTS","CONCURRENTLY"));
+								   ADDLIST2("IF EXISTS","CONCURRENTLY"));
 	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */
 	else if (HeadMatches2("DROP", "INDEX") &&
 			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
@@ -2647,33 +2660,33 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches2("DROP", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* Remove optional words IF EXISTS  */
 	else if (HeadMatches2("DROP", "VIEW") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 false) {} /* FALL THROUGH */
 	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   ADDLIST1("", "IF EXISTS"));
+								   ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 Matches3("DROP", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 
 	/* DROP OWNED BY */
 	else if (Matches2("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
 	else if (Matches3("DROP", "OWNED", "BY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 	else if (Matches3("DROP", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
 	else if (Matches2("DROP", "TRIGGER"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_triggers, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_triggers,
+							ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "TRIGGER") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2682,7 +2695,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, "");
 	}
 	else if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
@@ -2691,29 +2704,29 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("DROP", "ACCESS"))
 		COMPLETE_WITH_CONST("METHOD");
 	else if (Matches3("DROP", "ACCESS", "METHOD"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, "");
 
 	/* DROP EVENT TRIGGER */
 	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	else if (Matches3("DROP", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_event_triggers, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers,
+							ADDLIST1("IF EXISTS"));
 	/* Trye the same after removing optional words IF EXISTS */
 	else if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 Matches3("DROP", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* DROP POLICY */
 	else if (Matches2("DROP", "POLICY"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_policies, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies,
+							ADDLIST1("IF EXISTS"));
 	/* Try the same after after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "POLICY") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
 			 Matches2("DROP", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies, "");
 	/* DROP POLICY <name> */
 	else if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
@@ -2721,13 +2734,13 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, "");
 	}
 
 	/* DROP RULE */
 	else if (Matches2("DROP", "RULE"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_rules, "IF EXISTS"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_rules,
+							ADDLIST1("IF EXISTS"));
 	/* DROP RULE <name>, after removing optional words IF EXISTS */
 	else if (HeadMatches2("DROP", "RULE") &&
 			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
@@ -2736,7 +2749,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, "");
 	}
 	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
@@ -2751,15 +2764,15 @@ psql_completion(const char *text, int start, int end)
 
 	{
 		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
-		char *addition = ADDLIST1("", "IF EXISTS");
+		char *addition = ADDLIST1("IF EXISTS");
 
 		if (ent)
 		{
 			/* Completing USER needs special treat */
 			if (pg_strcasecmp(prev_wd, "USER") == 0)
 			{
-				COMPLETE_WITH_QUERY(
-					ADDLIST2(Query_for_list_of_roles, "MAPPING", "IF EXISTS"));
+				COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+									ADDLIST2("MAPPING", "IF EXISTS"));
 			}
 			else if (ent->query)
 			{
@@ -2767,12 +2780,12 @@ psql_completion(const char *text, int start, int end)
 									  strlen(addition) + 1);
 				strcpy(buf, ent->query);
 				strcat(buf, addition);
-				COMPLETE_WITH_QUERY(buf);
+				COMPLETE_WITH_QUERY(buf, "");
 				free(buf);
 			}
 			else if (ent->squery)
 				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery,
-										   ADDLIST1("", "IF EXISTS"));
+										   ADDLIST1("IF EXISTS"));
 		}
 	}
 	/* Remove optional IF EXISTS from DROP */
@@ -2789,7 +2802,7 @@ psql_completion(const char *text, int start, int end)
 
 /* EXECUTE */
 	else if (Matches1("EXECUTE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
+		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements, "");
 
 /* EXPLAIN */
 
@@ -2826,7 +2839,7 @@ psql_completion(const char *text, int start, int end)
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
 	else if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
 			 !TailMatches4("CREATE", MatchAny, MatchAny, MatchAny))
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, "");
 	/* applies in CREATE SERVER */
 	else if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
 			 HeadMatches2("CREATE", "SERVER"))
@@ -2834,18 +2847,17 @@ psql_completion(const char *text, int start, int end)
 
 /* FOREIGN TABLE */
 	else if (TailMatches2("FOREIGN", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 
 /* FOREIGN SERVER */
 	else if (TailMatches2("FOREIGN", "SERVER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_servers, "");
 
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
 	else if (TailMatches1("GRANT|REVOKE"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST13(Query_for_list_of_roles,
-					  "SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+			ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
 					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
 					  "EXECUTE", "USAGE", "ALL"));
 
@@ -2876,8 +2888,7 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
-			   ADDLIST15("",
-						 "ALL FUNCTIONS IN SCHEMA",
+			   ADDLIST15("ALL FUNCTIONS IN SCHEMA",
 						 "ALL SEQUENCES IN SCHEMA",
 						 "ALL TABLES IN SCHEMA",
 						 "DATABASE",
@@ -2909,23 +2920,23 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
 		if (TailMatches1("DATABASE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+			COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 		else if (TailMatches1("DOMAIN"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, "");
 		else if (TailMatches1("FUNCTION"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 		else if (TailMatches1("LANGUAGE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
+			COMPLETE_WITH_QUERY(Query_for_list_of_languages, "");
 		else if (TailMatches1("SCHEMA"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+			COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
 		else if (TailMatches1("SEQUENCE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
 		else if (TailMatches1("TABLE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 		else if (TailMatches1("TABLESPACE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
 		else if (TailMatches1("TYPE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, "");
 		else if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
@@ -2938,7 +2949,7 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
 			 (HeadMatches1("REVOKE") && TailMatches1("FROM")))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 
 	/* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */
 	else if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
@@ -2989,7 +3000,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
 	else if (TailMatches2("INSERT", "INTO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete "INSERT INTO <table> (" with attribute names */
 	else if (TailMatches4("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
@@ -3017,7 +3028,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete LOCK [TABLE] with a list of tables */
 	else if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   ADDLIST1("", "TABLE"));
+								   ADDLIST1("TABLE"));
 	else if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
@@ -3039,7 +3050,7 @@ psql_completion(const char *text, int start, int end)
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
 	else if (TailMatches1("NOTIFY"))
-		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
+		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'", "");
 
 /* OPTIONS */
 	else if (TailMatches1("OPTIONS"))
@@ -3047,7 +3058,7 @@ psql_completion(const char *text, int start, int end)
 
 /* OWNER TO  - complete with available roles */
 	else if (TailMatches2("OWNER", "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* ORDER BY */
 	else if (TailMatches3("FROM", MatchAny, "ORDER"))
@@ -3070,11 +3081,11 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches2("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
 	else if (Matches3("REASSIGN", "OWNED", "BY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	else if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 	else if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* REFRESH MATERIALIZED VIEW */
 	else if (Matches1("REFRESH"))
@@ -3083,9 +3094,9 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("VIEW");
 	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   ADDLIST1("", "CONCURRENTLY"));
+								   ADDLIST1("CONCURRENTLY"));
 	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
 	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
@@ -3103,13 +3114,13 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
 	else if (Matches2("REINDEX", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 	else if (Matches2("REINDEX", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 	else if (Matches2("REINDEX", "SCHEMA"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
 	else if (Matches2("REINDEX", "SYSTEM|DATABASE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+		COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 
 /* SECURITY LABEL */
 	else if (Matches1("SECURITY"))
@@ -3138,9 +3149,9 @@ psql_completion(const char *text, int start, int end)
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
 	else if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars, "");
 	else if (Matches1("SHOW"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars, "");
 	/* Complete "SET TRANSACTION" */
 	else if (Matches2("SET", "TRANSACTION"))
 		COMPLETE_WITH_LIST5("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
@@ -3176,20 +3187,20 @@ psql_completion(const char *text, int start, int end)
 	/* SET CONSTRAINTS */
 	else if (Matches2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema,
-								   ADDLIST1("", "ALL"));
+								   ADDLIST1("ALL"));
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
 	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
 	else if (Matches2("SET", "ROLE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
 	else if (Matches2("SET", "SESSION"))
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
 	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST1(Query_for_list_of_roles, "DEFAULT"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+							ADDLIST1("DEFAULT"));
 	/* Complete RESET SESSION with AUTHORIZATION */
 	else if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
@@ -3215,11 +3226,10 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST(my_list);
 		}
 		else if (TailMatches2("search_path", "TO|="))
-			COMPLETE_WITH_QUERY(
-				ADDLIST1(Query_for_list_of_schemas
-						 " AND nspname not like 'pg\\_toast%%' "
-						 " AND nspname not like 'pg\\_temp%%' ",
-						 "DEFAULT"));
+			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+								" AND nspname not like 'pg\\_toast%%' "
+								" AND nspname not like 'pg\\_temp%%' ",
+								ADDLIST1("DEFAULT"));
 		else
 		{
 			/* generic, type based, GUC support */
@@ -3230,7 +3240,7 @@ psql_completion(const char *text, int start, int end)
 				char		querybuf[1024];
 
 				snprintf(querybuf, sizeof(querybuf), Query_for_enum, prev2_wd);
-				COMPLETE_WITH_QUERY(querybuf);
+				COMPLETE_WITH_QUERY(querybuf, "");
 			}
 			else if (guctype && strcmp(guctype, "bool") == 0)
 				COMPLETE_WITH_LIST9("on", "off", "true", "false", "yes", "no",
@@ -3249,26 +3259,26 @@ psql_completion(const char *text, int start, int end)
 
 /* TABLE, but not TABLE embedded in other commands */
 	else if (Matches1("TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, "");
 
 /* TABLESAMPLE */
 	else if (TailMatches1("TABLESAMPLE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods, "");
 	else if (TailMatches2("TABLESAMPLE", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
 	else if (Matches1("TRUNCATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 /* UNLISTEN */
 	else if (Matches1("UNLISTEN"))
-		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
+		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'", "");
 
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
 	else if (TailMatches1("UPDATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete UPDATE <table> with "SET" */
 	else if (TailMatches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
@@ -3288,11 +3298,10 @@ psql_completion(const char *text, int start, int end)
 			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
 			 false) {} /* FALL THROUGH */
 	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(
-			ADDLIST3(Query_for_list_of_roles, 
-					 "CURRENT_USER", "PUBLIC", "USER"));
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+							ADDLIST3("CURRENT_USER", "PUBLIC", "USER"));
 	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, "");
 	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("SERVER");
 	else if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
@@ -3304,24 +3313,24 @@ psql_completion(const char *text, int start, int end)
  */
 	else if (Matches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST4("", "FULL", "FREEZE", "ANALYZE", "VERBOSE"));
+		   ADDLIST4("FULL", "FREEZE", "ANALYZE", "VERBOSE"));
 	else if (Matches2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST2("", "ANALYZE", "VERBOSE"));
+		   ADDLIST2("ANALYZE", "VERBOSE"));
 	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST1("", "VERBOSE"));
+		   ADDLIST1("VERBOSE"));
 	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST1("", "ANALYZE"));
+		   ADDLIST1("ANALYZE"));
 	else if (Matches2("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST1("", "ANALYZE"));
+		   ADDLIST1("ANALYZE"));
 	else if (Matches2("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-		   ADDLIST1("", "VERBOSE"));
+		   ADDLIST1("VERBOSE"));
 	else if (HeadMatches1("VACUUM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 
 /* WITH [RECURSIVE] */
 
@@ -3335,7 +3344,7 @@ psql_completion(const char *text, int start, int end)
 /* ANALYZE */
 	/* Complete with list of tables */
 	else if (Matches1("ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, "");
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
@@ -3345,11 +3354,11 @@ psql_completion(const char *text, int start, int end)
 /* ... FROM ... */
 /* TODO: also include SRF ? */
 	else if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 
 /* ... JOIN ... */
 	else if (TailMatches1("JOIN"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
@@ -3358,84 +3367,84 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatchesCS1("\\connect|\\c"))
 	{
 		if (!recognized_connection_string(text))
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+			COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 	}
 	else if (TailMatchesCS2("\\connect|\\c", MatchAny))
 	{
 		if (!recognized_connection_string(prev_wd))
-			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+			COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	}
 	else if (TailMatchesCS1("\\da*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, "");
 	else if (TailMatchesCS1("\\dA*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, "");
 	else if (TailMatchesCS1("\\db*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
 	else if (TailMatchesCS1("\\dD*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, "");
 	else if (TailMatchesCS1("\\des*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_servers, "");
 	else if (TailMatchesCS1("\\deu*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, "");
 	else if (TailMatchesCS1("\\dew*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, "");
 	else if (TailMatchesCS1("\\df*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 
 	else if (TailMatchesCS1("\\dFd*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries, "");
 	else if (TailMatchesCS1("\\dFp*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers, "");
 	else if (TailMatchesCS1("\\dFt*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates, "");
 	/* must be at end of \dF alternatives: */
 	else if (TailMatchesCS1("\\dF*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations, "");
 
 	else if (TailMatchesCS1("\\di*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 	else if (TailMatchesCS1("\\dL*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
+		COMPLETE_WITH_QUERY(Query_for_list_of_languages, "");
 	else if (TailMatchesCS1("\\dn*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
 	else if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 	else if (TailMatchesCS1("\\ds*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
 	else if (TailMatchesCS1("\\dt*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	else if (TailMatchesCS1("\\dT*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, "");
 	else if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	else if (TailMatchesCS1("\\dv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	else if (TailMatchesCS1("\\dx*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_extensions, "");
 	else if (TailMatchesCS1("\\dm*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	else if (TailMatchesCS1("\\dE*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 	else if (TailMatchesCS1("\\dy*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* must be at end of \d alternatives: */
 	else if (TailMatchesCS1("\\d*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, "");
 
 	else if (TailMatchesCS1("\\ef"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 	else if (TailMatchesCS1("\\ev"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 
 	else if (TailMatchesCS1("\\encoding"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_encodings, "");
 	else if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
 		COMPLETE_WITH_LIST(sql_commands);
 	else if (TailMatchesCS1("\\l*") && !TailMatchesCS1("\\lo*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+		COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 	else if (TailMatchesCS1("\\password"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	else if (TailMatchesCS1("\\pset"))
 	{
 		static const char *const my_list[] =
@@ -3495,14 +3504,14 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
 	}
 	else if (TailMatchesCS1("\\sf*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 	else if (TailMatchesCS1("\\sv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	else if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
 							"\\ir|\\include_relative|\\o|\\out|"
 							"\\s|\\w|\\write|\\lo_import"))
 	{
-		completion_charp = "\\";
+		SET_COMP_CHARP("\\");
 		matches = completion_matches(text, complete_from_files);
 	}
 
@@ -3518,9 +3527,9 @@ psql_completion(const char *text, int start, int end)
 		if (ent)
 		{
 			if (ent->query)
-				COMPLETE_WITH_QUERY(ent->query);
+				COMPLETE_WITH_QUERY(ent->query, "");
 			else if (ent->squery)
-				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, NULL);
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, "");
 		}
 	}
 
@@ -3782,13 +3791,13 @@ _complete_from_query(int is_schema_query, const char *text, int state)
 							  char_length, e_text);
 
 			/* If an addon query was provided, use it */
-			if (completion_charp)
-				appendPQExpBuffer(&query_buffer, "\n%s", completion_charp);
+			if (COMPLETION_CHARP[0])
+				appendPQExpBuffer(&query_buffer, "\n%s", COMPLETION_CHARP);
 		}
 		else
 		{
 			/* completion_charp is an sprintf-style format string */
-			appendPQExpBuffer(&query_buffer, completion_charp,
+			appendPQExpBuffer(&query_buffer, COMPLETION_CHARP,
 							  char_length, e_text,
 							  e_info_charp, e_info_charp,
 							  e_info_charp2, e_info_charp2);
@@ -3903,18 +3912,17 @@ complete_from_list(const char *text, int state)
 static char *
 complete_from_const(const char *text, int state)
 {
-	Assert(completion_charp != NULL);
 	if (state == 0)
 	{
 		if (completion_case_sensitive)
-			return pg_strdup(completion_charp);
+			return pg_strdup(COMPLETION_CHARP);
 		else
 
 			/*
 			 * If case insensitive matching was requested initially, adjust
 			 * the case according to setting.
 			 */
-			return pg_strdup_keyword_case(completion_charp, text);
+			return pg_strdup_keyword_case(COMPLETION_CHARP, text);
 	}
 	else
 		return NULL;
@@ -4015,7 +4023,7 @@ complete_from_files(const char *text, int state)
 	if (state == 0)
 	{
 		/* Initialization: stash the unquoted input. */
-		unquoted_text = strtokx(text, "", NULL, "'", *completion_charp,
+		unquoted_text = strtokx(text, "", NULL, "'", COMPLETION_CHARP[0],
 								false, true, pset.encoding);
 		/* expect a NULL return for the empty string only */
 		if (!unquoted_text)
@@ -4036,7 +4044,7 @@ complete_from_files(const char *text, int state)
 		 * bother providing a macro to simplify this.
 		 */
 		ret = quote_if_needed(unquoted_match, " \t\r\n\"`",
-							  '\'', *completion_charp, pset.encoding);
+							  '\'', COMPLETION_CHARP[0], pset.encoding);
 		if (ret)
 			free(unquoted_match);
 		else
@@ -4094,7 +4102,7 @@ pg_strdup_keyword_case(const char *s, const char *ref)
 
 /* Construct codelet to append given keywords  */
 static char *
-additional_kw_query(char *prefix, const char *ref, int n, ...)
+additional_kw_query(const char *ref, int n, ...)
 {
 	va_list ap;
 	static PQExpBuffer qbuf = NULL;
@@ -4105,8 +4113,6 @@ additional_kw_query(char *prefix, const char *ref, int n, ...)
 	else
 		resetPQExpBuffer(qbuf);
 
-	appendPQExpBufferStr(qbuf, prefix);
-
 	/* Construct an additional queriy to append keywords */
 	appendPQExpBufferStr(qbuf, " UNION SELECT * FROM (VALUES ");
 
-- 
2.9.2

0004-Refactoring-tab-complete-to-make-psql_completion-cod.patchtext/x-patch; charset=us-asciiDownload
From 1bdf05097a3f2fda11bc244628d19217409c2403 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Thu, 7 Apr 2016 13:38:54 +0900
Subject: [PATCH 4/4] Refactoring tab-complete to make psql_completion code
 simpler.

psql_completion consists of a sequence of "else-if"s and succeeding
finishing code. This structure prevents us from having anything other
than if-conditions and completion code in every then-clause in the
else-if sequence. For the reason we had no way to have operations like
modifying previous_words other than implement it is boolean functions
always return false. This patch separates the else-if sequence into
individual function "psql_completion_internal". This modification
allows the sequence to be simple if sequence and allows us to put any
code midst of the sequence.
---
 src/bin/psql/Makefile              |    2 +
 src/bin/psql/tab-complete-macros.h |  479 +++++++++
 src/bin/psql/tab-complete.c        | 1864 +++++++++++++++---------------------
 3 files changed, 1241 insertions(+), 1104 deletions(-)
 create mode 100644 src/bin/psql/tab-complete-macros.h

diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index 1f6a289..51f88ba 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -47,6 +47,8 @@ ifeq ($(GCC),yes)
 psqlscanslash.o: CFLAGS += -Wno-error
 endif
 
+tab-complete.o: tab-complete-macros.h
+
 distprep: sql_help.h psqlscanslash.c
 
 install: all installdirs
diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
new file mode 100644
index 0000000..e7ca851
--- /dev/null
+++ b/src/bin/psql/tab-complete-macros.h
@@ -0,0 +1,479 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2016, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/tab-complete-macros.h
+ */
+#ifndef TAB_COMPLETE_MACROS_H
+#define TAB_COMPLETE_MACROS_H
+
+/*
+ * For compactness, we use these macros to reference previous_words[].
+ * Caution: do not access a previous_words[] entry without having checked
+ * previous_words_count to be sure it's valid.  In most cases below, that
+ * check is implicit in a TailMatches() or similar macro, but in some places
+ * we have to check it explicitly.
+ */
+#define prev_wd   (previous_words[0])
+#define prev2_wd  (previous_words[1])
+#define prev3_wd  (previous_words[2])
+#define prev4_wd  (previous_words[3])
+#define prev5_wd  (previous_words[4])
+#define prev6_wd  (previous_words[5])
+#define prev7_wd  (previous_words[6])
+#define prev8_wd  (previous_words[7])
+#define prev9_wd  (previous_words[8])
+
+/* Move the position of the beginning word for matching macros.  */
+#define SHIFTHEAD(n) \
+	(head_shift += (n))
+
+/* Set the position of the beginning word for matching macros.  */
+#define SETHEAD(n) \
+	(head_shift = (n))
+
+/* Return the number of stored words counting head shift */
+#define WORD_COUNT() (previous_words_count - head_shift)
+
+/*
+ * Return the true index in previous_words for index from the
+ * beginning. n is 1-based and the result is 0-based.
+ */
+#define HEAD_INDEX(n) \
+	(previous_words_count - head_shift - (n))
+/*
+ * remove n words from current shifted position. s and n are 1-based index.
+ */
+#define COLLAPSE(s, n)							\
+    do { \
+		memmove(previous_words + HEAD_INDEX((s) + (n) - 1), previous_words + HEAD_INDEX((s) - 1), \
+				sizeof(char *) * (previous_words_count - HEAD_INDEX((s) - 1))); \
+		previous_words_count -= (n); \
+	} while(0)
+
+/*
+ * Macros for matching the last N words before point, and after head_sift,
+ * case-insensitively.
+ */
+#define TailMatches1(p1) \
+	(WORD_COUNT() >= 1 && \
+	 word_matches(p1, prev_wd))
+
+#define TailMatches2(p2, p1) \
+	(WORD_COUNT() >= 2 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd))
+
+#define TailMatches3(p3, p2, p1) \
+	(WORD_COUNT() >= 3 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd))
+
+#define TailMatches4(p4, p3, p2, p1) \
+	(WORD_COUNT() >= 4 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd))
+
+#define TailMatches5(p5, p4, p3, p2, p1) \
+	(WORD_COUNT() >= 5 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd))
+
+#define TailMatches6(p6, p5, p4, p3, p2, p1) \
+	(WORD_COUNT() >= 6 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd))
+
+#define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
+	(WORD_COUNT() >= 7 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd))
+
+#define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
+	(WORD_COUNT() >= 8 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd) && \
+	 word_matches(p8, prev8_wd))
+
+#define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
+	(WORD_COUNT() >= 9 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd) && \
+	 word_matches(p8, prev8_wd) && \
+	 word_matches(p9, prev9_wd))
+
+	/*
+	 * Macros for matching the last N words before point, and after
+	 * head_shift, case-sensitively.
+	 */
+#define TailMatchesCS1(p1) \
+	(WORD_COUNT() >= 1 && \
+	 word_matches_cs(p1, prev_wd))
+#define TailMatchesCS2(p2, p1) \
+	(WORD_COUNT() >= 2 && \
+	 word_matches_cs(p1, prev_wd) && \
+	 word_matches_cs(p2, prev2_wd))
+
+	/*
+	 * Macros for matching N words exactly to the line, and after head_shift,
+	 * case-insensitively.
+	 */
+#define Matches1(p1) \
+	(WORD_COUNT() == 1 && \
+	 TailMatches1(p1))
+#define Matches2(p1, p2) \
+	(WORD_COUNT() == 2 && \
+	 TailMatches2(p1, p2))
+#define Matches3(p1, p2, p3) \
+	(WORD_COUNT() == 3 && \
+	 TailMatches3(p1, p2, p3))
+#define Matches4(p1, p2, p3, p4) \
+	(WORD_COUNT() == 4 && \
+	 TailMatches4(p1, p2, p3, p4))
+#define Matches5(p1, p2, p3, p4, p5) \
+	(WORD_COUNT() == 5 && \
+	 TailMatches5(p1, p2, p3, p4, p5))
+#define Matches6(p1, p2, p3, p4, p5, p6) \
+	(WORD_COUNT() == 6 && \
+	 TailMatches6(p1, p2, p3, p4, p5, p6))
+#define Matches7(p1, p2, p3, p4, p5, p6, p7) \
+	(WORD_COUNT() == 7 && \
+	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
+#define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
+	(WORD_COUNT() == 8 && \
+	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
+#define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
+	(WORD_COUNT() == 9 && \
+	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
+
+/*
+ * Macros for matching N words after head_shift + sth in the line, regardless
+ * of what is after them, case-insensitively.
+ */
+#define MidMatches1(s, p1) \
+	(HEAD_INDEX(s) >=0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]))
+
+#define MidMatches2(s, p1, p2) \
+	(HEAD_INDEX((s) + 1) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]))
+
+#define MidMatches3(s, p1, p2, p3) \
+	(HEAD_INDEX((s) + 2) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]))
+
+#define MidMatches4(s, p1, p2, p3, p4) \
+	(HEAD_INDEX((s) + 3) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]))
+
+#define MidMatches5(s, p1, p2, p3, p4, p5) \
+	(HEAD_INDEX((s) + 4) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&	\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&		\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]))
+
+#define MidMatches6(s,p1, p2, p3, p4, p5, p6)		\
+	(HEAD_INDEX((s) + 5) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]))
+
+#define MidMatches7(s,p1, p2, p3, p4, p5, p6, p7)	\
+	(HEAD_INDEX((s) + 6) >= 0 &&							\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&			\
+	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]))
+
+#define HeadMatches1(p1) \
+	MidMatches1(1, p1)
+#define HeadMatches2(p1, p2) \
+	MidMatches2(1, p1, p2)
+#define HeadMatches3(p1, p2, p3) \
+	MidMatches3(1, p1, p2, p3)
+#define HeadMatches4(p1, p2, p3, p4) \
+	MidMatches4(1, p1, p2, p3, p4)
+#define HeadMatches5(p1, p2, p3, p4, p5) \
+	MidMatches5(1, p1, p2, p3, p4, p5)
+#define HeadMatches6(p1, p2, p3, p4, p5, p6) \
+	MidMatches6(1, p1, p2, p3, p4, p5, p6)
+#define HeadMatches7(p1, p2, p3, p4, p5, p6, p7) \
+	MidMatches7(1, p1, p2, p3, p4, p5, p6, p7)
+
+/*
+ * completion_charp is now not a simple string, but a PQExpBuffer. These
+ * macros are to make the appearance of completion_charp to be simpler.
+ */
+
+/* Append a string to existing completion_charp  */
+#define APPEND_COMPLETION_CHARP(charp) \
+	appendPQExpBufferStr(completion_charp, charp);
+
+/* Set a string to completion_charp  */
+#define SET_COMPLETION_CHARP(charp) \
+	resetPQExpBuffer(completion_charp);	\
+	APPEND_COMPLETION_CHARP(charp);
+
+/* Read the completion_charp as a string */
+#define COMPLETION_CHARP (completion_charp->data)
+
+
+/*
+ * A few macros to ease typing. You can use these to complete the given
+ * string with
+ * 1) The results from a query you pass it. (Perhaps one of those below?)
+ * 2) The results from a schema query you pass it.
+ * 3) The items from a null-pointer-terminated list (with or without
+ *	  case-sensitive comparison; see also COMPLETE_WITH_LISTn, below).
+ * 4) A string constant.
+ * 5) The list of attributes of the given table (possibly schema-qualified).
+ * 6) The list of arguments to the given function (possibly schema-qualified).
+ */
+#define COMPLETE_WITH_QUERY(query, addon) \
+do { \
+	SET_COMPLETION_CHARP(query);	\
+	APPEND_COMPLETION_CHARP(addon); \
+	return completion_matches(text, complete_from_query);	\
+} while (0)
+
+#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
+do { \
+	completion_squery = &(query); \
+	SET_COMPLETION_CHARP(addon); \
+	return completion_matches(text, complete_from_schema_query); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS(list) \
+do { \
+	completion_charpp = list; \
+	completion_case_sensitive = true; \
+	return completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST(list) \
+do { \
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	return completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_CONST(string) \
+do { \
+	SET_COMPLETION_CHARP(string);	\
+	completion_case_sensitive = false; \
+	return completion_matches(text, complete_from_const);	\
+} while (0)
+
+#define COMPLETE_WITH_ATTR(relation, addon) \
+do { \
+	char   *_completion_schema; \
+	char   *_completion_table; \
+\
+	_completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \
+								 false, false, pset.encoding); \
+	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+				   false, false, pset.encoding); \
+	_completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+								false, false, pset.encoding); \
+	if (_completion_table == NULL) \
+	{ \
+		SET_COMPLETION_CHARP(Query_for_list_of_attributes); \
+		APPEND_COMPLETION_CHARP(addon);					  \
+		completion_info_charp = relation; \
+	} \
+	else \
+	{ \
+		SET_COMPLETION_CHARP(Query_for_list_of_attributes_with_schema); \
+		APPEND_COMPLETION_CHARP(addon); \
+		completion_info_charp = _completion_table; \
+		completion_info_charp2 = _completion_schema; \
+	} \
+	return completion_matches(text, complete_from_query); \
+} while (0)
+
+#define COMPLETE_WITH_FUNCTION_ARG(function) \
+do { \
+	char   *_completion_schema; \
+	char   *_completion_function; \
+\
+	_completion_schema = strtokx(function, " \t\n\r", ".", "\"", 0, \
+								 false, false, pset.encoding); \
+	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+				   false, false, pset.encoding); \
+	_completion_function = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+								   false, false, pset.encoding); \
+	if (_completion_function == NULL) \
+	{ \
+		SET_COMPLETION_CHARP(Query_for_list_of_arguments); \
+		completion_info_charp = function; \
+	} \
+	else \
+	{ \
+		SET_COMPLETION_CHARP(Query_for_list_of_arguments_with_schema); \
+		completion_info_charp = _completion_function; \
+		completion_info_charp2 = _completion_schema; \
+	} \
+	return completion_matches(text, complete_from_query); \
+} while (0)
+
+/*
+ * These macros simplify use of COMPLETE_WITH_LIST for short, fixed lists.
+ * There is no COMPLETE_WITH_LIST1; use COMPLETE_WITH_CONST for that case.
+ */
+#define COMPLETE_WITH_LIST2(s1, s2) \
+do { \
+	static const char *const list[] = { s1, s2, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST3(s1, s2, s3) \
+do { \
+	static const char *const list[] = { s1, s2, s3, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST4(s1, s2, s3, s4) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST5(s1, s2, s3, s4, s5) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST6(s1, s2, s3, s4, s5, s6) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST7(s1, s2, s3, s4, s5, s6, s7) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST8(s1, s2, s3, s4, s5, s6, s7, s8) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST9(s1, s2, s3, s4, s5, s6, s7, s8, s9) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST10(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+/*
+ * Likewise for COMPLETE_WITH_LIST_CS.
+ */
+#define COMPLETE_WITH_LIST_CS2(s1, s2) \
+do { \
+	static const char *const list[] = { s1, s2, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS3(s1, s2, s3) \
+do { \
+	static const char *const list[] = { s1, s2, s3, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS4(s1, s2, s3, s4) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS5(s1, s2, s3, s4, s5) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+/*
+ * A macro to comlete "thing" at the pth position before from the current
+ * word. COMPLETE_THING(-1) completes the thing of the last word in
+ * previous_words.
+ */
+#define COMPLETE_THING(p) \
+do { \
+	const pgsql_thing_t *ent = find_thing_entry(previous_words[-(p) - 1]);	\
+	if (ent) \
+	{ \
+		if (ent->query) \
+			COMPLETE_WITH_QUERY(ent->query, ""); \
+		else if (ent->squery) \
+			COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, ""); \
+	} \
+	return NULL; \
+} while (0)
+
+/*
+ * Macros to construct a query to add additional keyword, additional_kw_query
+ * changes the case of the keywords following an input.
+ */
+#define ADDLIST1(s1) additional_kw_query(text, 1, s1)
+#define ADDLIST2(s1, s2) additional_kw_query(text, 2, s1, s2)
+#define ADDLIST3(s1, s2, s3) additional_kw_query(text, 3, s1, s2, s3)
+#define ADDLIST4(s1, s2, s3, s4) \
+	additional_kw_query(text, 4, s1, s2, s3, s4)
+#define ADDLIST13(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
+	additional_kw_query(text, 13, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13)
+#define ADDLIST15(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
+	additional_kw_query(text, 15, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13, s14, s15)
+
+
+#endif   /* TAB_COMPLETE_MACROS_H */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 832bcd7..d68d886 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -34,6 +34,7 @@
 
 #include "postgres_fe.h"
 #include "tab-complete.h"
+#include "tab-complete-macros.h"
 #include "input.h"
 
 /* If we don't have this, we might as well forget about the whole thing: */
@@ -133,210 +134,6 @@ static const SchemaQuery *completion_squery;	/* to pass a SchemaQuery */
 static bool completion_case_sensitive;	/* completion is case sensitive */
 
 /*
- * A few macros to ease typing. You can use these to complete the given
- * string with
- * 1) The results from a query you pass it. (Perhaps one of those below?)
- * 2) The results from a schema query you pass it.
- * 3) The items from a null-pointer-terminated list (with or without
- *	  case-sensitive comparison; see also COMPLETE_WITH_LISTn, below).
- * 4) A string constant.
- * 5) The list of attributes of the given table (possibly schema-qualified).
- * 6/ The list of arguments to the given function (possibly schema-qualified).
- */
-#define APPEND_COMP_CHARP(charp) \
-	appendPQExpBufferStr(completion_charp, charp);
-
-#define SET_COMP_CHARP(charp) \
-	resetPQExpBuffer(completion_charp);	\
-	APPEND_COMP_CHARP(charp);
-
-#define COMPLETION_CHARP (completion_charp->data)
-
-#define COMPLETE_WITH_QUERY(query, addon) \
-do { \
-	SET_COMP_CHARP(query);	\
-	APPEND_COMP_CHARP(addon); \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
-do { \
-	completion_squery = &(query); \
-	SET_COMP_CHARP(addon); \
-	matches = completion_matches(text, complete_from_schema_query); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS(list) \
-do { \
-	completion_charpp = list; \
-	completion_case_sensitive = true; \
-	matches = completion_matches(text, complete_from_list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST(list) \
-do { \
-	completion_charpp = list; \
-	completion_case_sensitive = false; \
-	matches = completion_matches(text, complete_from_list); \
-} while (0)
-
-#define COMPLETE_WITH_CONST(string) \
-do { \
-	SET_COMP_CHARP(string);	\
-	completion_case_sensitive = false; \
-	matches = completion_matches(text, complete_from_const); \
-} while (0)
-
-#define COMPLETE_WITH_ATTR(relation, addon) \
-do { \
-	char   *_completion_schema; \
-	char   *_completion_table; \
-\
-	_completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-								false, false, pset.encoding); \
-	if (_completion_table == NULL) \
-	{ \
-		SET_COMP_CHARP(Query_for_list_of_attributes); \
-		APPEND_COMP_CHARP(addon);					  \
-		completion_info_charp = relation; \
-	} \
-	else \
-	{ \
-		SET_COMP_CHARP(Query_for_list_of_attributes_with_schema); \
-		APPEND_COMP_CHARP(addon); \
-		completion_info_charp = _completion_table; \
-		completion_info_charp2 = _completion_schema; \
-	} \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-#define COMPLETE_WITH_FUNCTION_ARG(function) \
-do { \
-	char   *_completion_schema; \
-	char   *_completion_function; \
-\
-	_completion_schema = strtokx(function, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_function = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-								   false, false, pset.encoding); \
-	if (_completion_function == NULL) \
-	{ \
-		SET_COMP_CHARP(Query_for_list_of_arguments); \
-		completion_info_charp = function; \
-	} \
-	else \
-	{ \
-		SET_COMP_CHARP(Query_for_list_of_arguments_with_schema); \
-		completion_info_charp = _completion_function; \
-		completion_info_charp2 = _completion_schema; \
-	} \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-/*
- * These macros simplify use of COMPLETE_WITH_LIST for short, fixed lists.
- * There is no COMPLETE_WITH_LIST1; use COMPLETE_WITH_CONST for that case.
- */
-#define COMPLETE_WITH_LIST2(s1, s2) \
-do { \
-	static const char *const list[] = { s1, s2, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST3(s1, s2, s3) \
-do { \
-	static const char *const list[] = { s1, s2, s3, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST4(s1, s2, s3, s4) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST5(s1, s2, s3, s4, s5) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST6(s1, s2, s3, s4, s5, s6) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST7(s1, s2, s3, s4, s5, s6, s7) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST8(s1, s2, s3, s4, s5, s6, s7, s8) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST9(s1, s2, s3, s4, s5, s6, s7, s8, s9) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST10(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-/*
- * Likewise for COMPLETE_WITH_LIST_CS.
- */
-#define COMPLETE_WITH_LIST_CS2(s1, s2) \
-do { \
-	static const char *const list[] = { s1, s2, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS3(s1, s2, s3) \
-do { \
-	static const char *const list[] = { s1, s2, s3, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS4(s1, s2, s3, s4) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS5(s1, s2, s3, s4, s5) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define ADDLIST1(s1) additional_kw_query(text, 1, s1)
-#define ADDLIST2(s1, s2) additional_kw_query(text, 2, s1, s2)
-#define ADDLIST3(s1, s2, s3) additional_kw_query(text, 3, s1, s2, s3)
-#define ADDLIST4(s1, s2, s3, s4) \
-	additional_kw_query(text, 4, s1, s2, s3, s4)
-#define ADDLIST13(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
-	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
-						s8, s9, s10, s11, s12, s13)
-#define ADDLIST15(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
-	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
-						s8, s9, s10, s11, s12, s13, s14, s15)
-
-/*
  * Assembly instructions for schema queries
  */
 
@@ -993,9 +790,11 @@ static PGresult *exec_query(const char *query);
 
 static char **get_previous_words(int point, char **buffer, int *nwords);
 
-static char *get_guctype(const char *varname);
+static bool get_guctype(char *buf, size_t len, const char *varname);
 
 static const pgsql_thing_t *find_thing_entry(char *word);
+static char **psql_completion_internal(const char *text, char **previous_words,
+										   int previous_words_count);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -1151,235 +950,60 @@ psql_completion(const char *text, int start, int end)
 	/* The number of words found on the input line. */
 	int			previous_words_count;
 
-	/* The number of prefixing words to be ignored */
-	int			head_shift = 0;
+	(void) end;					/* "end" is not used */
 
-	/*
-	 * For compactness, we use these macros to reference previous_words[].
-	 * Caution: do not access a previous_words[] entry without having checked
-	 * previous_words_count to be sure it's valid.  In most cases below, that
-	 * check is implicit in a TailMatches() or similar macro, but in some
-	 * places we have to check it explicitly.
-	 */
-#define prev_wd   (previous_words[0])
-#define prev2_wd  (previous_words[1])
-#define prev3_wd  (previous_words[2])
-#define prev4_wd  (previous_words[3])
-#define prev5_wd  (previous_words[4])
-#define prev6_wd  (previous_words[5])
-#define prev7_wd  (previous_words[6])
-#define prev8_wd  (previous_words[7])
-#define prev9_wd  (previous_words[8])
-
-	/* Move the position of the beginning word for matching macros.  */
-#define HEADSHIFT(n) \
-	((head_shift += n) || true)
-
-	/* Return the number of stored words counting head shift */
-#define WORD_COUNT() (previous_words_count - head_shift)
-
-	/* Return the true index in previous_words for index from the beginning */
-#define HEAD_INDEX(n) \
-	(previous_words_count - head_shift - (n))
+#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
+	rl_completion_append_character = ' ';
+#endif
 
-	/*
-	 * remove n words from current shifted position, see MidMatchAndRevmove2
-	 * for the reason for the return value
-	 */
-#define COLLAPSE(n) \
-	((memmove(previous_words + HEAD_INDEX(n), previous_words + HEAD_INDEX(0), \
-			 sizeof(char *) * head_shift), \
-	 previous_words_count -= (n)) && false)
+	/* Clear a few things. */
+	if (completion_charp == NULL)
+		completion_charp = createPQExpBuffer();
+	completion_charpp = NULL;
+	completion_info_charp = NULL;
+	completion_info_charp2 = NULL;
 
 	/*
-	 * Find the position the specified word occurs last and shift to there.
-	 * This is used to ignore the words before there.
+	 * Scan the input line to extract the words before our current position.
+	 * According to those we'll make some smart decisions on what the user is
+	 * probably intending to type.
 	 */
-#define SHIFT_TO_LAST1(p1) \
-	(HEADSHIFT(find_last_index_of(p1, previous_words, previous_words_count))|| \
-	 true)
+	previous_words = get_previous_words(start,
+										&words_buffer,
+										&previous_words_count);
 
-	/*
-	 * Remove the specified words if they match from the sth word in
-	 * previous_words. This is a bit tricky, COLLAPSE is skipped when
-	 * HeadMatches failed but the last HEADSHIFT anyway should be done.
-	 */
-#define MidMatchAndRemove1(s, p1) \
-	((HEADSHIFT(s) && HeadMatches1(p1) && COLLAPSE(1)) || HEADSHIFT(-s))
-
-#define MidMatchAndRemove2(s, p1, p2) \
-	((HEADSHIFT(s) && HeadMatches2(p1, p2) && COLLAPSE(2)) || HEADSHIFT(-s))
-
-#define MidMatchAndRemove3(s, p1, p2, p3)									\
-	((HEADSHIFT(s) && HeadMatches3(p1, p2, p3) && COLLAPSE(3)) || HEADSHIFT(-s))
-
-	/* Macros for matching the last N words before point, case-insensitively. */
-#define TailMatches1(p1) \
-	(WORD_COUNT() >= 1 && \
-	 word_matches(p1, prev_wd))
-
-#define TailMatches2(p2, p1) \
-	(WORD_COUNT() >= 2 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd))
-
-#define TailMatches3(p3, p2, p1) \
-	(WORD_COUNT() >= 3 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd))
-
-#define TailMatches4(p4, p3, p2, p1) \
-	(WORD_COUNT() >= 4 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd))
-
-#define TailMatches5(p5, p4, p3, p2, p1) \
-	(WORD_COUNT() >= 5 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd))
-
-#define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(WORD_COUNT() >= 6 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd))
-
-#define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(WORD_COUNT() >= 7 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd))
-
-#define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(WORD_COUNT() >= 8 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd) && \
-	 word_matches(p8, prev8_wd))
-
-#define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(WORD_COUNT() >= 9 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd) && \
-	 word_matches(p8, prev8_wd) && \
-	 word_matches(p9, prev9_wd))
-
-	/* Macros for matching the last N words before point, case-sensitively. */
-#define TailMatchesCS1(p1) \
-	(WORD_COUNT() >= 1 && \
-	 word_matches_cs(p1, prev_wd))
-#define TailMatchesCS2(p2, p1) \
-	(WORD_COUNT() >= 2 && \
-	 word_matches_cs(p1, prev_wd) && \
-	 word_matches_cs(p2, prev2_wd))
+	matches  = psql_completion_internal(text, previous_words,
+										previous_words_count);
 
-	/*
-	 * Macros for matching N words exactly to the line,
-	 * case-insensitively.
-	 */
-#define Matches1(p1) \
-	(WORD_COUNT() == 1 && \
-	 TailMatches1(p1))
-#define Matches2(p1, p2) \
-	(WORD_COUNT() == 2 && \
-	 TailMatches2(p1, p2))
-#define Matches3(p1, p2, p3) \
-	(WORD_COUNT() == 3 && \
-	 TailMatches3(p1, p2, p3))
-#define Matches4(p1, p2, p3, p4) \
-	(WORD_COUNT() == 4 && \
-	 TailMatches4(p1, p2, p3, p4))
-#define Matches5(p1, p2, p3, p4, p5) \
-	(WORD_COUNT() == 5 && \
-	 TailMatches5(p1, p2, p3, p4, p5))
-#define Matches6(p1, p2, p3, p4, p5, p6) \
-	(WORD_COUNT() == 6 && \
-	 TailMatches6(p1, p2, p3, p4, p5, p6))
-#define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(WORD_COUNT() == 7 && \
-	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
-#define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(WORD_COUNT() == 8 && \
-	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
-#define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(WORD_COUNT() == 9 && \
-	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
+	/* free storage */
+	free(previous_words);
+	free(words_buffer);
+
+	if (matches != NULL)
+		return matches;
 
 	/*
-	 * Macros for matching N words at the start of the line, regardless of
-	 * what is after them, case-insensitively.
+	 * If we still don't have anything to match we have to fabricate some sort
+	 * of default list. If we were to just return NULL, readline automatically
+	 * attempts filename completion, and that's usually no good.
 	 */
-#define HeadMatches1(p1) \
-	(HEAD_INDEX(1) >=0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]))
-
-#define HeadMatches2(p1, p2) \
-	(HEAD_INDEX(2) >= 0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]) &&	\
-	 word_matches(p2, previous_words[HEAD_INDEX(2)]))
-
-#define HeadMatches3(p1, p2, p3) \
-	(HEAD_INDEX(3) >= 0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
-	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
-	 word_matches(p3, previous_words[HEAD_INDEX(3)]))
-
-#define HeadMatches4(p1, p2, p3, p4) \
-	(HEAD_INDEX(4) >= 0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
-	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
-	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
-	 word_matches(p4, previous_words[HEAD_INDEX(4)]))
-
-#define HeadMatches5(p1, p2, p3, p4, p5) \
-	(HEAD_INDEX(5) >= 0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
-	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
-	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
-	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
-	 word_matches(p5, previous_words[HEAD_INDEX(5)]))
-
-#define HeadMatches6(p1, p2, p3, p4, p5, p6)		\
-	(HEAD_INDEX(6) >= 0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
-	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
-	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
-	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
-	 word_matches(p5, previous_words[HEAD_INDEX(5)]) && \
-	 word_matches(p6, previous_words[HEAD_INDEX(6)]))
-
-#define HeadMatches7(p1, p2, p3, p4, p5, p6, p7)	\
-	(HEAD_INDEX(7) >= 0 && \
-	 word_matches(p1, previous_words[HEAD_INDEX(1)]) && \
-	 word_matches(p2, previous_words[HEAD_INDEX(2)]) && \
-	 word_matches(p3, previous_words[HEAD_INDEX(3)]) && \
-	 word_matches(p4, previous_words[HEAD_INDEX(4)]) && \
-	 word_matches(p5, previous_words[HEAD_INDEX(5)]) && \
-	 word_matches(p6, previous_words[HEAD_INDEX(6)]) && \
-	 word_matches(p7, previous_words[HEAD_INDEX(7)]))
+#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
+	rl_completion_append_character = '\0';
+#endif
+	COMPLETE_WITH_CONST("");		/* This returns matches */
+}
 
+/*
+ * The completion function.
+ *
+ * Makes completion list. previous_words may be modified on the way of making
+ * the returning list. Note that COMPLETE_WITH_* macros immediately return to
+ * the caller of this function.
+ */
+static char **
+psql_completion_internal(const char *text, char **previous_words,
+						 int previous_words_count)
+{
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
@@ -1411,104 +1035,122 @@ psql_completion(const char *text, int start, int end)
 		"\\timing", "\\unset", "\\x", "\\w", "\\watch", "\\z", "\\!", NULL
 	};
 
-	(void) end;					/* "end" is not used */
-
-#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
-	rl_completion_append_character = ' ';
-#endif
-
-	/* Clear a few things. */
-	if (completion_charp == NULL)
-		completion_charp = createPQExpBuffer();
-	completion_charpp = NULL;
-	completion_info_charp = NULL;
-	completion_info_charp2 = NULL;
-
-	/*
-	 * Scan the input line to extract the words before our current position.
-	 * According to those we'll make some smart decisions on what the user is
-	 * probably intending to type.
-	 */
-	previous_words = get_previous_words(start,
-										&words_buffer,
-										&previous_words_count);
+	/* The number of prefixing words to be ignored */
+	int			head_shift = 0;
 
 	/* If current word is a backslash command, offer completions for that */
 	if (text[0] == '\\')
 		COMPLETE_WITH_LIST_CS(backslash_commands);
 
 	/* If current word is a variable interpolation, handle that case */
-	else if (text[0] == ':' && text[1] != ':')
+	if (text[0] == ':' && text[1] != ':')
 	{
+		char	  **matches = NULL;
+
 		if (text[1] == '\'')
 			matches = complete_from_variables(text, ":'", "'", true);
 		else if (text[1] == '"')
 			matches = complete_from_variables(text, ":\"", "\"", true);
 		else
 			matches = complete_from_variables(text, ":", "", true);
+
+		return matches;
 	}
 
 	/* If no previous word, suggest one of the basic sql commands */
-	else if (previous_words_count == 0)
+	if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
 	/*
 	 * If this is in CREATE SCHEMA, seek to the last CREATE and regard it as
 	 * current command to complete.
 	 */
-	else if (HeadMatches2("CREATE", "SCHEMA") &&
-			 SHIFT_TO_LAST1("CREATE") &&
-			 false) {} /* FALL THROUGH */
+	if (HeadMatches2("CREATE", "SCHEMA"))
+	{
+		int n;
+
+		/* CREATE SCHEMA <name> */
+		if (Matches2("CREATE", "SCHEMA"))
+			COMPLETE_WITH_QUERY(Query_for_list_of_schemas,
+								ADDLIST1("IF NOT EXISTS"));
+		/* Remove optional words IF NOT EXISTS */
+		if (HeadMatches2("CREATE", "SCHEMA") &&
+			MidMatches3(3, "IF", "NOT", "EXISTS"))
+			COLLAPSE(3, 3);
+
+		if (Matches2("CREATE", "SCHEMA"))
+			COMPLETE_THING(-1);
+		
+		/* Else, move head match point past CREATE SCHEMA and go through */
+		if ((n = find_last_index_of("CREATE",
+									previous_words, previous_words_count)) > 0)
+		SHIFTHEAD(n);
+	}
+
 /* CREATE */
 	/* complete with something you can create */
-	else if (Matches1("CREATE"))
-		matches = completion_matches(text, create_command_generator);
+	if (Matches1("CREATE"))
+		return completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
-	else if (Matches1("DROP"))
-		matches = completion_matches(text, drop_command_generator);
+	if (Matches1("DROP"))
+		return completion_matches(text, drop_command_generator);
 
 /* ALTER */
 
-	/* ALTER TABLE */
-	else if (Matches2("ALTER", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-			ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
-
-	/* ALTER TABLE after removing optional words IF EXISTS*/
-	else if (HeadMatches2("ALTER", "TABLE") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches2("ALTER", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
-
 	/* ALTER something */
-	else if (Matches1("ALTER"))
+	if (Matches1("ALTER"))
 	{
 		static const char *const list_ALTER[] =
-		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
-			"EVENT TRIGGER", "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
-			"GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW", "OPERATOR",
-			"POLICY", "ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM", "TABLE",
-			"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
-		"USER", "USER MAPPING FOR", "VIEW", NULL};
+		{"AGGREGATE",
+		 "COLLATION", "CONVERSION",
+		 "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
+		 "EVENT TRIGGER", "EXTENSION",
+		 "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
+		 "GROUP",
+		 "INDEX",
+		 "LANGUAGE", "LARGE OBJECT",
+		 "MATERIALIZED VIEW", 
+		 "OPERATOR",
+		 "POLICY",
+		 "ROLE", "RULE",
+		 "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM",
+		 "TABLE", "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
+		 "USER", "USER MAPPING FOR",
+		 "VIEW",
+		 NULL};
 
 		COMPLETE_WITH_LIST(list_ALTER);
 	}
+	/* ALTER TABLE */
+	if (Matches2("ALTER", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+			ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
+
+	/* Remove optional words IF EXISTS just after ALTER TABLE */
+	if (HeadMatches2("ALTER", "TABLE") &&
+		MidMatches2(3, "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+
+	/* ALTER TABLE */
+	if (Matches2("ALTER", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx */
-	else if (TailMatches4("ALL", "IN", "TABLESPACE", MatchAny))
+	if (TailMatches4("ALL", "IN", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("SET TABLESPACE", "OWNED BY");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY */
-	else if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
+	if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY xxx */
-	else if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
+	if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("SET TABLESPACE");
 	/* ALTER AGGREGATE,FUNCTION <name> */
-	else if (Matches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
+	if (Matches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION <name> (...) */
-	else if (Matches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
+	if (Matches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
 	{
 		if (ends_with(prev_wd, ')'))
 			COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
@@ -1517,74 +1159,76 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER SCHEMA <name> */
-	else if (Matches3("ALTER", "SCHEMA", MatchAny))
+	if (Matches3("ALTER", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER TO", "RENAME TO");
 
 	/* ALTER COLLATION <name> */
-	else if (Matches3("ALTER", "COLLATION", MatchAny))
+	if (Matches3("ALTER", "COLLATION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER CONVERSION <name> */
-	else if (Matches3("ALTER", "CONVERSION", MatchAny))
+	if (Matches3("ALTER", "CONVERSION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER DATABASE <name> */
-	else if (Matches3("ALTER", "DATABASE", MatchAny))
+	if (Matches3("ALTER", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST7("RESET", "SET", "OWNER TO", "RENAME TO",
 							"IS_TEMPLATE", "ALLOW_CONNECTIONS",
 							"CONNECTION LIMIT");
 
 	/* ALTER EVENT TRIGGER */
-	else if (Matches3("ALTER", "EVENT", "TRIGGER"))
+	if (Matches3("ALTER", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
+	if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST4("DISABLE", "ENABLE", "OWNER TO", "RENAME TO");
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
+	if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST2("REPLICA", "ALWAYS");
 
 	/* ALTER EXTENSION <name> */
-	else if (Matches3("ALTER", "EXTENSION", MatchAny))
+	if (Matches3("ALTER", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST4("ADD", "DROP", "UPDATE", "SET SCHEMA");
 
 	/* ALTER EXTENSION <name> UPDATE */
-	else if (Matches4("ALTER", "EXTENSION", MatchAny, "UPDATE"))
+	if (Matches4("ALTER", "EXTENSION", MatchAny, "UPDATE"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions_with_TO,"");
 	}
 
 	/* ALTER EXTENSION <name> UPDATE TO */
-	else if (Matches5("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
+	if (Matches5("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions, "");
 	}
 
 	/* ALTER FOREIGN */
-	else if (Matches2("ALTER", "FOREIGN"))
+	if (Matches2("ALTER", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
 	/* ALTER FOREIGN TABLE */
-	else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
+	if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
 								   ADDLIST1("IF EXISTS"));
 
 	/* ALTER|DROP FOREIGN TABLE after removing optinal words IF EXISTS */
-	else if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
-			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
-			 Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
+	if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") &&
+		MidMatches2(4, "IF", "EXISTS"))
+		COLLAPSE(4, 2);
+
+	if (Matches3("ALTER|DROP", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 
 	/* ALTER FOREIGN TABLE <name> */
-	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
+	if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
 		{"ADD", "ALTER", "DISABLE TRIGGER", "DROP", "ENABLE", "INHERIT",
@@ -1595,58 +1239,62 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER FOREIGN TABLE xxx RENAME */
-	else if (Matches5("ALTER", "FOREIGN", "TABLE", MatchAny, "RENAME"))
+	if (Matches5("ALTER", "FOREIGN", "TABLE", MatchAny, "RENAME"))
 		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST2("COLUMN", "TO"));
 
 	/* ALTER INDEX */
-	else if (Matches2("ALTER", "INDEX"))
+	if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 			   ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
 	/* ALTER INDEX after removing optional words IF EXISTS */
-	else if (HeadMatches2("ALTER", "INDEX") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches2("ALTER", "INDEX"))
+	if (HeadMatches2("ALTER", "INDEX") &&
+		MidMatches2(3, "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+
+	if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 
 	/* ALTER INDEX <name> */
-	else if (Matches3("ALTER", "INDEX", MatchAny))
+	if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
 	/* ALTER INDEX <name> SET */
-	else if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
+	if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
 		COMPLETE_WITH_LIST2("(", "TABLESPACE");
 	/* ALTER INDEX <name> RESET */
-	else if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
+	if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER INDEX <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
+	if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
 		COMPLETE_WITH_LIST3("fillfactor", "fastupdate",
 							"gin_pending_list_limit");
-	else if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
+	if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST3("fillfactor =", "fastupdate =",
 							"gin_pending_list_limit =");
 
 	/* ALTER LANGUAGE <name> */
-	else if (Matches3("ALTER", "LANGUAGE", MatchAny))
+	if (Matches3("ALTER", "LANGUAGE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER_TO", "RENAME TO");
 
 	/* ALTER LARGE OBJECT <oid> */
-	else if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
+	if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
 		COMPLETE_WITH_CONST("OWNER TO");
 
 	/* ALTER MATERIALIZED VIEW */
-	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
+	if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 			   ADDLIST2("IF EXISTS", "ALL IN TABLESPACE"));
 
 	/* ALTER MATERIALIZED VIEW with name after removing optional words */
-	else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
-			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
-			 Matches3("ALTER", "MATERIALIZED", "VIEW"))
+	if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") &&
+		MidMatches2(4, "IF", "EXISTS"))
+		COLLAPSE(4, 2);
+
+	if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 
 	/* ALTER USER,ROLE <name> */
-	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
-			 !TailMatches2("USER", "MAPPING"))
+	if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
+		!TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_ALTERUSER[] =
 		{"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -1660,7 +1308,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER USER,ROLE <name> WITH */
-	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
+	if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_ALTERUSER_WITH[] =
@@ -1675,63 +1323,67 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
-	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	if (Matches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* ALTER DEFAULT PRIVILEGES */
-	else if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
+	if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
 		COMPLETE_WITH_LIST3("FOR ROLE", "FOR USER", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR */
-	else if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
+	if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
 		COMPLETE_WITH_LIST2("ROLE", "USER");
 	/* ALTER DEFAULT PRIVILEGES { FOR ROLE ... | IN SCHEMA ... } */
-	else if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER", MatchAny) ||
+	if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER", MatchAny) ||
 		Matches6("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("GRANT", "REVOKE");
 	/* ALTER DOMAIN <name> */
-	else if (Matches3("ALTER", "DOMAIN", MatchAny))
+	if (Matches3("ALTER", "DOMAIN", MatchAny))
 		COMPLETE_WITH_LIST6("ADD", "DROP", "OWNER TO", "RENAME", "SET",
 							"VALIDATE CONSTRAINT");
 	/* ALTER DOMAIN <sth> DROP */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
 	/* ALTER DOMAIN <sth> RENAME|VALIDATE CONSTRAINT */
-	else if (Matches5("ALTER", "DOMAIN", MatchAny, "RENAME|VALIDATE", "CONSTRAINT"))
+	if (Matches5("ALTER", "DOMAIN", MatchAny, "RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, "");
 	}
 	/* ALTER DOMAIN <sth> DROP CONSTRAINT */
-	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
+	if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type,
 							ADDLIST1("IF EXISTS"));
 	}
 	/* Try the same match after removing optional words IF EXISTS */
-	else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
-			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
-			 Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
+	if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") &&
+		MidMatches2(6, "IF", "EXISTS"))
+		COLLAPSE(6, 2);
+
+	if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, "");
 	}
 	/* ALTER DOMAIN <sth> RENAME */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("CONSTRAINT", "TO");
 	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
-	else if (Matches6("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
+	if (Matches6("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER DOMAIN <sth> SET */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
-	else if (Matches2("ALTER", "SEQUENCE"))
+	if (Matches2("ALTER", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
 								   ADDLIST1("IF EXISTS"));
 	/* ALTER SEQUENCE with name after removing optional words IF EXISTS */
-	else if (HeadMatches2("ALTER", "SEQUENCE") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches3("ALTER", "SEQUENCE", MatchAny))
+	if (HeadMatches2("ALTER", "SEQUENCE") &&
+		MidMatches2(3, "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+
+	if (Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1740,83 +1392,87 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
 	/* ALTER SEQUENCE <name> NO */
-	else if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
+	if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 	/* ALTER SERVER <name> */
-	else if (Matches3("ALTER", "SERVER", MatchAny))
+	if (Matches3("ALTER", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST4("VERSION", "OPTIONS", "OWNER TO", "RENAME TO");
 	/* ALTER SERVER <name> VERSION <version> */
-	else if (Matches5("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
+	if (Matches5("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
 		COMPLETE_WITH_CONST("OPTIONS");
 	/* ALTER SYSTEM SET, RESET, RESET ALL */
-	else if (Matches2("ALTER", "SYSTEM"))
+	if (Matches2("ALTER", "SYSTEM"))
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
-	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
+	if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars, "");
 	/* ALTER VIEW */
-	else if (Matches2("ALTER", "VIEW"))
+	if (Matches2("ALTER", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
 								   ADDLIST1("IF EXISTS"));
 	/* ALTER VIEW <name> with subcommands after removing optional worlds */
-	else if (HeadMatches2("ALTER", "VIEW") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches3("ALTER", "VIEW", MatchAny))
+	if (HeadMatches2("ALTER", "VIEW") &&
+		MidMatches2(3, "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+
+	if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
-	else if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
 	/* ALTER POLICY */
-	else if (Matches2("ALTER", "POLICY"))
+	if (Matches2("ALTER", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies,
 							ADDLIST1("IF EXISTS"));
 	/* ALTER POLICY <name> with ON after removing optional words IF EXISTS */
-	else if (HeadMatches2("ALTER", "POLICY") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches3("ALTER", "POLICY", MatchAny))
+	if (HeadMatches2("ALTER", "POLICY") &&
+		MidMatches2(3, "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+
+	if (Matches3("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
-	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
+	if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, "");
 	}
 	/* ALTER POLICY <name> ON <table> - show options */
-	else if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING (", "WITH CHECK (");
 	/* ALTER POLICY <name> ON <table> TO <role> */
-	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 	/* ALTER POLICY <name> ON <table> USING ( */
-	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
-	else if (Matches7("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
+	if (Matches7("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
 		COMPLETE_WITH_CONST("(");
 
 	/* ALTER RULE <name>, add ON */
-	else if (Matches3("ALTER", "RULE", MatchAny))
+	if (Matches3("ALTER", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
-	else if (Matches4("ALTER", "RULE", MatchAny, "ON"))
+	if (Matches4("ALTER", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, "");
 	}
 
 	/* ALTER RULE <name> ON <name> */
-	else if (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/* ALTER TRIGGER <name>, add ON */
-	else if (Matches3("ALTER", "TRIGGER", MatchAny))
+	if (Matches3("ALTER", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
-	else if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
+	if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, "");
@@ -1825,17 +1481,17 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
-	else if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
+	if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* ALTER TRIGGER <name> ON <name> */
-	else if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/*
 	 * If we detect ALTER TABLE <name>, suggest sub commands
 	 */
-	else if (Matches3("ALTER", "TABLE", MatchAny))
+	if (Matches3("ALTER", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER2[] =
 		{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
@@ -1845,97 +1501,103 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
 	/* ALTER TABLE xxx ENABLE */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST5("ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE",
 							"TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
 		COMPLETE_WITH_LIST2("RULE", "TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 	/* ALTER TABLE xxx INHERIT */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx NO INHERIT */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx DISABLE */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
 		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 
 	/* ALTER TABLE xxx ALTER */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
 		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST2("COLUMN", "CONSTRAINT"));
 
 	/* ALTER TABLE xxx RENAME */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
 		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST3("COLUMN", "CONSTRAINT", "TO"));
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/* ALTER [FOREIGN] TABLE xxx RENAME yyy */
-	else if ((HeadMatches2("ALTER", "TABLE") ||
+	if ((HeadMatches2("ALTER", "TABLE") ||
 			  HeadMatches3("ALTER", "FOREIGN", "TABLE")) &&
 			 TailMatches2("RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
-	else if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
+	if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* If we have ALTER TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/*  ALTER TABLE DROP COLUMN may take IF EXISTS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
 	/* ALTER TABLE <name> with ADD/ALTER/DROP after removing optional words */
-	else if (HeadMatches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP") &&
-			 MidMatchAndRemove1(4, "COLUMN") &&
-			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
-			 Matches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP"))
+	if (HeadMatches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP"))
+	{
+		if (MidMatches1(5, "COLUMN"))
+			COLLAPSE(5, 1);
+		if (MidMatches2(5, "IF", "EXISTS"))
+			COLLAPSE(5, 2);
+	}
+	if (Matches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
-	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN") &&
-			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
-			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+	if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN") &&
+		MidMatches2(6, "IF", "EXISTS"))
+		COLLAPSE(6, 2);
+
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME|VALIDATE", "CONSTRAINT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, "");
@@ -1944,49 +1606,51 @@ psql_completion(const char *text, int start, int end)
 	 * If we have ALTER TABLE <sth> DROP CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table,
 							ADDLIST1("IF EXISTS"));
 	}
 	/* Try the same after removing optional words IF EXISTS */
-	else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
-			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
-			 Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
+	if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") &&
+		MidMatches2(6, "IF", "EXISTS"))
+		COLLAPSE(6, 2);
+
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, "");
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
+	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
 			 Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
 		COMPLETE_WITH_LIST4("TYPE", "SET", "RESET", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
 			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
 		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
+	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
 		 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
+	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
 	Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
 		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
 			 Matches8("ALTER", "TABLE", MatchAny, "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("DEFAULT", "NOT NULL");
-	else if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
 	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
 		COMPLETE_WITH_LIST7("(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED",
 							"WITH", "WITHOUT");
 
@@ -1994,19 +1658,19 @@ psql_completion(const char *text, int start, int end)
 	 * If we have ALTER TABLE <sth> SET TABLESPACE provide a list of
 	 * tablespaces
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
 	/* If we have ALTER TABLE <sth> SET WITH provide OIDS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
 		COMPLETE_WITH_CONST("OIDS");
 	/* If we have ALTER TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
 		COMPLETE_WITH_LIST2("CLUSTER", "OIDS");
 	/* ALTER TABLE <foo> RESET */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLE <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
 	{
 		static const char *const list_TABLEOPTIONS[] =
 		{
@@ -2044,120 +1708,117 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
-	else if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
+	if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
 		COMPLETE_WITH_LIST4("FULL", "NOTHING", "DEFAULT", "USING");
-	else if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
 		COMPLETE_WITH_CONST("IDENTITY");
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
-	else if (Matches3("ALTER", "TABLESPACE", MatchAny))
+	if (Matches3("ALTER", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "OWNER TO", "SET", "RESET");
 	/* ALTER TABLESPACE <foo> SET|RESET */
-	else if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
+	if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
+	if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
 		COMPLETE_WITH_LIST3("seq_page_cost", "random_page_cost",
 							"effective_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
-	else if (Matches3("ALTER", "TEXT", "SEARCH"))
+	if (Matches3("ALTER", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
 		COMPLETE_WITH_LIST2("RENAME TO", "SET SCHEMA");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING",
 							"DROP MAPPING",	"OWNER TO", "RENAME TO", "SET SCHEMA");
-	else if (Matches7("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny, "DROP", "MAPPING"))
+	if (Matches7("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny, "DROP", "MAPPING"))
 		COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR");
-	/* Remove optional words IF EXISTS */
-	else if (HeadMatches7("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny, "DROP", "MAPPING") &&
-			 MidMatchAndRemove2(7, "IF", "EXISTS") &&
-			 false) {} /* Nothing to do for now */
 
 	/* complete ALTER TYPE <foo> with actions */
-	else if (Matches3("ALTER", "TYPE", MatchAny))
+	if (Matches3("ALTER", "TYPE", MatchAny))
 		COMPLETE_WITH_LIST7("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE",
 							"DROP ATTRIBUTE",
 							"OWNER TO", "RENAME", "SET SCHEMA");
 	/* complete ALTER TYPE <foo> ADD with actions */
-	else if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
+	if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
-	else if (Matches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE"))
+	if (Matches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE"))
 		COMPLETE_WITH_LIST2("IF NOT EXISTS", "");
 	/* Remove optional words IF NOT EXISTS */
-	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE") &&
-			 MidMatchAndRemove3(5, "IF", "NOT", "EXISTS") &&
-			 false) {} /* Nothing to do for now */
+	if (HeadMatches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE") &&
+		MidMatches2(6, "IF", "EXISTS"))
+		COLLAPSE(6, 2);
 	/* ALTER TYPE <foo> RENAME	*/
-	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "TO");
 	/* ALTER TYPE xxx RENAME ATTRIBUTE yyy */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE", MatchAny))
+	if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/*
 	 * If we have ALTER TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list
 	 * of attributes
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|RENAME", "ATTRIBUTE"))
+	if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
-	else if (Matches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE"))
+	if (Matches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
 	/* Remove optional words IF EXISTS */
-	else if (HeadMatches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE") &&
-			 MidMatchAndRemove2(5, "IF", "EXISTS") &&
+	if (HeadMatches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE") &&
+		MidMatches2(6, "IF", "EXISTS"))
+		COLLAPSE(6, 2);
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-			 Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
+	if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* complete ALTER GROUP <foo> */
-	else if (Matches3("ALTER", "GROUP", MatchAny))
+	if (Matches3("ALTER", "GROUP", MatchAny))
 		COMPLETE_WITH_LIST3("ADD USER", "DROP USER", "RENAME TO");
 	/* complete ALTER GROUP <foo> ADD|DROP with USER */
-	else if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
+	if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
 		COMPLETE_WITH_CONST("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
-	else if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
+	if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* BEGIN */
-	else if (Matches1("BEGIN"))
+	if (Matches1("BEGIN"))
 		COMPLETE_WITH_LIST6("WORK", "TRANSACTION", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
 /* END, ABORT */
-	else if (Matches1("END|ABORT"))
+	if (Matches1("END|ABORT"))
 		COMPLETE_WITH_LIST2("WORK", "TRANSACTION");
 /* COMMIT */
-	else if (Matches1("COMMIT"))
+	if (Matches1("COMMIT"))
 		COMPLETE_WITH_LIST3("WORK", "TRANSACTION", "PREPARED");
 /* RELEASE SAVEPOINT */
-	else if (Matches1("RELEASE"))
+	if (Matches1("RELEASE"))
 		COMPLETE_WITH_CONST("SAVEPOINT");
 /* ROLLBACK */
-	else if (Matches1("ROLLBACK"))
+	if (Matches1("ROLLBACK"))
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
-	else if (Matches1("CLUSTER"))
+	if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   ADDLIST1("VERBOSE"));
-	else if (Matches2("CLUSTER", "VERBOSE"))
+	if (Matches2("CLUSTER", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 	/* If we have CLUSTER <sth>, then add "USING" */
-	else if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
+	if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	else if (Matches3("CLUSTER", "VERBOSE", MatchAny))
+	if (Matches3("CLUSTER", "VERBOSE", MatchAny))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER <sth> USING, then add the index as well */
-	else if (Matches3("CLUSTER", MatchAny, "USING") ||
+	if (Matches3("CLUSTER", MatchAny, "USING") ||
 			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
@@ -2165,9 +1826,9 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* COMMENT */
-	else if (Matches1("COMMENT"))
+	if (Matches1("COMMENT"))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches2("COMMENT", "ON"))
+	if (Matches2("COMMENT", "ON"))
 	{
 		static const char *const list_COMMENT[] =
 		{"ACCESS METHOD", "CAST", "COLLATION", "CONVERSION", "DATABASE",
@@ -2180,26 +1841,26 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
-	else if (Matches4("COMMENT", "ON", "ACCESS", "METHOD"))
+	if (Matches4("COMMENT", "ON", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, "");
-	else if (Matches3("COMMENT", "ON", "FOREIGN"))
+	if (Matches3("COMMENT", "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
-	else if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
+	if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches3("COMMENT", "ON", "CONSTRAINT"))
+	if (Matches3("COMMENT", "ON", "CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_all_table_constraints, "");
-	else if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
+	if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
+	if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint, "");
 	}
-	else if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
+	if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
-	else if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
+	if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
-	else if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
+	if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
 		Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
 			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
 		COMPLETE_WITH_CONST("IS");
@@ -2210,274 +1871,278 @@ psql_completion(const char *text, int start, int end)
 	 * If we have COPY, offer list of tables or "(" (Also cover the analogous
 	 * backslash command).
 	 */
-	else if (Matches1("COPY|\\copy"))
+	if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   ADDLIST1("("));
 	/* If we have COPY BINARY, complete with list of tables */
-	else if (Matches2("COPY", "BINARY"))
+	if (Matches2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* If we have COPY (, complete it with legal commands */
-	else if (Matches2("COPY|\\copy", "("))
+	if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
 	/* If we have COPY [BINARY] <sth>, complete it with "TO" or "FROM" */
-	else if (Matches2("COPY|\\copy", MatchAny) ||
+	if (Matches2("COPY|\\copy", MatchAny) ||
 			 Matches3("COPY", "BINARY", MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "TO");
 	/* If we have COPY [BINARY] <sth> FROM|TO, complete with filename */
-	else if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
+	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
 			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
 	{
-		SET_COMP_CHARP("");
-		matches = completion_matches(text, complete_from_files);
+		SET_COMPLETION_CHARP("");
+		return completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename */
-	else if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
+	if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
 			 Matches5("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
 		COMPLETE_WITH_LIST6("BINARY", "OIDS", "DELIMITER", "NULL", "CSV",
 							"ENCODING");
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename CSV */
-	else if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
+	if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
 			 Matches6("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny, "CSV"))
 		COMPLETE_WITH_LIST5("HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE",
 							"FORCE NOT NULL");
 
 	/* CREATE ACCESS METHOD */
 	/* Complete "CREATE ACCESS METHOD <name>" */
-	else if (Matches4("CREATE", "ACCESS", "METHOD", MatchAny))
+	if (Matches4("CREATE", "ACCESS", "METHOD", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE" */
-	else if (Matches5("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
+	if (Matches5("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
 		COMPLETE_WITH_CONST("INDEX");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE <type>" */
-	else if (Matches6("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
+	if (Matches6("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
 		COMPLETE_WITH_CONST("HANDLER");
 
 	/* CREATE DATABASE */
-	else if (Matches3("CREATE", "DATABASE", MatchAny))
+	if (Matches3("CREATE", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST9("OWNER", "TEMPLATE", "ENCODING", "TABLESPACE",
 							"IS_TEMPLATE",
 							"ALLOW_CONNECTIONS", "CONNECTION LIMIT",
 							"LC_COLLATE", "LC_CTYPE");
 
-	else if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
+	if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases, "");
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
-	else if (Matches2("CREATE", "EXTENSION"))
+	if (Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions,
 							ADDLIST1("IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS */
-	else if (HeadMatches2("CREATE", "EXTENSION") &&
-			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
-			 Matches2("CREATE", "EXTENSION"))
+	if (HeadMatches2("CREATE", "EXTENSION") &&
+		MidMatches3(3, "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+
+	if (Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions, "");
 	/* CREATE EXTENSION <name> */
-	else if (Matches3("CREATE", "EXTENSION", MatchAny))
+	if (Matches3("CREATE", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
 	/* CREATE EXTENSION <name> VERSION */
-	else if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
+	if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions, "");
 	}
 
 	/* CREATE FOREIGN */
-	else if (Matches2("CREATE", "FOREIGN"))
+	if (Matches2("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* CREATE FOREIGN TABLE */
-	else if (Matches3("CREATE", "FOREIGN", "TABLE"))
+	if (Matches3("CREATE", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
 								   ADDLIST1("IF NOT EXISTS"));
 	/* Remove optional words IF NOT EXISTS */
-	else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
-			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
-			 false) {} /* FALL THROUGH */
+	if (HeadMatches3("CREATE", "FOREIGN", "TABLE") &&
+		MidMatches3(4, "IF", "NOT", "EXISTS"))
+		COLLAPSE(4, 3);
+
+	if (Matches3("CREATE", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 	/* CREATE FOREIGN DATA WRAPPER */
-	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	else if (TailMatches2("CREATE", "UNIQUE"))
+	if (TailMatches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
-	/* Remove optional word UNIQUE from CREATE UNIQUE INDEX */
-	else if (HeadMatches3("CREATE", MatchAny, "INDEX") &&
-			 MidMatchAndRemove1(1, "UNIQUE") &&
-			 false) {} /* FALL THROUGH */
-	/* If we have CREATE INDEX, then add "ON", "CONCURRENTLY" or IF NOT EXISTS,
-	   and existing indexes */
-	else if (Matches2("CREATE", "INDEX"))
+	/*
+	 * Complete CREATE INDEX with "ON", "CONCURRENTLY" or IF NOT EXISTS, and
+	 * existing indexes, after removing optional words
+	 */
+	if (HeadMatches2("CREATE", "UNIQUE") &&
+		MidMatches1(2, "UNIQUE"))
+		COLLAPSE(2, 1);
+
+	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 				   ADDLIST3("ON", "CONCURRENTLY", "IF NOT EXISTS"));
 	/* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and
 	 * existing indexes */
-	else if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
+	if (Matches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   ADDLIST2("IF NOT EXISTS", "ON"));
 
-	/* Remove optional words "CONCURRENTLY",  "IF NOT EXISTS" */
-	else if (HeadMatches2("CREATE", "INDEX") &&
-			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
-			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
-			 false) {} /* FALL THROUGH */
-
+	/* Complete CREATE INDEX after removing optional words */
+	if (HeadMatches2("CREATE", "INDEX"))
+	{
+		if (MidMatches1(3, "CONCURRENTLY"))
+			COLLAPSE(3, 1);
+		if (MidMatches3(3, "IF", "NOT", "EXISTS"))
+			COLLAPSE(3, 3);
+	}
+	if (Matches2("CREATE", "INDEX"))
+		COMPLETE_THING(-1);
 	/* Complete CREATE INDEX [<name>] ON with a list of tables */
-	else if (Matches4("CREATE", "INDEX", MatchAny, "ON") ||
+	if (Matches4("CREATE", "INDEX", MatchAny, "ON") ||
 			 Matches3("CREATE", "INDEX", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 
 	/* Complete CREATE INDEX <sth> with "ON" */
-	else if (Matches3("CREATE", "INDEX", MatchAny))
+	if (Matches3("CREATE", "INDEX", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/*
 	 * Complete INDEX <name> ON <table> with a list of table columns (which
 	 * should really be in parens)
 	 */
-	else if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
+	if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
 			 TailMatches3("INDEX", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	else if (Matches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
+	if (Matches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
 			 Matches4("INDEX", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* same if you put in USING */
-	else if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
+	if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
+	if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, "");
-	else if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
+	if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
 			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
 			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-	else if (Matches3("CREATE", "POLICY", MatchAny))
+	if (Matches3("CREATE", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
-	else if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
+	if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
-	else if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
+	if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING (", "WITH CHECK (");
 	/* CREATE POLICY <name> ON <table> FOR ALL|SELECT|INSERT|UPDATE|DELETE */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
 		COMPLETE_WITH_LIST5("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
 	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
 		COMPLETE_WITH_LIST2("TO", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> FOR SELECT|DELETE TO|USING" */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
 		COMPLETE_WITH_LIST2("TO", "USING (");
 	/* CREATE POLICY <name> ON <table> FOR ALL|UPDATE TO|USING|WITH CHECK */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
 		COMPLETE_WITH_LIST3("TO", "USING (", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS ON" */
-	else if (Matches3("CREATE", "RULE", MatchAny))
+	if (Matches3("CREATE", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("AS ON");
 	/* Complete "CREATE RULE <sth> AS" with "ON" */
-	else if (Matches4("CREATE", "RULE", MatchAny, "AS"))
+	if (Matches4("CREATE", "RULE", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE RULE <sth> AS ON" with SELECT|UPDATE|INSERT|DELETE */
-	else if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
+	if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
 	/* Complete "AS ON SELECT|UPDATE|INSERT|DELETE" with a "TO" */
-	else if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
+	if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
-	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
+	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
-/* Remove optional words TEMPORARY/TEMP */
-	else if (HeadMatches3("CREATE", MatchAny, "SEQUENCE") &&
-			 MidMatchAndRemove1(1, "TEMP|TEMPORARY") &&
-			 false) {} /* FALL THROUGH */
-/* CREATE SEQUENCE */
-	else if(Matches2("CREATE", "SEQUENCE"))
+
+/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
+	if (Matches2("CREATE", "TEMP|TEMPORARY"))
+		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
+/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
+	if (Matches2("CREATE", "UNLOGGED"))
+		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
+
+/* CREATE SEQUCNE after removing optional words TEMPORARY/TEMP */
+	if (HeadMatches1("CREATE") &&
+		MidMatches1(2, "TEMP|TEMPORARY|UNLOGGED"))
+		COLLAPSE(2, 1);
+
+	if (Matches2("CREATE", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
 								   ADDLIST1("IF NOT EXISTS"));
-	else if(HeadMatches2("CREATE", "SEQUENCE") &&
-			MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
-			Matches3("CREATE", "SEQUENCE", MatchAny))
+	if(HeadMatches2("CREATE", "SEQUENCE") &&
+	   MidMatches3(3, "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+
+	if (Matches3("CREATE", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	else if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO"))
+	if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
-	else if (Matches3("CREATE", "SERVER", MatchAny))
+	if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
-/* CREATE SCHEMA <name> */
-	else if (Matches2("CREATE", "SCHEMA"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas,
-							ADDLIST1("IF NOT EXISTS"));
-	/* Remove optional words IF NOT EXISTS */
-	else if (HeadMatches2("CREATE", "SCHEMA") &&
-			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
-			 false) {} /* FALL THROUGH*/
-
 /* CREATE TABLE  */
-	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (Matches2("CREATE", "TEMP|TEMPORARY"))
-		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
-	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	else if (Matches2("CREATE", "UNLOGGED"))
-		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
-
+	/* Optional words TEMP|TEMPORARY|UNLOGGED is already removed */
 	/* CREATE TABLE with name after removing optional words */
-	else if (HeadMatches3("CREATE", MatchAny, "TABLE") &&
-			 MidMatchAndRemove1(1, "TEMP|TEMPORARY|UNLOGGED") &&
-			 false) {} /* FALL THROUGH*/
-	else if (Matches2("CREATE", "TABLE"))
+	if (Matches2("CREATE", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   ADDLIST1("IF NOT EXISTS"));
+	/* CREATE TABLE after removing optional words here */
+	if (HeadMatches2("CREATE", "TABLE") &&
+		MidMatches3(3, "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
 
-	/* Remove optional words here */
-	else if (HeadMatches2("CREATE", "TABLE") &&
-			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
-			 false) {} /* FALL THROUGH */
-
+	if (Matches2("CREATE", "TABLE"))
+		COMPLETE_THING(-1);
 /* CREATE TABLESPACE */
-	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
+	if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
 	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
-	else if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
+	if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
 		COMPLETE_WITH_CONST("LOCATION");
 
 /* CREATE TEXT SEARCH */
-	else if (Matches3("CREATE", "TEXT", "SEARCH"))
+	if (Matches3("CREATE", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	else if (Matches3("CREATE", "TRIGGER", MatchAny))
+	if (Matches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (Matches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	if (Matches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (Matches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	if (Matches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if (Matches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+	if (Matches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
 	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
@@ -2485,17 +2150,17 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
 	 * tables
 	 */
-	else if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
+	if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
-	else if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
+	if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
 
 /* CREATE ROLE,USER,GROUP <name> */
-	else if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
+	if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_CREATEROLE[] =
@@ -2510,7 +2175,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE ROLE,USER,GROUP <name> WITH */
-	else if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
+	if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_CREATEROLE_WITH[] =
@@ -2528,84 +2193,88 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
 	 * PASSWORD
 	 */
-	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
-	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
+	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
 /* CREATE VIEW  */
-	else if (Matches2("CREATE", "VIEW"))
+	if (Matches2("CREATE", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
 								   ADDLIST1("IF NOT EXISTS"));
 	/* CREATE VIEW <name> with AS after removing optional words */
-	else if (HeadMatches2("CREATE", "VIEW") &&
-			 MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") &&
-			 Matches3("CREATE", "VIEW", MatchAny))
+	if (HeadMatches2("CREATE", "VIEW") &&
+		MidMatches3(3, "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+
+	if (Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
+	if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches2("CREATE", "MATERIALIZED"))
+	if (Matches2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
+	if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   ADDLIST1("IF NOT EXISTS"));
 	/* Try the same after removing optional words IF NOT EXISTS. VIEW will be
 	 * completed afterwards */
-	else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
-			 MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") &&
-			 Matches3("CREATE", "MATERIALIZED", "VIEW"))
+	if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") &&
+		MidMatches3(4, "IF", "NOT", "EXISTS"))
+		COLLAPSE(4, 3);
+
+	if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE EVENT TRIGGER */
-	else if (Matches2("CREATE", "EVENT"))
+	if (Matches2("CREATE", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	/* Complete CREATE EVENT TRIGGER <name> with ON */
-	else if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
+	if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
-	else if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
+	if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_LIST3("ddl_command_start", "ddl_command_end", "sql_drop");
 
 /* DECLARE */
-	else if (Matches2("DECLARE", MatchAny))
+	if (Matches2("DECLARE", MatchAny))
 		COMPLETE_WITH_LIST5("BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL",
 							"CURSOR");
-	else if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
+	if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
 		COMPLETE_WITH_LIST3("WITH HOLD", "WITHOUT HOLD", "FOR");
 
 /* DELETE --- can be inside EXPLAIN, RULE, etc */
 	/* ... despite which, only complete DELETE with FROM at start of line */
-	else if (Matches1("DELETE"))
+	if (Matches1("DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
-	else if (TailMatches2("DELETE", "FROM"))
+	if (TailMatches2("DELETE", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete DELETE FROM <table> */
-	else if (TailMatches3("DELETE", "FROM", MatchAny))
+	if (TailMatches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
 	/* XXX: implement tab completion for DELETE ... USING */
 
 /* DISCARD */
-	else if (Matches1("DISCARD"))
+	if (Matches1("DISCARD"))
 		COMPLETE_WITH_LIST4("ALL", "PLANS", "SEQUENCES", "TEMP");
 
 /* DO */
-	else if (Matches1("DO"))
+	if (Matches1("DO"))
 		COMPLETE_WITH_CONST("LANGUAGE");
 
 /* DROP */
 	/* Complete DROP object with CASCADE / RESTRICT */
-	else if (Matches3("DROP",
+	if (Matches3("DROP",
 					  "COLLATION|CONVERSION|DOMAIN|EXTENSION|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW",
 					  MatchAny) ||
 			 Matches4("DROP", "ACCESS", "METHOD", MatchAny) ||
@@ -2618,144 +2287,166 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (Matches2("DROP", "AGGREGATE"))
+	if (Matches2("DROP", "AGGREGATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates,
 								   ADDLIST1("IF EXISTS"));
-	else if (Matches2("DROP", "FUNCTION"))
+	if (Matches2("DROP", "FUNCTION"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions,
 								   ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
-	else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
+	if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") &&
+		MidMatches2(3, "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+
+	if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
-	else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
+	if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
-	else if (Matches2("DROP", "FOREIGN"))
+	if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
-	else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
+	if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws,
 							ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
-	else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&
-			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
-			 false) {} /* FALL THROUGH */
+	if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") &&	
+		MidMatches2(5, "IF", "EXISTS"))
+		COLLAPSE(5, 2);
+
+	if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, "");
 
 	/* DROP INDEX */
-	else if (Matches2("DROP", "INDEX"))
+	if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   ADDLIST2("IF EXISTS","CONCURRENTLY"));
-	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
+	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   ADDLIST1("IF EXISTS"));
 	/* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */
-	else if (HeadMatches2("DROP", "INDEX") &&
-			 MidMatchAndRemove1(2, "CONCURRENTLY") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches3("DROP", "INDEX", MatchAny))
+	if (HeadMatches2("DROP", "INDEX"))
+	{
+		if (MidMatches1(3, "CONCURRENTLY"))
+			COLLAPSE(3, 1);
+		if (MidMatches2(3, "IF", "EXISTS"))
+			COLLAPSE(3, 2);
+	}
+	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
-	else if (Matches2("DROP", "MATERIALIZED"))
+	if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches2("DROP", "VIEW"))
+	if (Matches2("DROP", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
 								   ADDLIST1("IF EXISTS"));
 	/* Remove optional words IF EXISTS  */
-	else if (HeadMatches2("DROP", "VIEW") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 false) {} /* FALL THROUGH */
-	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+	if (HeadMatches2("DROP", "VIEW") &&
+		MidMatches2(3, "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+
+	if (Matches2("DROP", "VIEW"))
+		COMPLETE_THING(-1);
+	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
-	else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
-			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
-			 Matches3("DROP", "MATERIALIZED", "VIEW"))
+	if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") &&
+		MidMatches2(4, "IF", "EXISTS"))
+		COLLAPSE(4, 2);
+
+	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 
 	/* DROP OWNED BY */
-	else if (Matches2("DROP", "OWNED"))
+	if (Matches2("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (Matches3("DROP", "OWNED", "BY"))
+	if (Matches3("DROP", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
-	else if (Matches3("DROP", "TEXT", "SEARCH"))
+	if (Matches3("DROP", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (Matches2("DROP", "TRIGGER"))
+	if (Matches2("DROP", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_triggers,
 							ADDLIST1("IF EXISTS"));
 	/* Try the same after removing optional words IF EXISTS */
-	else if (HeadMatches2("DROP", "TRIGGER") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches3("DROP", "TRIGGER", MatchAny))
+	if (HeadMatches2("DROP", "TRIGGER") &&
+		MidMatches2(3, "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+
+	if (Matches3("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
+	if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, "");
 	}
-	else if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
+	if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP ACCESS METHOD */
-	else if (Matches2("DROP", "ACCESS"))
+	if (Matches2("DROP", "ACCESS"))
 		COMPLETE_WITH_CONST("METHOD");
-	else if (Matches3("DROP", "ACCESS", "METHOD"))
+	if (Matches3("DROP", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, "");
 
 	/* DROP EVENT TRIGGER */
-	else if (Matches2("DROP", "EVENT"))
+	if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
-	else if (Matches3("DROP", "EVENT", "TRIGGER"))
+	if (Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers,
 							ADDLIST1("IF EXISTS"));
 	/* Trye the same after removing optional words IF EXISTS */
-	else if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
-			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
-			 Matches3("DROP", "EVENT", "TRIGGER"))
+	if (HeadMatches3("DROP", "EVENT", "TRIGGER") &&
+		MidMatches2(4, "IF", "EXISTS"))
+		COLLAPSE(4, 2);
+
+	if (Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* DROP POLICY */
-	else if (Matches2("DROP", "POLICY"))
+	if (Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies,
 							ADDLIST1("IF EXISTS"));
 	/* Try the same after after removing optional words IF EXISTS */
-	else if (HeadMatches2("DROP", "POLICY") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches2("DROP", "POLICY"))
+	if (HeadMatches2("DROP", "POLICY") &&
+		MidMatches2(3, "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+
+	if (Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies, "");
 	/* DROP POLICY <name> */
-	else if (Matches3("DROP", "POLICY", MatchAny))
+	if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* DROP POLICY <name> ON */
-	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
+	if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, "");
 	}
 
 	/* DROP RULE */
-	else if (Matches2("DROP", "RULE"))
+	if (Matches2("DROP", "RULE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_rules,
 							ADDLIST1("IF EXISTS"));
 	/* DROP RULE <name>, after removing optional words IF EXISTS */
-	else if (HeadMatches2("DROP", "RULE") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 Matches3("DROP", "RULE", MatchAny))
+	if (HeadMatches2("DROP", "RULE") &&
+		MidMatches2(3, "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+
+	if (Matches3("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
+	if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, "");
 	}
-	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
+	if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* IF EXISTS processing for DROP everything else */
-	else if (Matches2("DROP",
+	if (Matches2("DROP",
 					  "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|"
 					  "GROUP|LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|"
 					  "SERVER|TABLE|TABLESPACE|TYPE|USER") ||
@@ -2789,19 +2480,25 @@ psql_completion(const char *text, int start, int end)
 		}
 	}
 	/* Remove optional IF EXISTS from DROP */
-	else if (HeadMatches2("DROP",
-						  "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|GROUP|"
-						  "LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|SERVER|"
-						  "TABLE|TABLESPACE|TYPE|USER") &&
-			 MidMatchAndRemove2(2, "IF", "EXISTS") &&
-			 false) {} /* FALL THROUGH */
-	else if (HeadMatches4("DROP", "TEXT", "SEARCH",
-						  "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE") &&
-			 MidMatchAndRemove2(4, "IF", "EXISTS") &&
-			 false) {} /* FALL THROUGH */
+	if (HeadMatches2("DROP",
+					 "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|GROUP|"
+					 "LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|SERVER|"
+					 "TABLE|TABLESPACE|TYPE|USER") &&
+		MidMatches2(3, "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+
+	if (Matches2("DROP", MatchAny))
+		COMPLETE_THING(-1);
+	if (HeadMatches4("DROP", "TEXT", "SEARCH",
+					 "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE") &&
+		MidMatches2(5, "IF", "EXISTS"))
+		COLLAPSE(5, 2);
+
+	if (Matches4("DROP", "TEXT", "SEARCH", MatchAny))
+		COMPLETE_THING(-1);
 
 /* EXECUTE */
-	else if (Matches1("EXECUTE"))
+	if (Matches1("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements, "");
 
 /* EXPLAIN */
@@ -2809,22 +2506,22 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
-	else if (Matches1("EXPLAIN"))
+	if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"ANALYZE", "VERBOSE");
-	else if (Matches2("EXPLAIN", "ANALYZE"))
+	if (Matches2("EXPLAIN", "ANALYZE"))
 		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"VERBOSE");
-	else if (Matches2("EXPLAIN", "VERBOSE") ||
+	if (Matches2("EXPLAIN", "VERBOSE") ||
 			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
 		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-	else if (Matches1("FETCH|MOVE"))
+	if (Matches1("FETCH|MOVE"))
 		COMPLETE_WITH_LIST4("ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE");
 	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
-	else if (Matches2("FETCH|MOVE", MatchAny))
+	if (Matches2("FETCH|MOVE", MatchAny))
 		COMPLETE_WITH_LIST3("ALL", "NEXT", "PRIOR");
 
 	/*
@@ -2832,30 +2529,30 @@ psql_completion(const char *text, int start, int end)
 	 * but we may as well tab-complete both: perhaps some users prefer one
 	 * variant or the other.
 	 */
-	else if (Matches3("FETCH|MOVE", MatchAny, MatchAny))
+	if (Matches3("FETCH|MOVE", MatchAny, MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "IN");
 
 /* FOREIGN DATA WRAPPER */
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
-	else if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
+	if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
 			 !TailMatches4("CREATE", MatchAny, MatchAny, MatchAny))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, "");
 	/* applies in CREATE SERVER */
-	else if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
+	if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
 			 HeadMatches2("CREATE", "SERVER"))
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /* FOREIGN TABLE */
-	else if (TailMatches2("FOREIGN", "TABLE"))
+	if (TailMatches2("FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 
 /* FOREIGN SERVER */
-	else if (TailMatches2("FOREIGN", "SERVER"))
+	if (TailMatches2("FOREIGN", "SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers, "");
 
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	else if (TailMatches1("GRANT|REVOKE"))
+	if (TailMatches1("GRANT|REVOKE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
 			ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
 					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
@@ -2865,11 +2562,11 @@ psql_completion(const char *text, int start, int end)
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	else if (TailMatches2("GRANT|REVOKE", MatchAny))
+	if (TailMatches2("GRANT|REVOKE", MatchAny))
 	{
 		if (TailMatches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
-		else if (TailMatches2("GRANT", MatchAny))
+		if (TailMatches2("GRANT", MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2886,7 +2583,7 @@ psql_completion(const char *text, int start, int end)
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
 	 */
-	else if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
+	if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
 			   ADDLIST15("ALL FUNCTIONS IN SCHEMA",
 						 "ALL SEQUENCES IN SCHEMA",
@@ -2904,11 +2601,11 @@ psql_completion(const char *text, int start, int end)
 						 "TABLESPACE",
 						 "TYPE"));
 
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
 							"TABLES IN SCHEMA");
 
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/*
@@ -2917,27 +2614,27 @@ psql_completion(const char *text, int start, int end)
 	 *
 	 * Complete "GRANT/REVOKE * ON *" with "TO/FROM".
 	 */
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
 		if (TailMatches1("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
-		else if (TailMatches1("DOMAIN"))
+		if (TailMatches1("DOMAIN"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, "");
-		else if (TailMatches1("FUNCTION"))
+		if (TailMatches1("FUNCTION"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
-		else if (TailMatches1("LANGUAGE"))
+		if (TailMatches1("LANGUAGE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_languages, "");
-		else if (TailMatches1("SCHEMA"))
+		if (TailMatches1("SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
-		else if (TailMatches1("SEQUENCE"))
+		if (TailMatches1("SEQUENCE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
-		else if (TailMatches1("TABLE"))
+		if (TailMatches1("TABLE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
-		else if (TailMatches1("TABLESPACE"))
+		if (TailMatches1("TABLESPACE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
-		else if (TailMatches1("TYPE"))
+		if (TailMatches1("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, "");
-		else if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
+		if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2947,18 +2644,18 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "GRANT/REVOKE ... TO/FROM" with username, PUBLIC,
 	 * CURRENT_USER, or SESSION_USER.
 	 */
-	else if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
+	if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
 			 (HeadMatches1("REVOKE") && TailMatches1("FROM")))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 
 	/* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */
-	else if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
+	if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (HeadMatches1("REVOKE") && TailMatches3("ON", MatchAny, MatchAny))
+	if (HeadMatches1("REVOKE") && TailMatches3("ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("FROM");
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	else if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
+	if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
 	{
 		if (TailMatches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2967,7 +2664,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	else if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 	{
 		if (TailMatches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2976,7 +2673,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	else if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
+	if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
 	{
 		if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2985,62 +2682,62 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* GROUP BY */
-	else if (TailMatches3("FROM", MatchAny, "GROUP"))
+	if (TailMatches3("FROM", MatchAny, "GROUP"))
 		COMPLETE_WITH_CONST("BY");
 
 /* IMPORT FOREIGN SCHEMA */
-	else if (Matches1("IMPORT"))
+	if (Matches1("IMPORT"))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
-	else if (Matches2("IMPORT", "FOREIGN"))
+	if (Matches2("IMPORT", "FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT --- can be inside EXPLAIN, RULE, etc */
 	/* Complete INSERT with "INTO" */
-	else if (TailMatches1("INSERT"))
+	if (TailMatches1("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
-	else if (TailMatches2("INSERT", "INTO"))
+	if (TailMatches2("INSERT", "INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete "INSERT INTO <table> (" with attribute names */
-	else if (TailMatches4("INSERT", "INTO", MatchAny, "("))
+	if (TailMatches4("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES"
 	 */
-	else if (TailMatches3("INSERT", "INTO", MatchAny))
+	if (TailMatches3("INSERT", "INTO", MatchAny))
 		COMPLETE_WITH_LIST5("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES");
 
 	/*
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE"
 	 */
-	else if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
+	if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
 			 ends_with(prev_wd, ')'))
 		COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES");
 
 	/* Insert an open parenthesis after "VALUES" */
-	else if (TailMatches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
+	if (TailMatches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
 		COMPLETE_WITH_CONST("(");
 
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
-	else if (Matches1("LOCK"))
+	if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   ADDLIST1("TABLE"));
-	else if (Matches2("LOCK", "TABLE"))
+	if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* For the following, handle the case of a single table only for now */
 
 	/* Complete LOCK [TABLE] <table> with "IN" */
-	else if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
+	if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
 			 Matches3("LOCK", "TABLE", MatchAny))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	else if (Matches3("LOCK", MatchAny, "IN") ||
+	if (Matches3("LOCK", MatchAny, "IN") ||
 			 Matches4("LOCK", "TABLE", MatchAny, "IN"))
 		COMPLETE_WITH_LIST8("ACCESS SHARE MODE",
 							"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
@@ -3049,25 +2746,25 @@ psql_completion(const char *text, int start, int end)
 							"EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE");
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
-	else if (TailMatches1("NOTIFY"))
+	if (TailMatches1("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'", "");
 
 /* OPTIONS */
-	else if (TailMatches1("OPTIONS"))
+	if (TailMatches1("OPTIONS"))
 		COMPLETE_WITH_CONST("(");
 
 /* OWNER TO  - complete with available roles */
-	else if (TailMatches2("OWNER", "TO"))
+	if (TailMatches2("OWNER", "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* ORDER BY */
-	else if (TailMatches3("FROM", MatchAny, "ORDER"))
+	if (TailMatches3("FROM", MatchAny, "ORDER"))
 		COMPLETE_WITH_CONST("BY");
-	else if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
+	if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 /* PREPARE xx AS */
-	else if (Matches3("PREPARE", MatchAny, "AS"))
+	if (Matches3("PREPARE", MatchAny, "AS"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
 
 /*
@@ -3076,60 +2773,60 @@ psql_completion(const char *text, int start, int end)
  */
 
 /* REASSIGN OWNED BY xxx TO yyy */
-	else if (Matches1("REASSIGN"))
+	if (Matches1("REASSIGN"))
 		COMPLETE_WITH_CONST("OWNED BY");
-	else if (Matches2("REASSIGN", "OWNED"))
+	if (Matches2("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (Matches3("REASSIGN", "OWNED", "BY"))
+	if (Matches3("REASSIGN", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
-	else if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
+	if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
+	if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* REFRESH MATERIALIZED VIEW */
-	else if (Matches1("REFRESH"))
+	if (Matches1("REFRESH"))
 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
-	else if (Matches2("REFRESH", "MATERIALIZED"))
+	if (Matches2("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
+	if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   ADDLIST1("CONCURRENTLY"));
-	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
+	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
-	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
+	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
+	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
-	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
+	if (Matches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
-	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
+	if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
-	else if (Matches7("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
+	if (Matches7("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
 
 /* REINDEX */
-	else if (Matches1("REINDEX"))
+	if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
-	else if (Matches2("REINDEX", "TABLE"))
+	if (Matches2("REINDEX", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
-	else if (Matches2("REINDEX", "INDEX"))
+	if (Matches2("REINDEX", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
-	else if (Matches2("REINDEX", "SCHEMA"))
+	if (Matches2("REINDEX", "SCHEMA"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
-	else if (Matches2("REINDEX", "SYSTEM|DATABASE"))
+	if (Matches2("REINDEX", "SYSTEM|DATABASE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 
 /* SECURITY LABEL */
-	else if (Matches1("SECURITY"))
+	if (Matches1("SECURITY"))
 		COMPLETE_WITH_CONST("LABEL");
-	else if (Matches2("SECURITY", "LABEL"))
+	if (Matches2("SECURITY", "LABEL"))
 		COMPLETE_WITH_LIST2("ON", "FOR");
-	else if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
+	if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches3("SECURITY", "LABEL", "ON") ||
+	if (Matches3("SECURITY", "LABEL", "ON") ||
 			 Matches5("SECURITY", "LABEL", "FOR", MatchAny, "ON"))
 	{
 		static const char *const list_SECURITY_LABEL[] =
@@ -3140,7 +2837,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_SECURITY_LABEL);
 	}
-	else if (Matches5("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
+	if (Matches5("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("IS");
 
 /* SELECT */
@@ -3148,71 +2845,55 @@ psql_completion(const char *text, int start, int end)
 
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
-	else if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
+	if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars, "");
-	else if (Matches1("SHOW"))
+	if (Matches1("SHOW"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars, "");
 	/* Complete "SET TRANSACTION" */
-	else if (Matches2("SET", "TRANSACTION"))
-		COMPLETE_WITH_LIST5("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	else if (Matches2("BEGIN|START", "TRANSACTION") ||
+	if (Matches2("SET|BEGIN|START", "TRANSACTION") ||
 			 Matches2("BEGIN", "WORK") ||
-			 Matches1("BEGIN") ||
 		  Matches5("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
-		COMPLETE_WITH_LIST4("ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
-			 Matches2("BEGIN", "NOT") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
-		COMPLETE_WITH_CONST("DEFERRABLE");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
-			 Matches2("BEGIN", "ISOLATION") ||
+		COMPLETE_WITH_LIST2("ISOLATION LEVEL", "READ");
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
 			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
 		COMPLETE_WITH_CONST("LEVEL");
-	else if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
-			 Matches3("BEGIN", "ISOLATION", "LEVEL") ||
-			 Matches7("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
+	if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL"))
 		COMPLETE_WITH_LIST3("READ", "REPEATABLE READ", "SERIALIZABLE");
-	else if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
-			 Matches4("BEGIN", "ISOLATION", "LEVEL", "READ") ||
-			 Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
+	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ"))
 		COMPLETE_WITH_LIST2("UNCOMMITTED", "COMMITTED");
-	else if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
-			 Matches4("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
-			 Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
+	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE"))
 		COMPLETE_WITH_CONST("READ");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
-			 Matches2("BEGIN", "READ") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ"))
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
-	else if (Matches2("SET", "CONSTRAINTS"))
+	if (Matches2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema,
 								   ADDLIST1("ALL"));
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
-	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
+	if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
-	else if (Matches2("SET", "ROLE"))
+	if (Matches2("SET", "ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
-	else if (Matches2("SET", "SESSION"))
+	if (Matches2("SET", "SESSION"))
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
-	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
+	if (Matches3("SET", "SESSION", "AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
 							ADDLIST1("DEFAULT"));
 	/* Complete RESET SESSION with AUTHORIZATION */
-	else if (Matches2("RESET", "SESSION"))
+	if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
 	/* Complete SET <var> with "TO" */
-	else if (Matches2("SET", MatchAny))
+	if (Matches2("SET", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete ALTER DATABASE|FUNCTION|ROLE|USER ... SET <name> */
-	else if (HeadMatches2("ALTER", "DATABASE|FUNCTION|ROLE|USER") &&
+	if (HeadMatches2("ALTER", "DATABASE|FUNCTION|ROLE|USER") &&
 			 TailMatches2("SET", MatchAny))
 		COMPLETE_WITH_LIST2("FROM CURRENT", "TO");
 	/* Suggest possible variable values */
-	else if (TailMatches3("SET", MatchAny, "TO|="))
+	if (TailMatches3("SET", MatchAny, "TO|="))
 	{
 		/* special cased code for individual GUCs */
 		if (TailMatches2("DateStyle", "TO|="))
@@ -3225,7 +2906,7 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST(my_list);
 		}
-		else if (TailMatches2("search_path", "TO|="))
+		if (TailMatches2("search_path", "TO|="))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
 								" AND nspname not like 'pg\\_toast%%' "
 								" AND nspname not like 'pg\\_temp%%' ",
@@ -3233,103 +2914,108 @@ psql_completion(const char *text, int start, int end)
 		else
 		{
 			/* generic, type based, GUC support */
-			char	   *guctype = get_guctype(prev2_wd);
+			char		guctype[6];
 
-			if (guctype && strcmp(guctype, "enum") == 0)
+			/*
+			 * The value guc_guctype returned is 
+			 */
+			if (get_guctype(guctype, 6, prev2_wd))
 			{
-				char		querybuf[1024];
+				if (strcmp(guctype, "enum") == 0)
+				{
+					char		querybuf[1024];
 
-				snprintf(querybuf, sizeof(querybuf), Query_for_enum, prev2_wd);
-				COMPLETE_WITH_QUERY(querybuf, "");
+					snprintf(querybuf, sizeof(querybuf),
+							 Query_for_enum, prev2_wd);
+					COMPLETE_WITH_QUERY(querybuf, "");
+				}
+				if (strcmp(guctype, "bool") == 0)
+					COMPLETE_WITH_LIST9("on", "off", "true", "false",
+										"yes", "no", "1", "0", "DEFAULT");
 			}
-			else if (guctype && strcmp(guctype, "bool") == 0)
-				COMPLETE_WITH_LIST9("on", "off", "true", "false", "yes", "no",
-									"1", "0", "DEFAULT");
 			else
 				COMPLETE_WITH_CONST("DEFAULT");
-
-			if (guctype)
-				free(guctype);
 		}
 	}
 
 /* START TRANSACTION */
-	else if (Matches1("START"))
+	if (Matches1("START"))
 		COMPLETE_WITH_CONST("TRANSACTION");
 
 /* TABLE, but not TABLE embedded in other commands */
-	else if (Matches1("TABLE"))
+	if (Matches1("TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, "");
 
 /* TABLESAMPLE */
-	else if (TailMatches1("TABLESAMPLE"))
+	if (TailMatches1("TABLESAMPLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods, "");
-	else if (TailMatches2("TABLESAMPLE", MatchAny))
+	if (TailMatches2("TABLESAMPLE", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
-	else if (Matches1("TRUNCATE"))
+	if (Matches1("TRUNCATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 /* UNLISTEN */
-	else if (Matches1("UNLISTEN"))
+	if (Matches1("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'", "");
 
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
-	else if (TailMatches1("UPDATE"))
+	if (TailMatches1("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete UPDATE <table> with "SET" */
-	else if (TailMatches2("UPDATE", MatchAny))
+	if (TailMatches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
 	/* Complete UPDATE <table> SET with list of attributes */
-	else if (TailMatches3("UPDATE", MatchAny, "SET"))
+	if (TailMatches3("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* UPDATE <table> SET <attr> = */
-	else if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
+	if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if (Matches3("ALTER|CREATE", "USER", "MAPPING"))
+	if (Matches3("ALTER|CREATE", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
-	else if (Matches3("DROP", "USER", "MAPPING"))
+	if (Matches3("DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR");
-	else if (HeadMatches3("DROP", "USER", "MAPPING") &&
-			 MidMatchAndRemove2(3, "IF", "EXISTS") &&
-			 false) {} /* FALL THROUGH */
-	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
+	if (HeadMatches3("DROP", "USER", "MAPPING") &&
+		MidMatches2(4, "IF", "EXISTS"))
+		COLLAPSE(4, 2);
+
+	if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
 							ADDLIST3("CURRENT_USER", "PUBLIC", "USER"));
-	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
+	if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, "");
-	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
+	if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("SERVER");
-	else if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
+	if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /*
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
-	else if (Matches1("VACUUM"))
+	if (Matches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 		   ADDLIST4("FULL", "FREEZE", "ANALYZE", "VERBOSE"));
-	else if (Matches2("VACUUM", "FULL|FREEZE"))
+	if (Matches2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 		   ADDLIST2("ANALYZE", "VERBOSE"));
-	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
+	if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 		   ADDLIST1("VERBOSE"));
-	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
+	if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 		   ADDLIST1("ANALYZE"));
-	else if (Matches2("VACUUM", "VERBOSE"))
+	if (Matches2("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 		   ADDLIST1("ANALYZE"));
-	else if (Matches2("VACUUM", "ANALYZE"))
+	if (Matches2("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 		   ADDLIST1("VERBOSE"));
-	else if (HeadMatches1("VACUUM"))
+	if (HeadMatches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 
 /* WITH [RECURSIVE] */
@@ -3338,114 +3024,114 @@ psql_completion(const char *text, int start, int end)
 	 * Only match when WITH is the first word, as WITH may appear in many
 	 * other contexts.
 	 */
-	else if (Matches1("WITH"))
+	if (Matches1("WITH"))
 		COMPLETE_WITH_CONST("RECURSIVE");
 
 /* ANALYZE */
 	/* Complete with list of tables */
-	else if (Matches1("ANALYZE"))
+	if (Matches1("ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, "");
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
-	else if (TailMatches2(MatchAny, "WHERE"))
+	if (TailMatches2(MatchAny, "WHERE"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
-	else if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
+	if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 
 /* ... JOIN ... */
-	else if (TailMatches1("JOIN"))
+	if (TailMatches1("JOIN"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
-	else if (TailMatchesCS1("\\?"))
+	if (TailMatchesCS1("\\?"))
 		COMPLETE_WITH_LIST_CS3("commands", "options", "variables");
-	else if (TailMatchesCS1("\\connect|\\c"))
+	if (TailMatchesCS1("\\connect|\\c"))
 	{
 		if (!recognized_connection_string(text))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 	}
-	else if (TailMatchesCS2("\\connect|\\c", MatchAny))
+	if (TailMatchesCS2("\\connect|\\c", MatchAny))
 	{
 		if (!recognized_connection_string(prev_wd))
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	}
-	else if (TailMatchesCS1("\\da*"))
+	if (TailMatchesCS1("\\da*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, "");
-	else if (TailMatchesCS1("\\dA*"))
+	if (TailMatchesCS1("\\dA*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, "");
-	else if (TailMatchesCS1("\\db*"))
+	if (TailMatchesCS1("\\db*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
-	else if (TailMatchesCS1("\\dD*"))
+	if (TailMatchesCS1("\\dD*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, "");
-	else if (TailMatchesCS1("\\des*"))
+	if (TailMatchesCS1("\\des*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers, "");
-	else if (TailMatchesCS1("\\deu*"))
+	if (TailMatchesCS1("\\deu*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, "");
-	else if (TailMatchesCS1("\\dew*"))
+	if (TailMatchesCS1("\\dew*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, "");
-	else if (TailMatchesCS1("\\df*"))
+	if (TailMatchesCS1("\\df*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 
-	else if (TailMatchesCS1("\\dFd*"))
+	if (TailMatchesCS1("\\dFd*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries, "");
-	else if (TailMatchesCS1("\\dFp*"))
+	if (TailMatchesCS1("\\dFp*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers, "");
-	else if (TailMatchesCS1("\\dFt*"))
+	if (TailMatchesCS1("\\dFt*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates, "");
 	/* must be at end of \dF alternatives: */
-	else if (TailMatchesCS1("\\dF*"))
+	if (TailMatchesCS1("\\dF*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations, "");
 
-	else if (TailMatchesCS1("\\di*"))
+	if (TailMatchesCS1("\\di*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
-	else if (TailMatchesCS1("\\dL*"))
+	if (TailMatchesCS1("\\dL*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages, "");
-	else if (TailMatchesCS1("\\dn*"))
+	if (TailMatchesCS1("\\dn*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
-	else if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
+	if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
-	else if (TailMatchesCS1("\\ds*"))
+	if (TailMatchesCS1("\\ds*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
-	else if (TailMatchesCS1("\\dt*"))
+	if (TailMatchesCS1("\\dt*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
-	else if (TailMatchesCS1("\\dT*"))
+	if (TailMatchesCS1("\\dT*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, "");
-	else if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
+	if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
-	else if (TailMatchesCS1("\\dv*"))
+	if (TailMatchesCS1("\\dv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
-	else if (TailMatchesCS1("\\dx*"))
+	if (TailMatchesCS1("\\dx*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions, "");
-	else if (TailMatchesCS1("\\dm*"))
+	if (TailMatchesCS1("\\dm*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
-	else if (TailMatchesCS1("\\dE*"))
+	if (TailMatchesCS1("\\dE*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
-	else if (TailMatchesCS1("\\dy*"))
+	if (TailMatchesCS1("\\dy*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* must be at end of \d alternatives: */
-	else if (TailMatchesCS1("\\d*"))
+	if (TailMatchesCS1("\\d*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, "");
 
-	else if (TailMatchesCS1("\\ef"))
+	if (TailMatchesCS1("\\ef"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
-	else if (TailMatchesCS1("\\ev"))
+	if (TailMatchesCS1("\\ev"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 
-	else if (TailMatchesCS1("\\encoding"))
+	if (TailMatchesCS1("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings, "");
-	else if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
+	if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
 		COMPLETE_WITH_LIST(sql_commands);
-	else if (TailMatchesCS1("\\l*") && !TailMatchesCS1("\\lo*"))
+	if (TailMatchesCS1("\\l*") && !TailMatchesCS1("\\lo*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
-	else if (TailMatchesCS1("\\password"))
+	if (TailMatchesCS1("\\password"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
-	else if (TailMatchesCS1("\\pset"))
+	if (TailMatchesCS1("\\pset"))
 	{
 		static const char *const my_list[] =
 		{"border", "columns", "expanded", "fieldsep", "fieldsep_zero",
@@ -3456,7 +3142,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (TailMatchesCS2("\\pset", MatchAny))
+	if (TailMatchesCS2("\\pset", MatchAny))
 	{
 		if (TailMatchesCS1("format"))
 		{
@@ -3466,53 +3152,53 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (TailMatchesCS1("linestyle"))
+		if (TailMatchesCS1("linestyle"))
 			COMPLETE_WITH_LIST_CS3("ascii", "old-ascii", "unicode");
-		else if (TailMatchesCS1("unicode_border_linestyle|"
+		if (TailMatchesCS1("unicode_border_linestyle|"
 								"unicode_column_linestyle|"
 								"unicode_header_linestyle"))
 			COMPLETE_WITH_LIST_CS2("single", "double");
 	}
-	else if (TailMatchesCS1("\\unset"))
+	if (TailMatchesCS1("\\unset"))
 	{
-		matches = complete_from_variables(text, "", "", true);
+		return complete_from_variables(text, "", "", true);
 	}
-	else if (TailMatchesCS1("\\set"))
+	if (TailMatchesCS1("\\set"))
 	{
-		matches = complete_from_variables(text, "", "", false);
+		return complete_from_variables(text, "", "", false);
 	}
-	else if (TailMatchesCS2("\\set", MatchAny))
+	if (TailMatchesCS2("\\set", MatchAny))
 	{
 		if (TailMatchesCS1("AUTOCOMMIT|ON_ERROR_STOP|QUIET|"
 						   "SINGLELINE|SINGLESTEP"))
 			COMPLETE_WITH_LIST_CS2("on", "off");
-		else if (TailMatchesCS1("COMP_KEYWORD_CASE"))
+		if (TailMatchesCS1("COMP_KEYWORD_CASE"))
 			COMPLETE_WITH_LIST_CS4("lower", "upper",
 								   "preserve-lower", "preserve-upper");
-		else if (TailMatchesCS1("ECHO"))
+		if (TailMatchesCS1("ECHO"))
 			COMPLETE_WITH_LIST_CS4("errors", "queries", "all", "none");
-		else if (TailMatchesCS1("ECHO_HIDDEN"))
+		if (TailMatchesCS1("ECHO_HIDDEN"))
 			COMPLETE_WITH_LIST_CS3("noexec", "off", "on");
-		else if (TailMatchesCS1("HISTCONTROL"))
+		if (TailMatchesCS1("HISTCONTROL"))
 			COMPLETE_WITH_LIST_CS4("ignorespace", "ignoredups",
 								   "ignoreboth", "none");
-		else if (TailMatchesCS1("ON_ERROR_ROLLBACK"))
+		if (TailMatchesCS1("ON_ERROR_ROLLBACK"))
 			COMPLETE_WITH_LIST_CS3("on", "off", "interactive");
-		else if (TailMatchesCS1("SHOW_CONTEXT"))
+		if (TailMatchesCS1("SHOW_CONTEXT"))
 			COMPLETE_WITH_LIST_CS3("never", "errors", "always");
-		else if (TailMatchesCS1("VERBOSITY"))
+		if (TailMatchesCS1("VERBOSITY"))
 			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
 	}
-	else if (TailMatchesCS1("\\sf*"))
+	if (TailMatchesCS1("\\sf*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
-	else if (TailMatchesCS1("\\sv*"))
+	if (TailMatchesCS1("\\sv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
-	else if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
+	if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
 							"\\ir|\\include_relative|\\o|\\out|"
 							"\\s|\\w|\\write|\\lo_import"))
 	{
-		SET_COMP_CHARP("\\");
-		matches = completion_matches(text, complete_from_files);
+		SET_COMPLETION_CHARP("\\");
+		return completion_matches(text, complete_from_files);
 	}
 
 	/*
@@ -3520,41 +3206,9 @@ psql_completion(const char *text, int start, int end)
 	 * check if that was the previous word. If so, execute the query to get a
 	 * list of them.
 	 */
-	else
-	{
-		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
-
-		if (ent)
-		{
-			if (ent->query)
-				COMPLETE_WITH_QUERY(ent->query, "");
-			else if (ent->squery)
-				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, "");
-		}
-	}
-
-	/*
-	 * If we still don't have anything to match we have to fabricate some sort
-	 * of default list. If we were to just return NULL, readline automatically
-	 * attempts filename completion, and that's usually no good.
-	 */
-	if (matches == NULL)
-	{
-		COMPLETE_WITH_CONST("");
-#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
-		rl_completion_append_character = '\0';
-#endif
-	}
-
-	/* free storage */
-	free(previous_words);
-	free(words_buffer);
-
-	/* Return our Grand List O' Matches */
-	return matches;
+	COMPLETE_THING(-1);
 }
 
-
 /*
  * GENERATOR FUNCTIONS
  *
@@ -4320,14 +3974,15 @@ get_previous_words(int point, char **buffer, int *nwords)
  * Returns NULL if the variable is unknown. Otherwise the returned string,
  * containing the type, has to be freed.
  */
-static char *
-get_guctype(const char *varname)
+static bool
+get_guctype(char *buf, size_t len, const char *varname)
 {
 	PQExpBufferData query_buffer;
 	char	   *e_varname;
 	PGresult   *result;
-	char	   *guctype = NULL;
+	bool		success = true;
 
+	memset(buf, 0, len);
 	e_varname = escape_string(varname);
 
 	initPQExpBuffer(&query_buffer);
@@ -4341,11 +3996,12 @@ get_guctype(const char *varname)
 	free(e_varname);
 
 	if (PQresultStatus(result) == PGRES_TUPLES_OK && PQntuples(result) > 0)
-		guctype = pg_strdup(PQgetvalue(result, 0, 0));
-
+		strncpy(buf, PQgetvalue(result, 0, 0), len - 1);
+	else
+		success = false;
 	PQclear(result);
 
-	return guctype;
+	return success;
 }
 
 /*
-- 
2.9.2

#39Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#38)
Re: IF (NOT) EXISTS in psql-completion

2016-09-06 15:00 GMT+02:00 Kyotaro HORIGUCHI <
horiguchi.kyotaro@lab.ntt.co.jp>:

Hello, this is the new version of this patch. Rebased on the
current master.

At Tue, 06 Sep 2016 13:06:51 +0900 (Tokyo Standard Time), Kyotaro
HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <
20160906.130651.171572544.horiguchi.kyotaro@lab.ntt.co.jp>

Thank you. I'll rebase the following patch and repost as soon as
possible.

/messages/by-id/20160407.211917.

147996130.horiguchi.kyotaro@lab.ntt.co.jp

This patch consists of the following files. Since these files are
splitted in strange criteria and order for historical reasons,
I'll reorganize this and post them later.

ok, can I start with testing and review with some from these files?

Regards

Pavel

Show quoted text

- 0001-Suggest-IF-NOT-EXISTS-for-tab-completion-of-psql.patch

Add suggestion of IF (NOT) EXISTS on master. This should be the
last patch in this patchset.

- 0002-Make-added-keywords-for-completion-queries-follow-to.patch

Current suggestion mechanism doesn't distinguish object names
and keywords, which should be differently handled in
determining letter cases. This patch fixes that.

- 0003-Make-COMPLETE_WITH_ATTR-to-accept-additional-keyword.patch

This patch apply the 0002 fix to COMPLET_WITH_ATTR.

- 0004-Refactoring-tab-complete-to-make-psql_completion-cod.patch

By Tom's suggestion, in order to modify previous_words in more
sane way, transforming the else-if sequence in psql_completion
into a simple if sequence.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#40Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Pavel Stehule (#39)
Re: IF (NOT) EXISTS in psql-completion

Hello,

At Sat, 10 Sep 2016 07:40:16 +0200, Pavel Stehule <pavel.stehule@gmail.com> wrote in <CAFj8pRC02=1AvrnsE+T++S_EB2zkwj3wp-6KhYi5pMR25=nwew@mail.gmail.com>

2016-09-06 15:00 GMT+02:00 Kyotaro HORIGUCHI <
horiguchi.kyotaro@lab.ntt.co.jp>:

Hello, this is the new version of this patch. Rebased on the
current master.

..

This patch consists of the following files. Since these files are
splitted in strange criteria and order for historical reasons,
I'll reorganize this and post them later.

ok, can I start with testing and review with some from these files?

No problem, of couese. The reorganizing won't be a labor but will
change the spape so as to affect reviewing. Please wait for a
while.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#41Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#40)
5 attachment(s)
Re: IF (NOT) EXISTS in psql-completion

Hello, this is the new version.

At Tue, 13 Sep 2016 10:50:13 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <20160913.105013.65452566.horiguchi.kyotaro@lab.ntt.co.jp>

This patch consists of the following files. Since these files are
splitted in strange criteria and order for historical reasons,
I'll reorganize this and post them later.

The focus of this patch has changed. The first motivation was
completing IF-EXISTS but the underlying issue was flexibility of
psql_completion. And as Pavel's suggestion, keywords suggested
along with database objects should follow the character case of
input.

For the purpose of resolving the issues, I reorganized the
confused patch set. The attached patches are organized as the
following.

1. Refactoring tab-complete to make psql_completion code

Does two things. One is moving out the macros that has grown to
be too large to stay in tab_completion.c to new file
tab-complete-macros.h The other is separating out the else-if
sequence in psql_completion() as a new function
psql_completion_internal(). This allows us to the following
things.

- Exit from arbitrary place in the former-else-if sequence just
by return.

- Do other than "if(matching) { completion }" in anywhere
convenient in the midst of the former-els...

- Recursively matching for sub syntaxes. EXPLAIN, RULE and
others are using this feature. (Needs the 4th patch to do
this, though)

2. Make keywords' case follow to input

Allow the keywords suggested along with databse objects to
follow the input letter case. The core part of this patch is a
new function additional_kw_query(), which dynamically generates
additional query string with specified keywords in the desired
letter case. COMPLETE_WITH_* macros are modified to accept the
function.

3. Fix suggested keywords to follow input in tab-completion session 2

The 2nd patch above leaves some query string containing static
keyword strings, which results in failure to follow input
letter cases. Most of them are naturally removed but role names
are a bother. This patch puts additional query strings for
several usage of roles but it might be overdone.

4. Introduce word shift and removal feature to psql-completion

This is the second core for the flexibility of completion code.
The word shift feature is the ability to omit first several
words in *MatchesN macros. For example this allows complete
create-schema's schema elements in a natural code. (Currently
those syntaxes that can be a schema elements are using
TailMatches instead of Matches, as the result HeadMatches are
not available there). The words removing feature is the ability
to (desructively) clip multiple suceessive words in the
previous_words list. This feature allows suceeding completion
code not to care about the removed words, such like UNIQUE,
CONCURRENTLY, VERBOSE and so on.

5. Add suggestion for IF (NOT) EXISTS for some syntaxes

This adds IF (NOT) EXISTS suggestion, as a PoC. This patch no
loger covers all adoptable syntaces since the places where more
than boilerplating is required are omitted.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

0001-Refactoring-tab-complete-to-make-psql_completion-cod.patchtext/x-patch; charset=us-asciiDownload
From f3931c2ddabd08d0714af29b8bacc1ef0b61cad2 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Wed, 14 Sep 2016 11:59:04 +0900
Subject: [PATCH 1/5] Refactoring tab-complete to make psql_completion code
 simpler.

psql_completion consists of a long-long sequence of "else-if"s and
succeeding finishing code. This structure enforces us to use a very
tricky way to put something other than matching-completion pairs in
the midst of the sequence. This patch separates the
matching-completion pairs into individual function
"psql_completion_internal". Returning at the points of completion let
us free from the else-if sequence and let us put anything we want into
the matching-completion sequence.

Addition to that the amount of the code seems to be a good reason to
move it out of tab-complete.c. This patch searates it out as
tab-complete-macros.h
---
 src/bin/psql/Makefile              |    2 +
 src/bin/psql/tab-complete-macros.h |  405 +++++++++++
 src/bin/psql/tab-complete.c        | 1347 ++++++++++++++----------------------
 3 files changed, 932 insertions(+), 822 deletions(-)
 create mode 100644 src/bin/psql/tab-complete-macros.h

diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index 1f6a289..51f88ba 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -47,6 +47,8 @@ ifeq ($(GCC),yes)
 psqlscanslash.o: CFLAGS += -Wno-error
 endif
 
+tab-complete.o: tab-complete-macros.h
+
 distprep: sql_help.h psqlscanslash.c
 
 install: all installdirs
diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
new file mode 100644
index 0000000..8ee52b8
--- /dev/null
+++ b/src/bin/psql/tab-complete-macros.h
@@ -0,0 +1,405 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2016, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/tab-complete-macros.h
+ */
+#ifndef TAB_COMPLETE_MACROS_H
+#define TAB_COMPLETE_MACROS_H
+
+/*
+ * For compactness, we use these macros to reference previous_words[].
+ * Caution: do not access a previous_words[] entry without having checked
+ * previous_words_count to be sure it's valid.  In most cases below, that
+ * check is implicit in a TailMatches() or similar macro, but in some places
+ * we have to check it explicitly.
+ */
+#define prev_wd   (previous_words[0])
+#define prev2_wd  (previous_words[1])
+#define prev3_wd  (previous_words[2])
+#define prev4_wd  (previous_words[3])
+#define prev5_wd  (previous_words[4])
+#define prev6_wd  (previous_words[5])
+#define prev7_wd  (previous_words[6])
+#define prev8_wd  (previous_words[7])
+#define prev9_wd  (previous_words[8])
+
+/*
+ * Return the index in previous_words for index from the beginning. n is
+ * 1-based and the result is 0-based.
+ */
+#define HEAD_INDEX(n) \
+	(previous_words_count - (n))
+
+/*
+ * Macros for matching the last N words before point, and after head_sift,
+ * case-insensitively.
+ */
+#define TailMatches1(p1) \
+	(previous_words_count >= 1 && \
+	 word_matches(p1, prev_wd))
+
+#define TailMatches2(p2, p1) \
+	(previous_words_count >= 2 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd))
+
+#define TailMatches3(p3, p2, p1) \
+	(previous_words_count >= 3 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd))
+
+#define TailMatches4(p4, p3, p2, p1) \
+	(previous_words_count >= 4 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd))
+
+#define TailMatches5(p5, p4, p3, p2, p1) \
+	(previous_words_count >= 5 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd))
+
+#define TailMatches6(p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 6 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd))
+
+#define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 7 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd))
+
+#define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 8 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd) && \
+	 word_matches(p8, prev8_wd))
+
+#define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 9 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd) && \
+	 word_matches(p8, prev8_wd) && \
+	 word_matches(p9, prev9_wd))
+
+	/*
+	 * Macros for matching the last N words before point, and after
+	 * head_shift, case-sensitively.
+	 */
+#define TailMatchesCS1(p1) \
+	(previous_words_count >= 1 && \
+	 word_matches_cs(p1, prev_wd))
+#define TailMatchesCS2(p2, p1) \
+	(previous_words_count >= 2 && \
+	 word_matches_cs(p1, prev_wd) && \
+	 word_matches_cs(p2, prev2_wd))
+
+	/*
+	 * Macros for matching N words exactly to the line, and after head_shift,
+	 * case-insensitively.
+	 */
+#define Matches1(p1) \
+	(previous_words_count == 1 && \
+	 TailMatches1(p1))
+#define Matches2(p1, p2) \
+	(previous_words_count == 2 && \
+	 TailMatches2(p1, p2))
+#define Matches3(p1, p2, p3) \
+	(previous_words_count == 3 && \
+	 TailMatches3(p1, p2, p3))
+#define Matches4(p1, p2, p3, p4) \
+	(previous_words_count == 4 && \
+	 TailMatches4(p1, p2, p3, p4))
+#define Matches5(p1, p2, p3, p4, p5) \
+	(previous_words_count == 5 && \
+	 TailMatches5(p1, p2, p3, p4, p5))
+#define Matches6(p1, p2, p3, p4, p5, p6) \
+	(previous_words_count == 6 && \
+	 TailMatches6(p1, p2, p3, p4, p5, p6))
+#define Matches7(p1, p2, p3, p4, p5, p6, p7) \
+	(previous_words_count == 7 && \
+	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
+#define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
+	(previous_words_count == 8 && \
+	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
+#define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
+	(previous_words_count == 9 && \
+	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
+
+/*
+ * Macros for matching N words after head_shift + sth in the line, regardless
+ * of what is after them, case-insensitively.
+ */
+#define MidMatches1(s, p1) \
+	(HEAD_INDEX(s) >=0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]))
+
+#define MidMatches2(s, p1, p2) \
+	(HEAD_INDEX((s) + 1) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]))
+
+#define MidMatches3(s, p1, p2, p3) \
+	(HEAD_INDEX((s) + 2) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]))
+
+#define MidMatches4(s, p1, p2, p3, p4) \
+	(HEAD_INDEX((s) + 3) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]))
+
+#define MidMatches5(s, p1, p2, p3, p4, p5) \
+	(HEAD_INDEX((s) + 4) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&	\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&		\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]))
+
+#define MidMatches6(s,p1, p2, p3, p4, p5, p6)		\
+	(HEAD_INDEX((s) + 5) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]))
+
+#define MidMatches7(s,p1, p2, p3, p4, p5, p6, p7)	\
+	(HEAD_INDEX((s) + 6) >= 0 &&							\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&			\
+	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]))
+
+#define HeadMatches1(p1) \
+	MidMatches1(1, p1)
+#define HeadMatches2(p1, p2) \
+	MidMatches2(1, p1, p2)
+#define HeadMatches3(p1, p2, p3) \
+	MidMatches3(1, p1, p2, p3)
+#define HeadMatches4(p1, p2, p3, p4) \
+	MidMatches4(1, p1, p2, p3, p4)
+#define HeadMatches5(p1, p2, p3, p4, p5) \
+	MidMatches5(1, p1, p2, p3, p4, p5)
+#define HeadMatches6(p1, p2, p3, p4, p5, p6) \
+	MidMatches6(1, p1, p2, p3, p4, p5, p6)
+#define HeadMatches7(p1, p2, p3, p4, p5, p6, p7) \
+	MidMatches7(1, p1, p2, p3, p4, p5, p6, p7)
+
+/*
+ * A few macros to ease typing. You can use these to complete the given
+ * string with
+ * 1) The results from a query you pass it. (Perhaps one of those below?)
+ * 2) The results from a schema query you pass it.
+ * 3) The items from a null-pointer-terminated list (with or without
+ *	  case-sensitive comparison; see also COMPLETE_WITH_LISTn, below).
+ * 4) A string constant.
+ * 5) The list of attributes of the given table (possibly schema-qualified).
+ * 6) The list of arguments to the given function (possibly schema-qualified).
+ */
+#define COMPLETE_WITH_QUERY(query) \
+do { \
+	completion_charp = query;	\
+	return completion_matches(text, complete_from_query);	\
+} while (0)
+
+#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
+do { \
+	completion_squery = &(query); \
+	completion_charp = addon; \
+	return completion_matches(text, complete_from_schema_query); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS(list) \
+do { \
+	completion_charpp = list; \
+	completion_case_sensitive = true; \
+	return completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST(list) \
+do { \
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	return completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_CONST(string) \
+do { \
+	completion_charp = string;	\
+	completion_case_sensitive = false; \
+	return completion_matches(text, complete_from_const);	\
+} while (0)
+
+#define COMPLETE_WITH_ATTR(relation, addon) \
+do { \
+	char   *_completion_schema; \
+	char   *_completion_table; \
+\
+	_completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \
+								 false, false, pset.encoding); \
+	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+				   false, false, pset.encoding); \
+	_completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+								false, false, pset.encoding); \
+	if (_completion_table == NULL) \
+	{ \
+		completion_charp = Query_for_list_of_attributes addon; \
+		completion_info_charp = relation; \
+	} \
+	else \
+	{ \
+		completion_charp = Query_for_list_of_attributes_with_schema addon; \
+		completion_info_charp = _completion_table; \
+		completion_info_charp2 = _completion_schema; \
+	} \
+	return completion_matches(text, complete_from_query); \
+} while (0)
+
+#define COMPLETE_WITH_FUNCTION_ARG(function) \
+do { \
+	char   *_completion_schema; \
+	char   *_completion_function; \
+\
+	_completion_schema = strtokx(function, " \t\n\r", ".", "\"", 0, \
+								 false, false, pset.encoding); \
+	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+				   false, false, pset.encoding); \
+	_completion_function = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+								   false, false, pset.encoding); \
+	if (_completion_function == NULL) \
+	{ \
+		completion_charp = Query_for_list_of_arguments; \
+		completion_info_charp = function; \
+	} \
+	else \
+	{ \
+		completion_charp = Query_for_list_of_arguments_with_schema;	\
+		completion_info_charp = _completion_function; \
+		completion_info_charp2 = _completion_schema; \
+	} \
+	return completion_matches(text, complete_from_query); \
+} while (0)
+
+/*
+ * These macros simplify use of COMPLETE_WITH_LIST for short, fixed lists.
+ * There is no COMPLETE_WITH_LIST1; use COMPLETE_WITH_CONST for that case.
+ */
+#define COMPLETE_WITH_LIST2(s1, s2) \
+do { \
+	static const char *const list[] = { s1, s2, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST3(s1, s2, s3) \
+do { \
+	static const char *const list[] = { s1, s2, s3, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST4(s1, s2, s3, s4) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST5(s1, s2, s3, s4, s5) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST6(s1, s2, s3, s4, s5, s6) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST7(s1, s2, s3, s4, s5, s6, s7) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST8(s1, s2, s3, s4, s5, s6, s7, s8) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST9(s1, s2, s3, s4, s5, s6, s7, s8, s9) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST10(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+/*
+ * Likewise for COMPLETE_WITH_LIST_CS.
+ */
+#define COMPLETE_WITH_LIST_CS2(s1, s2) \
+do { \
+	static const char *const list[] = { s1, s2, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS3(s1, s2, s3) \
+do { \
+	static const char *const list[] = { s1, s2, s3, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS4(s1, s2, s3, s4) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS5(s1, s2, s3, s4, s5) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+
+
+#endif   /* TAB_COMPLETE_MACROS_H */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 50a45eb..679e58f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -34,6 +34,7 @@
 
 #include "postgres_fe.h"
 #include "tab-complete.h"
+#include "tab-complete-macros.h"
 #include "input.h"
 
 /* If we don't have this, we might as well forget about the whole thing: */
@@ -133,186 +134,6 @@ static const SchemaQuery *completion_squery;	/* to pass a SchemaQuery */
 static bool completion_case_sensitive;	/* completion is case sensitive */
 
 /*
- * A few macros to ease typing. You can use these to complete the given
- * string with
- * 1) The results from a query you pass it. (Perhaps one of those below?)
- * 2) The results from a schema query you pass it.
- * 3) The items from a null-pointer-terminated list (with or without
- *	  case-sensitive comparison; see also COMPLETE_WITH_LISTn, below).
- * 4) A string constant.
- * 5) The list of attributes of the given table (possibly schema-qualified).
- * 6/ The list of arguments to the given function (possibly schema-qualified).
- */
-#define COMPLETE_WITH_QUERY(query) \
-do { \
-	completion_charp = query; \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
-do { \
-	completion_squery = &(query); \
-	completion_charp = addon; \
-	matches = completion_matches(text, complete_from_schema_query); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS(list) \
-do { \
-	completion_charpp = list; \
-	completion_case_sensitive = true; \
-	matches = completion_matches(text, complete_from_list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST(list) \
-do { \
-	completion_charpp = list; \
-	completion_case_sensitive = false; \
-	matches = completion_matches(text, complete_from_list); \
-} while (0)
-
-#define COMPLETE_WITH_CONST(string) \
-do { \
-	completion_charp = string; \
-	completion_case_sensitive = false; \
-	matches = completion_matches(text, complete_from_const); \
-} while (0)
-
-#define COMPLETE_WITH_ATTR(relation, addon) \
-do { \
-	char   *_completion_schema; \
-	char   *_completion_table; \
-\
-	_completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-								false, false, pset.encoding); \
-	if (_completion_table == NULL) \
-	{ \
-		completion_charp = Query_for_list_of_attributes  addon; \
-		completion_info_charp = relation; \
-	} \
-	else \
-	{ \
-		completion_charp = Query_for_list_of_attributes_with_schema  addon; \
-		completion_info_charp = _completion_table; \
-		completion_info_charp2 = _completion_schema; \
-	} \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-#define COMPLETE_WITH_FUNCTION_ARG(function) \
-do { \
-	char   *_completion_schema; \
-	char   *_completion_function; \
-\
-	_completion_schema = strtokx(function, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_function = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-								   false, false, pset.encoding); \
-	if (_completion_function == NULL) \
-	{ \
-		completion_charp = Query_for_list_of_arguments; \
-		completion_info_charp = function; \
-	} \
-	else \
-	{ \
-		completion_charp = Query_for_list_of_arguments_with_schema; \
-		completion_info_charp = _completion_function; \
-		completion_info_charp2 = _completion_schema; \
-	} \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-/*
- * These macros simplify use of COMPLETE_WITH_LIST for short, fixed lists.
- * There is no COMPLETE_WITH_LIST1; use COMPLETE_WITH_CONST for that case.
- */
-#define COMPLETE_WITH_LIST2(s1, s2) \
-do { \
-	static const char *const list[] = { s1, s2, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST3(s1, s2, s3) \
-do { \
-	static const char *const list[] = { s1, s2, s3, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST4(s1, s2, s3, s4) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST5(s1, s2, s3, s4, s5) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST6(s1, s2, s3, s4, s5, s6) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST7(s1, s2, s3, s4, s5, s6, s7) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST8(s1, s2, s3, s4, s5, s6, s7, s8) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST9(s1, s2, s3, s4, s5, s6, s7, s8, s9) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST10(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-/*
- * Likewise for COMPLETE_WITH_LIST_CS.
- */
-#define COMPLETE_WITH_LIST_CS2(s1, s2) \
-do { \
-	static const char *const list[] = { s1, s2, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS3(s1, s2, s3) \
-do { \
-	static const char *const list[] = { s1, s2, s3, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS4(s1, s2, s3, s4) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS5(s1, s2, s3, s4, s5) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-/*
  * Assembly instructions for schema queries
  */
 
@@ -962,6 +783,8 @@ static char **get_previous_words(int point, char **buffer, int *nwords);
 
 static char *get_guctype(const char *varname);
 
+static char **psql_completion_internal(const char *text, char **previous_words,
+										   int previous_words_count);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -1117,156 +940,58 @@ psql_completion(const char *text, int start, int end)
 	/* The number of words found on the input line. */
 	int			previous_words_count;
 
-	/*
-	 * For compactness, we use these macros to reference previous_words[].
-	 * Caution: do not access a previous_words[] entry without having checked
-	 * previous_words_count to be sure it's valid.  In most cases below, that
-	 * check is implicit in a TailMatches() or similar macro, but in some
-	 * places we have to check it explicitly.
-	 */
-#define prev_wd   (previous_words[0])
-#define prev2_wd  (previous_words[1])
-#define prev3_wd  (previous_words[2])
-#define prev4_wd  (previous_words[3])
-#define prev5_wd  (previous_words[4])
-#define prev6_wd  (previous_words[5])
-#define prev7_wd  (previous_words[6])
-#define prev8_wd  (previous_words[7])
-#define prev9_wd  (previous_words[8])
-
-	/* Macros for matching the last N words before point, case-insensitively. */
-#define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, prev_wd))
-
-#define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd))
-
-#define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd))
-
-#define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd))
-
-#define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd))
-
-#define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd))
-
-#define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd))
-
-#define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd) && \
-	 word_matches(p8, prev8_wd))
-
-#define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd) && \
-	 word_matches(p8, prev8_wd) && \
-	 word_matches(p9, prev9_wd))
-
-	/* Macros for matching the last N words before point, case-sensitively. */
-#define TailMatchesCS1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches_cs(p1, prev_wd))
-#define TailMatchesCS2(p2, p1) \
-	(previous_words_count >= 2 && \
-	 word_matches_cs(p1, prev_wd) && \
-	 word_matches_cs(p2, prev2_wd))
+	(void) end;					/* "end" is not used */
 
-	/*
-	 * Macros for matching N words beginning at the start of the line,
-	 * case-insensitively.
-	 */
-#define Matches1(p1) \
-	(previous_words_count == 1 && \
-	 TailMatches1(p1))
-#define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
-	 TailMatches2(p1, p2))
-#define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
-	 TailMatches3(p1, p2, p3))
-#define Matches4(p1, p2, p3, p4) \
-	(previous_words_count == 4 && \
-	 TailMatches4(p1, p2, p3, p4))
-#define Matches5(p1, p2, p3, p4, p5) \
-	(previous_words_count == 5 && \
-	 TailMatches5(p1, p2, p3, p4, p5))
-#define Matches6(p1, p2, p3, p4, p5, p6) \
-	(previous_words_count == 6 && \
-	 TailMatches6(p1, p2, p3, p4, p5, p6))
-#define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(previous_words_count == 7 && \
-	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
-#define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(previous_words_count == 8 && \
-	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
-#define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(previous_words_count == 9 && \
-	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
+#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
+	rl_completion_append_character = ' ';
+#endif
+
+	/* Clear a few things. */
+	completion_charp = NULL;
+	completion_charpp = NULL;
+	completion_info_charp = NULL;
+	completion_info_charp2 = NULL;
 
 	/*
-	 * Macros for matching N words at the start of the line, regardless of
-	 * what is after them, case-insensitively.
+	 * Scan the input line to extract the words before our current position.
+	 * According to those we'll make some smart decisions on what the user is
+	 * probably intending to type.
 	 */
-#define HeadMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]))
+	previous_words = get_previous_words(start,
+										&words_buffer,
+										&previous_words_count);
+
+	matches  = psql_completion_internal(text, previous_words,
+										previous_words_count);
 
-#define HeadMatches2(p1, p2) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]))
+	/* free storage */
+	free(previous_words);
+	free(words_buffer);
 
-#define HeadMatches3(p1, p2, p3) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]) && \
-	 word_matches(p3, previous_words[previous_words_count - 3]))
+	if (matches != NULL)
+		return matches;
+
+	/*
+	 * If we still don't have anything to match we have to fabricate some sort
+	 * of default list. If we were to just return NULL, readline automatically
+	 * attempts filename completion, and that's usually no good.
+	 */
+#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
+	rl_completion_append_character = '\0';
+#endif
+	COMPLETE_WITH_CONST("");		/* This returns matches */
+}
 
+/*
+ * The completion function.
+ *
+ * Makes completion list.
+ * Note: COMPLETE_WITH_* macros immediately return to the caller.
+ */
+static char **
+psql_completion_internal(const char *text, char **previous_words,
+						 int previous_words_count)
+{
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
@@ -1298,90 +1023,80 @@ psql_completion(const char *text, int start, int end)
 		"\\timing", "\\unset", "\\x", "\\w", "\\watch", "\\z", "\\!", NULL
 	};
 
-	(void) end;					/* "end" is not used */
-
-#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
-	rl_completion_append_character = ' ';
-#endif
-
-	/* Clear a few things. */
-	completion_charp = NULL;
-	completion_charpp = NULL;
-	completion_info_charp = NULL;
-	completion_info_charp2 = NULL;
-
-	/*
-	 * Scan the input line to extract the words before our current position.
-	 * According to those we'll make some smart decisions on what the user is
-	 * probably intending to type.
-	 */
-	previous_words = get_previous_words(start,
-										&words_buffer,
-										&previous_words_count);
-
 	/* If current word is a backslash command, offer completions for that */
 	if (text[0] == '\\')
 		COMPLETE_WITH_LIST_CS(backslash_commands);
 
 	/* If current word is a variable interpolation, handle that case */
-	else if (text[0] == ':' && text[1] != ':')
+	if (text[0] == ':' && text[1] != ':')
 	{
 		if (text[1] == '\'')
-			matches = complete_from_variables(text, ":'", "'", true);
-		else if (text[1] == '"')
-			matches = complete_from_variables(text, ":\"", "\"", true);
-		else
-			matches = complete_from_variables(text, ":", "", true);
+			return complete_from_variables(text, ":'", "'", true);
+		if (text[1] == '"')
+			return complete_from_variables(text, ":\"", "\"", true);
+
+		return complete_from_variables(text, ":", "", true);
 	}
 
 	/* If no previous word, suggest one of the basic sql commands */
-	else if (previous_words_count == 0)
+	if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
 /* CREATE */
 	/* complete with something you can create */
-	else if (TailMatches1("CREATE"))
-		matches = completion_matches(text, create_command_generator);
+	if (Matches1("CREATE"))
+		return completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
-	else if (Matches1("DROP"))
-		matches = completion_matches(text, drop_command_generator);
+	if (Matches1("DROP"))
+		return completion_matches(text, drop_command_generator);
 
 /* ALTER */
 
 	/* ALTER TABLE */
-	else if (Matches2("ALTER", "TABLE"))
+	if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
 	/* ALTER something */
-	else if (Matches1("ALTER"))
+	if (Matches1("ALTER"))
 	{
 		static const char *const list_ALTER[] =
-		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
-			"EVENT TRIGGER", "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
-			"GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW", "OPERATOR",
-			"POLICY", "ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM", "TABLE",
-			"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
-		"USER", "USER MAPPING FOR", "VIEW", NULL};
+		{"AGGREGATE",
+		 "COLLATION", "CONVERSION",
+		 "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
+		 "EVENT TRIGGER", "EXTENSION",
+		 "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
+		 "GROUP",
+		 "INDEX",
+		 "LANGUAGE", "LARGE OBJECT",
+		 "MATERIALIZED VIEW", 
+		 "OPERATOR",
+		 "POLICY",
+		 "ROLE", "RULE",
+		 "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM",
+		 "TABLE", "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
+		 "USER", "USER MAPPING FOR",
+		 "VIEW",
+		 NULL};
 
 		COMPLETE_WITH_LIST(list_ALTER);
 	}
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx */
-	else if (TailMatches4("ALL", "IN", "TABLESPACE", MatchAny))
+	if (TailMatches4("ALL", "IN", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("SET TABLESPACE", "OWNED BY");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY */
-	else if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
+	if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY xxx */
-	else if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
+	if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("SET TABLESPACE");
 	/* ALTER AGGREGATE,FUNCTION <name> */
-	else if (Matches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
+	if (Matches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION <name> (...) */
-	else if (Matches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
+	if (Matches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
 	{
 		if (ends_with(prev_wd, ')'))
 			COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
@@ -1390,63 +1105,63 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER SCHEMA <name> */
-	else if (Matches3("ALTER", "SCHEMA", MatchAny))
+	if (Matches3("ALTER", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER TO", "RENAME TO");
 
 	/* ALTER COLLATION <name> */
-	else if (Matches3("ALTER", "COLLATION", MatchAny))
+	if (Matches3("ALTER", "COLLATION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER CONVERSION <name> */
-	else if (Matches3("ALTER", "CONVERSION", MatchAny))
+	if (Matches3("ALTER", "CONVERSION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER DATABASE <name> */
-	else if (Matches3("ALTER", "DATABASE", MatchAny))
+	if (Matches3("ALTER", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST7("RESET", "SET", "OWNER TO", "RENAME TO",
 							"IS_TEMPLATE", "ALLOW_CONNECTIONS",
 							"CONNECTION LIMIT");
 
 	/* ALTER EVENT TRIGGER */
-	else if (Matches3("ALTER", "EVENT", "TRIGGER"))
+	if (Matches3("ALTER", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
+	if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST4("DISABLE", "ENABLE", "OWNER TO", "RENAME TO");
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
+	if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST2("REPLICA", "ALWAYS");
 
 	/* ALTER EXTENSION <name> */
-	else if (Matches3("ALTER", "EXTENSION", MatchAny))
+	if (Matches3("ALTER", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST4("ADD", "DROP", "UPDATE", "SET SCHEMA");
 
 	/* ALTER EXTENSION <name> UPDATE */
-	else if (Matches4("ALTER", "EXTENSION", MatchAny, "UPDATE"))
+	if (Matches4("ALTER", "EXTENSION", MatchAny, "UPDATE"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions_with_TO);
 	}
 
 	/* ALTER EXTENSION <name> UPDATE TO */
-	else if (Matches5("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
+	if (Matches5("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
 	}
 
 	/* ALTER FOREIGN */
-	else if (Matches2("ALTER", "FOREIGN"))
+	if (Matches2("ALTER", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
 	/* ALTER FOREIGN TABLE <name> */
-	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
+	if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
 		{"ADD", "ALTER", "DISABLE TRIGGER", "DROP", "ENABLE", "INHERIT",
@@ -1457,42 +1172,42 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER INDEX */
-	else if (Matches2("ALTER", "INDEX"))
+	if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 	/* ALTER INDEX <name> */
-	else if (Matches3("ALTER", "INDEX", MatchAny))
+	if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
 	/* ALTER INDEX <name> SET */
-	else if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
+	if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
 		COMPLETE_WITH_LIST2("(", "TABLESPACE");
 	/* ALTER INDEX <name> RESET */
-	else if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
+	if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER INDEX <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
+	if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
 		COMPLETE_WITH_LIST3("fillfactor", "fastupdate",
 							"gin_pending_list_limit");
-	else if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
+	if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST3("fillfactor =", "fastupdate =",
 							"gin_pending_list_limit =");
 
 	/* ALTER LANGUAGE <name> */
-	else if (Matches3("ALTER", "LANGUAGE", MatchAny))
+	if (Matches3("ALTER", "LANGUAGE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER_TO", "RENAME TO");
 
 	/* ALTER LARGE OBJECT <oid> */
-	else if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
+	if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
 		COMPLETE_WITH_CONST("OWNER TO");
 
 	/* ALTER MATERIALIZED VIEW */
-	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
+	if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
 	/* ALTER USER,ROLE <name> */
-	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
-			 !TailMatches2("USER", "MAPPING"))
+	if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
+		!TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_ALTERUSER[] =
 		{"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -1506,7 +1221,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER USER,ROLE <name> WITH */
-	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
+	if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_ALTERUSER_WITH[] =
@@ -1521,43 +1236,43 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
-	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	if (Matches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* ALTER DEFAULT PRIVILEGES */
-	else if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
+	if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
 		COMPLETE_WITH_LIST3("FOR ROLE", "FOR USER", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR */
-	else if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
+	if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
 		COMPLETE_WITH_LIST2("ROLE", "USER");
 	/* ALTER DEFAULT PRIVILEGES { FOR ROLE ... | IN SCHEMA ... } */
-	else if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER", MatchAny) ||
+	if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER", MatchAny) ||
 		Matches6("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("GRANT", "REVOKE");
 	/* ALTER DOMAIN <name> */
-	else if (Matches3("ALTER", "DOMAIN", MatchAny))
+	if (Matches3("ALTER", "DOMAIN", MatchAny))
 		COMPLETE_WITH_LIST6("ADD", "DROP", "OWNER TO", "RENAME", "SET",
 							"VALIDATE CONSTRAINT");
 	/* ALTER DOMAIN <sth> DROP */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
 	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
 	}
 	/* ALTER DOMAIN <sth> RENAME */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("CONSTRAINT", "TO");
 	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
-	else if (Matches6("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
+	if (Matches6("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER DOMAIN <sth> SET */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	/* ALTER SEQUENCE <name> */
-	else if (Matches3("ALTER", "SEQUENCE", MatchAny))
+	if (Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1566,74 +1281,74 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
 	/* ALTER SEQUENCE <name> NO */
-	else if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
+	if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 	/* ALTER SERVER <name> */
-	else if (Matches3("ALTER", "SERVER", MatchAny))
+	if (Matches3("ALTER", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST4("VERSION", "OPTIONS", "OWNER TO", "RENAME TO");
 	/* ALTER SERVER <name> VERSION <version> */
-	else if (Matches5("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
+	if (Matches5("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
 		COMPLETE_WITH_CONST("OPTIONS");
 	/* ALTER SYSTEM SET, RESET, RESET ALL */
-	else if (Matches2("ALTER", "SYSTEM"))
+	if (Matches2("ALTER", "SYSTEM"))
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
-	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
+	if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
 	/* ALTER VIEW <name> */
-	else if (Matches3("ALTER", "VIEW", MatchAny))
+	if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
-	else if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
 	/* ALTER POLICY <name> */
-	else if (Matches2("ALTER", "POLICY"))
+	if (Matches2("ALTER", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* ALTER POLICY <name> ON */
-	else if (Matches3("ALTER", "POLICY", MatchAny))
+	if (Matches3("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
-	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
+	if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 	/* ALTER POLICY <name> ON <table> - show options */
-	else if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING (", "WITH CHECK (");
 	/* ALTER POLICY <name> ON <table> TO <role> */
-	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* ALTER POLICY <name> ON <table> USING ( */
-	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
-	else if (Matches7("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
+	if (Matches7("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
 		COMPLETE_WITH_CONST("(");
 
 	/* ALTER RULE <name>, add ON */
-	else if (Matches3("ALTER", "RULE", MatchAny))
+	if (Matches3("ALTER", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
-	else if (Matches4("ALTER", "RULE", MatchAny, "ON"))
+	if (Matches4("ALTER", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
 
 	/* ALTER RULE <name> ON <name> */
-	else if (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/* ALTER TRIGGER <name>, add ON */
-	else if (Matches3("ALTER", "TRIGGER", MatchAny))
+	if (Matches3("ALTER", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
-	else if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
+	if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
@@ -1642,17 +1357,17 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
-	else if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
+	if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER TRIGGER <name> ON <name> */
-	else if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/*
 	 * If we detect ALTER TABLE <name>, suggest sub commands
 	 */
-	else if (Matches3("ALTER", "TABLE", MatchAny))
+	if (Matches3("ALTER", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER2[] =
 		{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
@@ -1662,114 +1377,114 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
 	/* ALTER TABLE xxx ENABLE */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST5("ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE",
 							"TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
 		COMPLETE_WITH_LIST2("RULE", "TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
 	/* ALTER TABLE xxx INHERIT */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx NO INHERIT */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx DISABLE */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
 		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
 
 	/* ALTER TABLE xxx ALTER */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
 
 	/* ALTER TABLE xxx RENAME */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/* ALTER TABLE xxx RENAME yyy */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
+	if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
-	else if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
+	if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* If we have ALTER TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
+	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
 			 Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
 		COMPLETE_WITH_LIST4("TYPE", "SET", "RESET", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
 			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
 		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
+	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
 		 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
+	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
 	Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
 		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
 			 Matches8("ALTER", "TABLE", MatchAny, "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("DEFAULT", "NOT NULL");
-	else if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
 	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
 		COMPLETE_WITH_LIST7("(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED",
 							"WITH", "WITHOUT");
 
@@ -1777,19 +1492,19 @@ psql_completion(const char *text, int start, int end)
 	 * If we have ALTER TABLE <sth> SET TABLESPACE provide a list of
 	 * tablespaces
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	/* If we have ALTER TABLE <sth> SET WITH provide OIDS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
 		COMPLETE_WITH_CONST("OIDS");
 	/* If we have ALTER TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
 		COMPLETE_WITH_LIST2("CLUSTER", "OIDS");
 	/* ALTER TABLE <foo> RESET */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLE <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
 	{
 		static const char *const list_TABLEOPTIONS[] =
 		{
@@ -1827,103 +1542,103 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
-	else if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
+	if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
 		COMPLETE_WITH_LIST4("FULL", "NOTHING", "DEFAULT", "USING");
-	else if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
 		COMPLETE_WITH_CONST("IDENTITY");
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
-	else if (Matches3("ALTER", "TABLESPACE", MatchAny))
+	if (Matches3("ALTER", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "OWNER TO", "SET", "RESET");
 	/* ALTER TABLESPACE <foo> SET|RESET */
-	else if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
+	if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
+	if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
 		COMPLETE_WITH_LIST3("seq_page_cost", "random_page_cost",
 							"effective_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
-	else if (Matches3("ALTER", "TEXT", "SEARCH"))
+	if (Matches3("ALTER", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
 		COMPLETE_WITH_LIST2("RENAME TO", "SET SCHEMA");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING",
 							"DROP MAPPING FOR",
 							"OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* complete ALTER TYPE <foo> with actions */
-	else if (Matches3("ALTER", "TYPE", MatchAny))
+	if (Matches3("ALTER", "TYPE", MatchAny))
 		COMPLETE_WITH_LIST7("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE",
 							"DROP ATTRIBUTE",
 							"OWNER TO", "RENAME", "SET SCHEMA");
 	/* complete ALTER TYPE <foo> ADD with actions */
-	else if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
+	if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
 	/* ALTER TYPE <foo> RENAME	*/
-	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "TO");
 	/* ALTER TYPE xxx RENAME ATTRIBUTE yyy */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE", MatchAny))
+	if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/*
 	 * If we have ALTER TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list
 	 * of attributes
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
+	if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
+	if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* complete ALTER GROUP <foo> */
-	else if (Matches3("ALTER", "GROUP", MatchAny))
+	if (Matches3("ALTER", "GROUP", MatchAny))
 		COMPLETE_WITH_LIST3("ADD USER", "DROP USER", "RENAME TO");
 	/* complete ALTER GROUP <foo> ADD|DROP with USER */
-	else if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
+	if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
 		COMPLETE_WITH_CONST("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
-	else if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
+	if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* BEGIN */
-	else if (Matches1("BEGIN"))
+	if (Matches1("BEGIN"))
 		COMPLETE_WITH_LIST6("WORK", "TRANSACTION", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
 /* END, ABORT */
-	else if (Matches1("END|ABORT"))
+	if (Matches1("END|ABORT"))
 		COMPLETE_WITH_LIST2("WORK", "TRANSACTION");
 /* COMMIT */
-	else if (Matches1("COMMIT"))
+	if (Matches1("COMMIT"))
 		COMPLETE_WITH_LIST3("WORK", "TRANSACTION", "PREPARED");
 /* RELEASE SAVEPOINT */
-	else if (Matches1("RELEASE"))
+	if (Matches1("RELEASE"))
 		COMPLETE_WITH_CONST("SAVEPOINT");
 /* ROLLBACK */
-	else if (Matches1("ROLLBACK"))
+	if (Matches1("ROLLBACK"))
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
-	else if (Matches1("CLUSTER"))
+	if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
-	else if (Matches2("CLUSTER", "VERBOSE"))
+	if (Matches2("CLUSTER", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 	/* If we have CLUSTER <sth>, then add "USING" */
-	else if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
+	if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	else if (Matches3("CLUSTER", "VERBOSE", MatchAny))
+	if (Matches3("CLUSTER", "VERBOSE", MatchAny))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER <sth> USING, then add the index as well */
-	else if (Matches3("CLUSTER", MatchAny, "USING") ||
+	if (Matches3("CLUSTER", MatchAny, "USING") ||
 			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
@@ -1931,9 +1646,9 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* COMMENT */
-	else if (Matches1("COMMENT"))
+	if (Matches1("COMMENT"))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches2("COMMENT", "ON"))
+	if (Matches2("COMMENT", "ON"))
 	{
 		static const char *const list_COMMENT[] =
 		{"ACCESS METHOD", "CAST", "COLLATION", "CONVERSION", "DATABASE",
@@ -1946,26 +1661,26 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
-	else if (Matches4("COMMENT", "ON", "ACCESS", "METHOD"))
+	if (Matches4("COMMENT", "ON", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (Matches3("COMMENT", "ON", "FOREIGN"))
+	if (Matches3("COMMENT", "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
-	else if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
+	if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches3("COMMENT", "ON", "CONSTRAINT"))
+	if (Matches3("COMMENT", "ON", "CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
-	else if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
+	if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
+	if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
 	}
-	else if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
+	if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
+	if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	else if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
+	if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
 		Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
 			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
 		COMPLETE_WITH_CONST("IS");
@@ -1976,97 +1691,97 @@ psql_completion(const char *text, int start, int end)
 	 * If we have COPY, offer list of tables or "(" (Also cover the analogous
 	 * backslash command).
 	 */
-	else if (Matches1("COPY|\\copy"))
+	if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION ALL SELECT '('");
 	/* If we have COPY BINARY, complete with list of tables */
-	else if (Matches2("COPY", "BINARY"))
+	if (Matches2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* If we have COPY (, complete it with legal commands */
-	else if (Matches2("COPY|\\copy", "("))
+	if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
 	/* If we have COPY [BINARY] <sth>, complete it with "TO" or "FROM" */
-	else if (Matches2("COPY|\\copy", MatchAny) ||
+	if (Matches2("COPY|\\copy", MatchAny) ||
 			 Matches3("COPY", "BINARY", MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "TO");
 	/* If we have COPY [BINARY] <sth> FROM|TO, complete with filename */
-	else if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
+	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
 			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
 	{
 		completion_charp = "";
-		matches = completion_matches(text, complete_from_files);
+		return completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename */
-	else if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
+	if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
 			 Matches5("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
 		COMPLETE_WITH_LIST6("BINARY", "OIDS", "DELIMITER", "NULL", "CSV",
 							"ENCODING");
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename CSV */
-	else if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
+	if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
 			 Matches6("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny, "CSV"))
 		COMPLETE_WITH_LIST5("HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE",
 							"FORCE NOT NULL");
 
 	/* CREATE ACCESS METHOD */
 	/* Complete "CREATE ACCESS METHOD <name>" */
-	else if (Matches4("CREATE", "ACCESS", "METHOD", MatchAny))
+	if (Matches4("CREATE", "ACCESS", "METHOD", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE" */
-	else if (Matches5("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
+	if (Matches5("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
 		COMPLETE_WITH_CONST("INDEX");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE <type>" */
-	else if (Matches6("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
+	if (Matches6("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
 		COMPLETE_WITH_CONST("HANDLER");
 
 	/* CREATE DATABASE */
-	else if (Matches3("CREATE", "DATABASE", MatchAny))
+	if (Matches3("CREATE", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST9("OWNER", "TEMPLATE", "ENCODING", "TABLESPACE",
 							"IS_TEMPLATE",
 							"ALLOW_CONNECTIONS", "CONNECTION LIMIT",
 							"LC_COLLATE", "LC_CTYPE");
 
-	else if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
+	if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
-	else if (Matches2("CREATE", "EXTENSION"))
+	if (Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
-	else if (Matches3("CREATE", "EXTENSION", MatchAny))
+	if (Matches3("CREATE", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
 	/* CREATE EXTENSION <name> VERSION */
-	else if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
+	if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
 	}
 
 	/* CREATE FOREIGN */
-	else if (Matches2("CREATE", "FOREIGN"))
+	if (Matches2("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* CREATE FOREIGN DATA WRAPPER */
-	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	else if (TailMatches2("CREATE", "UNIQUE"))
+	if (TailMatches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
 	/*
 	 * If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY", and
 	 * existing indexes
 	 */
-	else if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	if (TailMatches2("CREATE|UNIQUE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
 								   " UNION SELECT 'CONCURRENTLY'");
 	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	else if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
+	if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
 			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
@@ -2074,11 +1789,11 @@ psql_completion(const char *text, int start, int end)
 	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
 	 * indexes
 	 */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+	if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'");
 	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
+	if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
 			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
@@ -2086,117 +1801,117 @@ psql_completion(const char *text, int start, int end)
 	 * Complete INDEX <name> ON <table> with a list of table columns (which
 	 * should really be in parens)
 	 */
-	else if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-			 TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+	if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
+		TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-			 TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
+	if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
+		TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* same if you put in USING */
-	else if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
+	if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	else if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
+	if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
+	if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
 			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
 			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-	else if (Matches3("CREATE", "POLICY", MatchAny))
+	if (Matches3("CREATE", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
-	else if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
+	if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
-	else if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
+	if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING (", "WITH CHECK (");
 	/* CREATE POLICY <name> ON <table> FOR ALL|SELECT|INSERT|UPDATE|DELETE */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
 		COMPLETE_WITH_LIST5("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
 	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
 		COMPLETE_WITH_LIST2("TO", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> FOR SELECT|DELETE TO|USING" */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
 		COMPLETE_WITH_LIST2("TO", "USING (");
 	/* CREATE POLICY <name> ON <table> FOR ALL|UPDATE TO|USING|WITH CHECK */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
 		COMPLETE_WITH_LIST3("TO", "USING (", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS ON" */
-	else if (Matches3("CREATE", "RULE", MatchAny))
+	if (Matches3("CREATE", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("AS ON");
 	/* Complete "CREATE RULE <sth> AS" with "ON" */
-	else if (Matches4("CREATE", "RULE", MatchAny, "AS"))
+	if (Matches4("CREATE", "RULE", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE RULE <sth> AS ON" with SELECT|UPDATE|INSERT|DELETE */
-	else if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
+	if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
 	/* Complete "AS ON SELECT|UPDATE|INSERT|DELETE" with a "TO" */
-	else if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
+	if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
-	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
+	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	else if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
+	if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
 			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	else if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
+	if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
 		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
-	else if (Matches3("CREATE", "SERVER", MatchAny))
+	if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
 /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	else if (TailMatches2("CREATE", "UNLOGGED"))
+	if (TailMatches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
 /* CREATE TABLESPACE */
-	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
+	if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
 	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
-	else if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
+	if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
 		COMPLETE_WITH_CONST("LOCATION");
 
 /* CREATE TEXT SEARCH */
-	else if (Matches3("CREATE", "TEXT", "SEARCH"))
+	if (Matches3("CREATE", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	else if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	if (TailMatches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+	if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
 	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
@@ -2204,17 +1919,17 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
 	 * tables
 	 */
-	else if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
+	if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
-	else if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
+	if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
 
 /* CREATE ROLE,USER,GROUP <name> */
-	else if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
+	if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_CREATEROLE[] =
@@ -2229,7 +1944,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE ROLE,USER,GROUP <name> WITH */
-	else if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
+	if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_CREATEROLE_WITH[] =
@@ -2247,70 +1962,70 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
 	 * PASSWORD
 	 */
-	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
-	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
+	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE VIEW <name> with AS */
-	else if (TailMatches3("CREATE", "VIEW", MatchAny))
+	if (TailMatches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
+	if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches2("CREATE", "MATERIALIZED"))
+	if (Matches2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE EVENT TRIGGER */
-	else if (Matches2("CREATE", "EVENT"))
+	if (Matches2("CREATE", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	/* Complete CREATE EVENT TRIGGER <name> with ON */
-	else if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
+	if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
-	else if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
+	if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_LIST3("ddl_command_start", "ddl_command_end", "sql_drop");
 
 /* DECLARE */
-	else if (Matches2("DECLARE", MatchAny))
+	if (Matches2("DECLARE", MatchAny))
 		COMPLETE_WITH_LIST5("BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL",
 							"CURSOR");
-	else if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
+	if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
 		COMPLETE_WITH_LIST3("WITH HOLD", "WITHOUT HOLD", "FOR");
 
 /* DELETE --- can be inside EXPLAIN, RULE, etc */
 	/* ... despite which, only complete DELETE with FROM at start of line */
-	else if (Matches1("DELETE"))
+	if (Matches1("DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
-	else if (TailMatches2("DELETE", "FROM"))
+	if (TailMatches2("DELETE", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete DELETE FROM <table> */
-	else if (TailMatches3("DELETE", "FROM", MatchAny))
+	if (TailMatches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
 	/* XXX: implement tab completion for DELETE ... USING */
 
 /* DISCARD */
-	else if (Matches1("DISCARD"))
+	if (Matches1("DISCARD"))
 		COMPLETE_WITH_LIST4("ALL", "PLANS", "SEQUENCES", "TEMP");
 
 /* DO */
-	else if (Matches1("DO"))
+	if (Matches1("DO"))
 		COMPLETE_WITH_CONST("LANGUAGE");
 
 /* DROP */
 	/* Complete DROP object with CASCADE / RESTRICT */
-	else if (Matches3("DROP",
+	if (Matches3("DROP",
 					  "COLLATION|CONVERSION|DOMAIN|EXTENSION|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW",
 					  MatchAny) ||
 			 Matches4("DROP", "ACCESS", "METHOD", MatchAny) ||
@@ -2323,88 +2038,88 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
+	if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
-	else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
+	if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
-	else if (Matches2("DROP", "FOREIGN"))
+	if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* DROP INDEX */
-	else if (Matches2("DROP", "INDEX"))
+	if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
+	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches3("DROP", "INDEX", MatchAny))
+	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	else if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
+	if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
-	else if (Matches2("DROP", "MATERIALIZED"))
+	if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
 	/* DROP OWNED BY */
-	else if (Matches2("DROP", "OWNED"))
+	if (Matches2("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (Matches3("DROP", "OWNED", "BY"))
+	if (Matches3("DROP", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
-	else if (Matches3("DROP", "TEXT", "SEARCH"))
+	if (Matches3("DROP", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (Matches3("DROP", "TRIGGER", MatchAny))
+	if (Matches3("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
+	if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
 	}
-	else if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
+	if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP ACCESS METHOD */
-	else if (Matches2("DROP", "ACCESS"))
+	if (Matches2("DROP", "ACCESS"))
 		COMPLETE_WITH_CONST("METHOD");
-	else if (Matches3("DROP", "ACCESS", "METHOD"))
+	if (Matches3("DROP", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 
 	/* DROP EVENT TRIGGER */
-	else if (Matches2("DROP", "EVENT"))
+	if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
-	else if (Matches3("DROP", "EVENT", "TRIGGER"))
+	if (Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* DROP POLICY <name>  */
-	else if (Matches2("DROP", "POLICY"))
+	if (Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* DROP POLICY <name> ON */
-	else if (Matches3("DROP", "POLICY", MatchAny))
+	if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* DROP POLICY <name> ON <table> */
-	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
+	if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 
 	/* DROP RULE */
-	else if (Matches3("DROP", "RULE", MatchAny))
+	if (Matches3("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
+	if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
-	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
+	if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 /* EXECUTE */
-	else if (Matches1("EXECUTE"))
+	if (Matches1("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
 
 /* EXPLAIN */
@@ -2412,22 +2127,22 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
-	else if (Matches1("EXPLAIN"))
+	if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"ANALYZE", "VERBOSE");
-	else if (Matches2("EXPLAIN", "ANALYZE"))
+	if (Matches2("EXPLAIN", "ANALYZE"))
 		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"VERBOSE");
-	else if (Matches2("EXPLAIN", "VERBOSE") ||
+	if (Matches2("EXPLAIN", "VERBOSE") ||
 			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
 		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-	else if (Matches1("FETCH|MOVE"))
+	if (Matches1("FETCH|MOVE"))
 		COMPLETE_WITH_LIST4("ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE");
 	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
-	else if (Matches2("FETCH|MOVE", MatchAny))
+	if (Matches2("FETCH|MOVE", MatchAny))
 		COMPLETE_WITH_LIST3("ALL", "NEXT", "PRIOR");
 
 	/*
@@ -2435,31 +2150,31 @@ psql_completion(const char *text, int start, int end)
 	 * but we may as well tab-complete both: perhaps some users prefer one
 	 * variant or the other.
 	 */
-	else if (Matches3("FETCH|MOVE", MatchAny, MatchAny))
+	if (Matches3("FETCH|MOVE", MatchAny, MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "IN");
 
 /* FOREIGN DATA WRAPPER */
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
-	else if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
+	if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
 			 !TailMatches4("CREATE", MatchAny, MatchAny, MatchAny))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 	/* applies in CREATE SERVER */
-	else if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
+	if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
 			 HeadMatches2("CREATE", "SERVER"))
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /* FOREIGN TABLE */
-	else if (TailMatches2("FOREIGN", "TABLE") &&
+	if (TailMatches2("FOREIGN", "TABLE") &&
 			 !TailMatches3("CREATE", MatchAny, MatchAny))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* FOREIGN SERVER */
-	else if (TailMatches2("FOREIGN", "SERVER"))
+	if (TailMatches2("FOREIGN", "SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
 
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	else if (TailMatches1("GRANT|REVOKE"))
+	if (TailMatches1("GRANT|REVOKE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'SELECT'"
 							" UNION SELECT 'INSERT'"
@@ -2479,11 +2194,11 @@ psql_completion(const char *text, int start, int end)
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	else if (TailMatches2("GRANT|REVOKE", MatchAny))
+	if (TailMatches2("GRANT|REVOKE", MatchAny))
 	{
 		if (TailMatches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
-		else if (TailMatches2("GRANT", MatchAny))
+		if (TailMatches2("GRANT", MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2500,7 +2215,7 @@ psql_completion(const char *text, int start, int end)
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
 	 */
-	else if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
+	if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
 								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
 								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
@@ -2518,11 +2233,11 @@ psql_completion(const char *text, int start, int end)
 								   " UNION SELECT 'TABLESPACE'"
 								   " UNION SELECT 'TYPE'");
 
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
 							"TABLES IN SCHEMA");
 
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/*
@@ -2531,27 +2246,27 @@ psql_completion(const char *text, int start, int end)
 	 *
 	 * Complete "GRANT/REVOKE * ON *" with "TO/FROM".
 	 */
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
 		if (TailMatches1("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-		else if (TailMatches1("DOMAIN"))
+		if (TailMatches1("DOMAIN"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-		else if (TailMatches1("FUNCTION"))
+		if (TailMatches1("FUNCTION"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-		else if (TailMatches1("LANGUAGE"))
+		if (TailMatches1("LANGUAGE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-		else if (TailMatches1("SCHEMA"))
+		if (TailMatches1("SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-		else if (TailMatches1("SEQUENCE"))
+		if (TailMatches1("SEQUENCE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-		else if (TailMatches1("TABLE"))
+		if (TailMatches1("TABLE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-		else if (TailMatches1("TABLESPACE"))
+		if (TailMatches1("TABLESPACE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-		else if (TailMatches1("TYPE"))
+		if (TailMatches1("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-		else if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
+		if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2561,18 +2276,18 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "GRANT/REVOKE ... TO/FROM" with username, PUBLIC,
 	 * CURRENT_USER, or SESSION_USER.
 	 */
-	else if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
+	if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
 			 (HeadMatches1("REVOKE") && TailMatches1("FROM")))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 
 	/* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */
-	else if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
+	if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (HeadMatches1("REVOKE") && TailMatches3("ON", MatchAny, MatchAny))
+	if (HeadMatches1("REVOKE") && TailMatches3("ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("FROM");
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	else if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
+	if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
 	{
 		if (TailMatches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2581,7 +2296,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	else if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 	{
 		if (TailMatches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2590,7 +2305,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	else if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
+	if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
 	{
 		if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2599,62 +2314,62 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* GROUP BY */
-	else if (TailMatches3("FROM", MatchAny, "GROUP"))
+	if (TailMatches3("FROM", MatchAny, "GROUP"))
 		COMPLETE_WITH_CONST("BY");
 
 /* IMPORT FOREIGN SCHEMA */
-	else if (Matches1("IMPORT"))
+	if (Matches1("IMPORT"))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
-	else if (Matches2("IMPORT", "FOREIGN"))
+	if (Matches2("IMPORT", "FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT --- can be inside EXPLAIN, RULE, etc */
 	/* Complete INSERT with "INTO" */
-	else if (TailMatches1("INSERT"))
+	if (TailMatches1("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
-	else if (TailMatches2("INSERT", "INTO"))
+	if (TailMatches2("INSERT", "INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete "INSERT INTO <table> (" with attribute names */
-	else if (TailMatches4("INSERT", "INTO", MatchAny, "("))
+	if (TailMatches4("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES"
 	 */
-	else if (TailMatches3("INSERT", "INTO", MatchAny))
+	if (TailMatches3("INSERT", "INTO", MatchAny))
 		COMPLETE_WITH_LIST5("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES");
 
 	/*
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE"
 	 */
-	else if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
+	if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
 			 ends_with(prev_wd, ')'))
 		COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES");
 
 	/* Insert an open parenthesis after "VALUES" */
-	else if (TailMatches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
+	if (TailMatches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
 		COMPLETE_WITH_CONST("(");
 
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
-	else if (Matches1("LOCK"))
+	if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION SELECT 'TABLE'");
-	else if (Matches2("LOCK", "TABLE"))
+	if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* For the following, handle the case of a single table only for now */
 
 	/* Complete LOCK [TABLE] <table> with "IN" */
-	else if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
+	if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
 			 Matches3("LOCK", "TABLE", MatchAny))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	else if (Matches3("LOCK", MatchAny, "IN") ||
+	if (Matches3("LOCK", MatchAny, "IN") ||
 			 Matches4("LOCK", "TABLE", MatchAny, "IN"))
 		COMPLETE_WITH_LIST8("ACCESS SHARE MODE",
 							"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
@@ -2663,25 +2378,25 @@ psql_completion(const char *text, int start, int end)
 							"EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE");
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
-	else if (TailMatches1("NOTIFY"))
+	if (TailMatches1("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
 
 /* OPTIONS */
-	else if (TailMatches1("OPTIONS"))
+	if (TailMatches1("OPTIONS"))
 		COMPLETE_WITH_CONST("(");
 
 /* OWNER TO  - complete with available roles */
-	else if (TailMatches2("OWNER", "TO"))
+	if (TailMatches2("OWNER", "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* ORDER BY */
-	else if (TailMatches3("FROM", MatchAny, "ORDER"))
+	if (TailMatches3("FROM", MatchAny, "ORDER"))
 		COMPLETE_WITH_CONST("BY");
-	else if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
+	if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 /* PREPARE xx AS */
-	else if (Matches3("PREPARE", MatchAny, "AS"))
+	if (Matches3("PREPARE", MatchAny, "AS"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
 
 /*
@@ -2690,60 +2405,60 @@ psql_completion(const char *text, int start, int end)
  */
 
 /* REASSIGN OWNED BY xxx TO yyy */
-	else if (Matches1("REASSIGN"))
+	if (Matches1("REASSIGN"))
 		COMPLETE_WITH_CONST("OWNED BY");
-	else if (Matches2("REASSIGN", "OWNED"))
+	if (Matches2("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (Matches3("REASSIGN", "OWNED", "BY"))
+	if (Matches3("REASSIGN", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
+	if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
+	if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* REFRESH MATERIALIZED VIEW */
-	else if (Matches1("REFRESH"))
+	if (Matches1("REFRESH"))
 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
-	else if (Matches2("REFRESH", "MATERIALIZED"))
+	if (Matches2("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
+	if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
+	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
+	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
+	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
-	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
+	if (Matches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
-	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
+	if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
-	else if (Matches7("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
+	if (Matches7("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
 
 /* REINDEX */
-	else if (Matches1("REINDEX"))
+	if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
-	else if (Matches2("REINDEX", "TABLE"))
+	if (Matches2("REINDEX", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-	else if (Matches2("REINDEX", "INDEX"))
+	if (Matches2("REINDEX", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches2("REINDEX", "SCHEMA"))
+	if (Matches2("REINDEX", "SCHEMA"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-	else if (Matches2("REINDEX", "SYSTEM|DATABASE"))
+	if (Matches2("REINDEX", "SYSTEM|DATABASE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 
 /* SECURITY LABEL */
-	else if (Matches1("SECURITY"))
+	if (Matches1("SECURITY"))
 		COMPLETE_WITH_CONST("LABEL");
-	else if (Matches2("SECURITY", "LABEL"))
+	if (Matches2("SECURITY", "LABEL"))
 		COMPLETE_WITH_LIST2("ON", "FOR");
-	else if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
+	if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches3("SECURITY", "LABEL", "ON") ||
+	if (Matches3("SECURITY", "LABEL", "ON") ||
 			 Matches5("SECURITY", "LABEL", "FOR", MatchAny, "ON"))
 	{
 		static const char *const list_SECURITY_LABEL[] =
@@ -2754,7 +2469,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_SECURITY_LABEL);
 	}
-	else if (Matches5("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
+	if (Matches5("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("IS");
 
 /* SELECT */
@@ -2762,69 +2477,69 @@ psql_completion(const char *text, int start, int end)
 
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
-	else if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
+	if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
-	else if (Matches1("SHOW"))
+	if (Matches1("SHOW"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
 	/* Complete "SET TRANSACTION" */
-	else if (Matches2("SET", "TRANSACTION"))
+	if (Matches2("SET", "TRANSACTION"))
 		COMPLETE_WITH_LIST5("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	else if (Matches2("BEGIN|START", "TRANSACTION") ||
-			 Matches2("BEGIN", "WORK") ||
-			 Matches1("BEGIN") ||
-		  Matches5("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
+	if (Matches2("BEGIN|START", "TRANSACTION") ||
+		Matches2("BEGIN", "WORK") ||
+		Matches1("BEGIN") ||
+		Matches5("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
 		COMPLETE_WITH_LIST4("ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
-			 Matches2("BEGIN", "NOT") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
+		Matches2("BEGIN", "NOT") ||
+		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
 		COMPLETE_WITH_CONST("DEFERRABLE");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
-			 Matches2("BEGIN", "ISOLATION") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
+		Matches2("BEGIN", "ISOLATION") ||
+		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
 		COMPLETE_WITH_CONST("LEVEL");
-	else if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
-			 Matches3("BEGIN", "ISOLATION", "LEVEL") ||
-			 Matches7("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
+	if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
+		Matches3("BEGIN", "ISOLATION", "LEVEL") ||
+		Matches7("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
 		COMPLETE_WITH_LIST3("READ", "REPEATABLE READ", "SERIALIZABLE");
-	else if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
-			 Matches4("BEGIN", "ISOLATION", "LEVEL", "READ") ||
-			 Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
+	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
+		Matches4("BEGIN", "ISOLATION", "LEVEL", "READ") ||
+		Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
 		COMPLETE_WITH_LIST2("UNCOMMITTED", "COMMITTED");
-	else if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
-			 Matches4("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
-			 Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
+	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
+		Matches4("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
+		Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
 		COMPLETE_WITH_CONST("READ");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
-			 Matches2("BEGIN", "READ") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
+		Matches2("BEGIN", "READ") ||
+		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
-	else if (Matches2("SET", "CONSTRAINTS"))
+	if (Matches2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
-	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
+	if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
-	else if (Matches2("SET", "ROLE"))
+	if (Matches2("SET", "ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
-	else if (Matches2("SET", "SESSION"))
+	if (Matches2("SET", "SESSION"))
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
-	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
+	if (Matches3("SET", "SESSION", "AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
 	/* Complete RESET SESSION with AUTHORIZATION */
-	else if (Matches2("RESET", "SESSION"))
+	if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
 	/* Complete SET <var> with "TO" */
-	else if (Matches2("SET", MatchAny))
+	if (Matches2("SET", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete ALTER DATABASE|FUNCTION|ROLE|USER ... SET <name> */
-	else if (HeadMatches2("ALTER", "DATABASE|FUNCTION|ROLE|USER") &&
+	if (HeadMatches2("ALTER", "DATABASE|FUNCTION|ROLE|USER") &&
 			 TailMatches2("SET", MatchAny))
 		COMPLETE_WITH_LIST2("FROM CURRENT", "TO");
 	/* Suggest possible variable values */
-	else if (TailMatches3("SET", MatchAny, "TO|="))
+	if (TailMatches3("SET", MatchAny, "TO|="))
 	{
 		/* special cased code for individual GUCs */
 		if (TailMatches2("DateStyle", "TO|="))
@@ -2837,112 +2552,117 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST(my_list);
 		}
-		else if (TailMatches2("search_path", "TO|="))
+		if (TailMatches2("search_path", "TO|="))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
 								" AND nspname not like 'pg\\_toast%%' "
 								" AND nspname not like 'pg\\_temp%%' "
 								" UNION SELECT 'DEFAULT' ");
 		else
 		{
-			/* generic, type based, GUC support */
+			/* generic, type based, GUC support, guctype is malloc'ed */
 			char	   *guctype = get_guctype(prev2_wd);
 
 			if (guctype && strcmp(guctype, "enum") == 0)
 			{
 				char		querybuf[1024];
 
+				free(guctype);
 				snprintf(querybuf, sizeof(querybuf), Query_for_enum, prev2_wd);
 				COMPLETE_WITH_QUERY(querybuf);
 			}
-			else if (guctype && strcmp(guctype, "bool") == 0)
+
+			if (guctype && strcmp(guctype, "bool") == 0)
+			{
+				free(guctype);
 				COMPLETE_WITH_LIST9("on", "off", "true", "false", "yes", "no",
 									"1", "0", "DEFAULT");
-			else
-				COMPLETE_WITH_CONST("DEFAULT");
+			}
 
 			if (guctype)
 				free(guctype);
+
+			COMPLETE_WITH_CONST("DEFAULT");
 		}
 	}
 
 /* START TRANSACTION */
-	else if (Matches1("START"))
+	if (Matches1("START"))
 		COMPLETE_WITH_CONST("TRANSACTION");
 
 /* TABLE, but not TABLE embedded in other commands */
-	else if (Matches1("TABLE"))
+	if (Matches1("TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
 /* TABLESAMPLE */
-	else if (TailMatches1("TABLESAMPLE"))
+	if (TailMatches1("TABLESAMPLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods);
-	else if (TailMatches2("TABLESAMPLE", MatchAny))
+	if (TailMatches2("TABLESAMPLE", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
-	else if (Matches1("TRUNCATE"))
+	if (Matches1("TRUNCATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* UNLISTEN */
-	else if (Matches1("UNLISTEN"))
+	if (Matches1("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
 
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
-	else if (TailMatches1("UPDATE"))
+	if (TailMatches1("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete UPDATE <table> with "SET" */
-	else if (TailMatches2("UPDATE", MatchAny))
+	if (TailMatches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
 	/* Complete UPDATE <table> SET with list of attributes */
-	else if (TailMatches3("UPDATE", MatchAny, "SET"))
+	if (TailMatches3("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* UPDATE <table> SET <attr> = */
-	else if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
+	if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
+	if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
-	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
+	if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
 							" UNION SELECT 'PUBLIC'"
 							" UNION SELECT 'USER'");
-	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
+	if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
+	if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("SERVER");
-	else if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
+	if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /*
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
-	else if (Matches1("VACUUM"))
+	if (Matches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'FULL'"
 								   " UNION SELECT 'FREEZE'"
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches2("VACUUM", "FULL|FREEZE"))
+	if (Matches2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
+	if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
+	if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (Matches2("VACUUM", "VERBOSE"))
+	if (Matches2("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (Matches2("VACUUM", "ANALYZE"))
+	if (Matches2("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (HeadMatches1("VACUUM"))
+	if (HeadMatches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 /* WITH [RECURSIVE] */
@@ -2951,114 +2671,114 @@ psql_completion(const char *text, int start, int end)
 	 * Only match when WITH is the first word, as WITH may appear in many
 	 * other contexts.
 	 */
-	else if (Matches1("WITH"))
+	if (Matches1("WITH"))
 		COMPLETE_WITH_CONST("RECURSIVE");
 
 /* ANALYZE */
 	/* Complete with list of tables */
-	else if (Matches1("ANALYZE"))
+	if (Matches1("ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
-	else if (TailMatches2(MatchAny, "WHERE"))
+	if (TailMatches2(MatchAny, "WHERE"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
-	else if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
+	if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* ... JOIN ... */
-	else if (TailMatches1("JOIN"))
+	if (TailMatches1("JOIN"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
-	else if (TailMatchesCS1("\\?"))
+	if (TailMatchesCS1("\\?"))
 		COMPLETE_WITH_LIST_CS3("commands", "options", "variables");
-	else if (TailMatchesCS1("\\connect|\\c"))
+	if (TailMatchesCS1("\\connect|\\c"))
 	{
 		if (!recognized_connection_string(text))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 	}
-	else if (TailMatchesCS2("\\connect|\\c", MatchAny))
+	if (TailMatchesCS2("\\connect|\\c", MatchAny))
 	{
 		if (!recognized_connection_string(prev_wd))
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	}
-	else if (TailMatchesCS1("\\da*"))
+	if (TailMatchesCS1("\\da*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
-	else if (TailMatchesCS1("\\dA*"))
+	if (TailMatchesCS1("\\dA*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (TailMatchesCS1("\\db*"))
+	if (TailMatchesCS1("\\db*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-	else if (TailMatchesCS1("\\dD*"))
+	if (TailMatchesCS1("\\dD*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-	else if (TailMatchesCS1("\\des*"))
+	if (TailMatchesCS1("\\des*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
-	else if (TailMatchesCS1("\\deu*"))
+	if (TailMatchesCS1("\\deu*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (TailMatchesCS1("\\dew*"))
+	if (TailMatchesCS1("\\dew*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
-	else if (TailMatchesCS1("\\df*"))
+	if (TailMatchesCS1("\\df*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 
-	else if (TailMatchesCS1("\\dFd*"))
+	if (TailMatchesCS1("\\dFd*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
-	else if (TailMatchesCS1("\\dFp*"))
+	if (TailMatchesCS1("\\dFp*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
-	else if (TailMatchesCS1("\\dFt*"))
+	if (TailMatchesCS1("\\dFt*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
 	/* must be at end of \dF alternatives: */
-	else if (TailMatchesCS1("\\dF*"))
+	if (TailMatchesCS1("\\dF*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
 
-	else if (TailMatchesCS1("\\di*"))
+	if (TailMatchesCS1("\\di*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (TailMatchesCS1("\\dL*"))
+	if (TailMatchesCS1("\\dL*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-	else if (TailMatchesCS1("\\dn*"))
+	if (TailMatchesCS1("\\dn*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-	else if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
+	if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-	else if (TailMatchesCS1("\\ds*"))
+	if (TailMatchesCS1("\\ds*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-	else if (TailMatchesCS1("\\dt*"))
+	if (TailMatchesCS1("\\dt*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-	else if (TailMatchesCS1("\\dT*"))
+	if (TailMatchesCS1("\\dT*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-	else if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
+	if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (TailMatchesCS1("\\dv*"))
+	if (TailMatchesCS1("\\dv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (TailMatchesCS1("\\dx*"))
+	if (TailMatchesCS1("\\dx*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
-	else if (TailMatchesCS1("\\dm*"))
+	if (TailMatchesCS1("\\dm*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (TailMatchesCS1("\\dE*"))
+	if (TailMatchesCS1("\\dE*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
-	else if (TailMatchesCS1("\\dy*"))
+	if (TailMatchesCS1("\\dy*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* must be at end of \d alternatives: */
-	else if (TailMatchesCS1("\\d*"))
+	if (TailMatchesCS1("\\d*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
-	else if (TailMatchesCS1("\\ef"))
+	if (TailMatchesCS1("\\ef"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (TailMatchesCS1("\\ev"))
+	if (TailMatchesCS1("\\ev"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
-	else if (TailMatchesCS1("\\encoding"))
+	if (TailMatchesCS1("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
-	else if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
+	if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
 		COMPLETE_WITH_LIST(sql_commands);
-	else if (TailMatchesCS1("\\l*") && !TailMatchesCS1("\\lo*"))
+	if (TailMatchesCS1("\\l*") && !TailMatchesCS1("\\lo*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-	else if (TailMatchesCS1("\\password"))
+	if (TailMatchesCS1("\\password"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (TailMatchesCS1("\\pset"))
+	if (TailMatchesCS1("\\pset"))
 	{
 		static const char *const my_list[] =
 		{"border", "columns", "expanded", "fieldsep", "fieldsep_zero",
@@ -3069,7 +2789,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (TailMatchesCS2("\\pset", MatchAny))
+	if (TailMatchesCS2("\\pset", MatchAny))
 	{
 		if (TailMatchesCS1("format"))
 		{
@@ -3079,53 +2799,53 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (TailMatchesCS1("linestyle"))
+		if (TailMatchesCS1("linestyle"))
 			COMPLETE_WITH_LIST_CS3("ascii", "old-ascii", "unicode");
-		else if (TailMatchesCS1("unicode_border_linestyle|"
+		if (TailMatchesCS1("unicode_border_linestyle|"
 								"unicode_column_linestyle|"
 								"unicode_header_linestyle"))
 			COMPLETE_WITH_LIST_CS2("single", "double");
 	}
-	else if (TailMatchesCS1("\\unset"))
+	if (TailMatchesCS1("\\unset"))
 	{
-		matches = complete_from_variables(text, "", "", true);
+		return complete_from_variables(text, "", "", true);
 	}
-	else if (TailMatchesCS1("\\set"))
+	if (TailMatchesCS1("\\set"))
 	{
-		matches = complete_from_variables(text, "", "", false);
+		return complete_from_variables(text, "", "", false);
 	}
-	else if (TailMatchesCS2("\\set", MatchAny))
+	if (TailMatchesCS2("\\set", MatchAny))
 	{
 		if (TailMatchesCS1("AUTOCOMMIT|ON_ERROR_STOP|QUIET|"
 						   "SINGLELINE|SINGLESTEP"))
 			COMPLETE_WITH_LIST_CS2("on", "off");
-		else if (TailMatchesCS1("COMP_KEYWORD_CASE"))
+		if (TailMatchesCS1("COMP_KEYWORD_CASE"))
 			COMPLETE_WITH_LIST_CS4("lower", "upper",
 								   "preserve-lower", "preserve-upper");
-		else if (TailMatchesCS1("ECHO"))
+		if (TailMatchesCS1("ECHO"))
 			COMPLETE_WITH_LIST_CS4("errors", "queries", "all", "none");
-		else if (TailMatchesCS1("ECHO_HIDDEN"))
+		if (TailMatchesCS1("ECHO_HIDDEN"))
 			COMPLETE_WITH_LIST_CS3("noexec", "off", "on");
-		else if (TailMatchesCS1("HISTCONTROL"))
+		if (TailMatchesCS1("HISTCONTROL"))
 			COMPLETE_WITH_LIST_CS4("ignorespace", "ignoredups",
 								   "ignoreboth", "none");
-		else if (TailMatchesCS1("ON_ERROR_ROLLBACK"))
+		if (TailMatchesCS1("ON_ERROR_ROLLBACK"))
 			COMPLETE_WITH_LIST_CS3("on", "off", "interactive");
-		else if (TailMatchesCS1("SHOW_CONTEXT"))
+		if (TailMatchesCS1("SHOW_CONTEXT"))
 			COMPLETE_WITH_LIST_CS3("never", "errors", "always");
-		else if (TailMatchesCS1("VERBOSITY"))
+		if (TailMatchesCS1("VERBOSITY"))
 			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
 	}
-	else if (TailMatchesCS1("\\sf*"))
+	if (TailMatchesCS1("\\sf*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (TailMatchesCS1("\\sv*"))
+	if (TailMatchesCS1("\\sv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
+	if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
 							"\\ir|\\include_relative|\\o|\\out|"
 							"\\s|\\w|\\write|\\lo_import"))
 	{
 		completion_charp = "\\";
-		matches = completion_matches(text, complete_from_files);
+		return completion_matches(text, complete_from_files);
 	}
 
 	/*
@@ -3143,7 +2863,7 @@ psql_completion(const char *text, int start, int end)
 			{
 				if (words_after_create[i].query)
 					COMPLETE_WITH_QUERY(words_after_create[i].query);
-				else if (words_after_create[i].squery)
+				if (words_after_create[i].squery)
 					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
 											   NULL);
 				break;
@@ -3151,25 +2871,8 @@ psql_completion(const char *text, int start, int end)
 		}
 	}
 
-	/*
-	 * If we still don't have anything to match we have to fabricate some sort
-	 * of default list. If we were to just return NULL, readline automatically
-	 * attempts filename completion, and that's usually no good.
-	 */
-	if (matches == NULL)
-	{
-		COMPLETE_WITH_CONST("");
-#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
-		rl_completion_append_character = '\0';
-#endif
-	}
-
-	/* free storage */
-	free(previous_words);
-	free(words_buffer);
-
-	/* Return our Grand List O' Matches */
-	return matches;
+	/* We found no match */
+	return NULL;
 }
 
 
-- 
2.9.2

0002-Make-keywords-case-follow-to-input.patchtext/x-patch; charset=us-asciiDownload
From d3256de4c7cae904e68c540514f29fdc813849c5 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Wed, 14 Sep 2016 12:48:16 +0900
Subject: [PATCH 2/5] Make keywords' case follow to input

Currently some keywords suggested along with database objects are
always in upper case. This patch changes the behavior so that the case
of the additional keywords follow the setting of COMP_KEYWORD_CASE.

COMPLETE_WITH_ATTR needs completion_charp to be appendable, so this
patch changes it to a PQExpBuffer and adjust COMPLET_WITH_* macros to
that change.

This leaves keywords contained in Query_for_list_of_grant_roles and
Query_for_enum, but it is another problem.
---
 src/bin/psql/tab-complete-macros.h |  40 +++-
 src/bin/psql/tab-complete.c        | 418 +++++++++++++++++++------------------
 2 files changed, 250 insertions(+), 208 deletions(-)

diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
index 8ee52b8..d6b455b 100644
--- a/src/bin/psql/tab-complete-macros.h
+++ b/src/bin/psql/tab-complete-macros.h
@@ -231,16 +231,26 @@
  * 5) The list of attributes of the given table (possibly schema-qualified).
  * 6) The list of arguments to the given function (possibly schema-qualified).
  */
-#define COMPLETE_WITH_QUERY(query) \
+#define APPEND_COMP_CHARP(charp) \
+	appendPQExpBufferStr(completion_charp, charp);
+
+#define SET_COMP_CHARP(charp) \
+	resetPQExpBuffer(completion_charp);	\
+	APPEND_COMP_CHARP(charp);
+
+#define COMPLETION_CHARP (completion_charp->data)
+
+#define COMPLETE_WITH_QUERY(query, addon)				\
 do { \
-	completion_charp = query;	\
+	SET_COMP_CHARP(query);	\
+	APPEND_COMP_CHARP(addon); \
 	return completion_matches(text, complete_from_query);	\
 } while (0)
 
 #define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
 do { \
 	completion_squery = &(query); \
-	completion_charp = addon; \
+	SET_COMP_CHARP(addon); \
 	return completion_matches(text, complete_from_schema_query); \
 } while (0)
 
@@ -260,7 +270,7 @@ do { \
 
 #define COMPLETE_WITH_CONST(string) \
 do { \
-	completion_charp = string;	\
+	SET_COMP_CHARP(string);	\
 	completion_case_sensitive = false; \
 	return completion_matches(text, complete_from_const);	\
 } while (0)
@@ -278,12 +288,14 @@ do { \
 								false, false, pset.encoding); \
 	if (_completion_table == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_attributes addon; \
+		SET_COMP_CHARP(Query_for_list_of_attributes); \
+		APPEND_COMP_CHARP(addon);					  \
 		completion_info_charp = relation; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_attributes_with_schema addon; \
+		SET_COMP_CHARP(Query_for_list_of_attributes_with_schema); \
+		APPEND_COMP_CHARP(addon); \
 		completion_info_charp = _completion_table; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -303,12 +315,12 @@ do { \
 								   false, false, pset.encoding); \
 	if (_completion_function == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_arguments; \
+		SET_COMP_CHARP(Query_for_list_of_arguments); \
 		completion_info_charp = function; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_arguments_with_schema;	\
+		SET_COMP_CHARP(Query_for_list_of_arguments_with_schema); \
 		completion_info_charp = _completion_function; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -400,6 +412,16 @@ do { \
 	COMPLETE_WITH_LIST_CS(list); \
 } while (0)
 
-
+#define ADDLIST1(s1) additional_kw_query(text, 1, s1)
+#define ADDLIST2(s1, s2) additional_kw_query(text, 2, s1, s2)
+#define ADDLIST3(s1, s2, s3) additional_kw_query(text, 3, s1, s2, s3)
+#define ADDLIST4(s1, s2, s3, s4) \
+	additional_kw_query(text, 4, s1, s2, s3, s4)
+#define ADDLIST13(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
+	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13)
+#define ADDLIST15(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
+	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13, s14, s15)
 
 #endif   /* TAB_COMPLETE_MACROS_H */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 679e58f..c0ab0a3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -126,7 +126,7 @@ static int	completion_max_records;
  * Communication variables set by COMPLETE_WITH_FOO macros and then used by
  * the completion callback functions.  Ugly but there is no better way.
  */
-static const char *completion_charp;	/* to pass a string */
+static PQExpBuffer completion_charp = NULL;		/* to pass a string */
 static const char *const * completion_charpp;	/* to pass a list of strings */
 static const char *completion_info_charp;		/* to pass a second string */
 static const char *completion_info_charp2;		/* to pass a third string */
@@ -776,6 +776,7 @@ static char **complete_from_variables(const char *text,
 static char *complete_from_files(const char *text, int state);
 
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
+static char *additional_kw_query( const char *ref, int n, ...);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
 
@@ -947,7 +948,8 @@ psql_completion(const char *text, int start, int end)
 #endif
 
 	/* Clear a few things. */
-	completion_charp = NULL;
+	if (completion_charp == NULL)
+		completion_charp = createPQExpBuffer();
 	completion_charpp = NULL;
 	completion_info_charp = NULL;
 	completion_info_charp2 = NULL;
@@ -1057,7 +1059,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* ALTER TABLE */
 	if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+								   ADDLIST1("ALL IN TABLESPACE"));
 
 	/* ALTER something */
 	if (Matches1("ALTER"))
@@ -1088,7 +1090,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("SET TABLESPACE", "OWNED BY");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY */
 	if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY xxx */
 	if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("SET TABLESPACE");
@@ -1124,7 +1126,7 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER EVENT TRIGGER */
 	if (Matches3("ALTER", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* ALTER EVENT TRIGGER <name> */
 	if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
@@ -1142,14 +1144,14 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches4("ALTER", "EXTENSION", MatchAny, "UPDATE"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions_with_TO);
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions_with_TO,"");
 	}
 
 	/* ALTER EXTENSION <name> UPDATE TO */
 	if (Matches5("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions, "");
 	}
 
 	/* ALTER FOREIGN */
@@ -1174,7 +1176,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* ALTER INDEX */
 	if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+								   ADDLIST1("ALL IN TABLESPACE"));
 	/* ALTER INDEX <name> */
 	if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
@@ -1203,7 +1205,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* ALTER MATERIALIZED VIEW */
 	if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+								   ADDLIST1("ALL IN TABLESPACE"));
 
 	/* ALTER USER,ROLE <name> */
 	if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
@@ -1259,7 +1261,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, "");
 	}
 	/* ALTER DOMAIN <sth> RENAME */
 	if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
@@ -1294,7 +1296,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
 	if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars, "");
 	/* ALTER VIEW <name> */
 	if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
@@ -1306,7 +1308,7 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER POLICY <name> */
 	if (Matches2("ALTER", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies, "");
 	/* ALTER POLICY <name> ON */
 	if (Matches3("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
@@ -1314,14 +1316,14 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, "");
 	}
 	/* ALTER POLICY <name> ON <table> - show options */
 	if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING (", "WITH CHECK (");
 	/* ALTER POLICY <name> ON <table> TO <role> */
 	if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 	/* ALTER POLICY <name> ON <table> USING ( */
 	if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
@@ -1337,7 +1339,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches4("ALTER", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, "");
 	}
 
 	/* ALTER RULE <name> ON <name> */
@@ -1351,14 +1353,14 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, "");
 	}
 
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
 	if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* ALTER TRIGGER <name> ON <name> */
 	if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
@@ -1385,22 +1387,22 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
 	if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
 	{
 		completion_info_charp = prev4_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
 	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 	if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
 	{
 		completion_info_charp = prev4_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 	/* ALTER TABLE xxx INHERIT */
 	if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
@@ -1414,21 +1416,21 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, "");
 	}
 	if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, "");
 	}
 
 	/* ALTER TABLE xxx ALTER */
 	if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
+		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST2("COLUMN", "CONSTRAINT"));
 
 	/* ALTER TABLE xxx RENAME */
 	if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
+		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST3("COLUMN", "CONSTRAINT", "TO"));
 	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
@@ -1454,7 +1456,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, "");
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
 	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
@@ -1481,7 +1483,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
 	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
 	if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
@@ -1493,7 +1495,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * tablespaces
 	 */
 	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
 	/* If we have ALTER TABLE <sth> SET WITH provide OIDS */
 	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
 		COMPLETE_WITH_CONST("OIDS");
@@ -1545,7 +1547,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
 	if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
@@ -1609,7 +1611,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
 	if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* BEGIN */
 	if (Matches1("BEGIN"))
@@ -1628,9 +1630,10 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
 	if (Matches1("CLUSTER"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
+								   ADDLIST1("VERBOSE"));
 	if (Matches2("CLUSTER", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 	/* If we have CLUSTER <sth>, then add "USING" */
 	if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
 		COMPLETE_WITH_CONST("USING");
@@ -1642,7 +1645,7 @@ psql_completion_internal(const char *text, char **previous_words,
 			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
 	}
 
 /* COMMENT */
@@ -1662,24 +1665,24 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
 	if (Matches4("COMMENT", "ON", "ACCESS", "METHOD"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, "");
 	if (Matches3("COMMENT", "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 	if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 	if (Matches3("COMMENT", "ON", "CONSTRAINT"))
-		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
+		COMPLETE_WITH_QUERY(Query_for_all_table_constraints, "");
 	if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint, "");
 	}
 	if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 	if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
 		Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
 			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
@@ -1693,10 +1696,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	 */
 	if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION ALL SELECT '('");
+								   ADDLIST1("("));
 	/* If we have COPY BINARY, complete with list of tables */
 	if (Matches2("COPY", "BINARY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* If we have COPY (, complete it with legal commands */
 	if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
@@ -1708,7 +1711,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
 			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
 	{
-		completion_charp = "";
+		SET_COMP_CHARP("");
 		return completion_matches(text, complete_from_files);
 	}
 
@@ -1743,12 +1746,12 @@ psql_completion_internal(const char *text, char **previous_words,
 							"LC_COLLATE", "LC_CTYPE");
 
 	if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
+		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases, "");
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	if (Matches2("CREATE", "EXTENSION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions, "");
 	/* CREATE EXTENSION <name> */
 	if (Matches3("CREATE", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
@@ -1756,7 +1759,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions, "");
 	}
 
 	/* CREATE FOREIGN */
@@ -1778,20 +1781,18 @@ psql_completion_internal(const char *text, char **previous_words,
 	 */
 	if (TailMatches2("CREATE|UNIQUE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'ON'"
-								   " UNION SELECT 'CONCURRENTLY'");
+								   ADDLIST2("ON", "CONCURRENTLY"));
 	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
 	if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
 			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 
 	/*
 	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
 	 * indexes
 	 */
 	if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'ON'");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, ADDLIST1("ON"));
 	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
 	if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
 			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
@@ -1814,7 +1815,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, "");
 	if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
 			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
 			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
@@ -1826,7 +1827,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
 	if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
 	if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING (", "WITH CHECK (");
@@ -1844,7 +1845,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST3("TO", "USING (", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
 	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
 	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
@@ -1864,7 +1865,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
 	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 /* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
@@ -1920,10 +1921,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * tables
 	 */
 	if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
 	if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
 	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
@@ -2009,7 +2010,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
 	if (TailMatches2("DELETE", "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete DELETE FROM <table> */
 	if (TailMatches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
@@ -2048,9 +2049,9 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* DROP INDEX */
 	if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'CONCURRENTLY'");
+								   ADDLIST1("CONCURRENTLY"));
 	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 	if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
@@ -2060,13 +2061,13 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 
 	/* DROP OWNED BY */
 	if (Matches2("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
 	if (Matches3("DROP", "OWNED", "BY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 	if (Matches3("DROP", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
@@ -2077,7 +2078,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, "");
 	}
 	if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
@@ -2086,17 +2087,17 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("DROP", "ACCESS"))
 		COMPLETE_WITH_CONST("METHOD");
 	if (Matches3("DROP", "ACCESS", "METHOD"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, "");
 
 	/* DROP EVENT TRIGGER */
 	if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	if (Matches3("DROP", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* DROP POLICY <name>  */
 	if (Matches2("DROP", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies, "");
 	/* DROP POLICY <name> ON */
 	if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
@@ -2104,7 +2105,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, "");
 	}
 
 	/* DROP RULE */
@@ -2113,14 +2114,14 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, "");
 	}
 	if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 /* EXECUTE */
 	if (Matches1("EXECUTE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
+		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements, "");
 
 /* EXPLAIN */
 
@@ -2157,7 +2158,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
 	if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
 			 !TailMatches4("CREATE", MatchAny, MatchAny, MatchAny))
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, "");
 	/* applies in CREATE SERVER */
 	if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
 			 HeadMatches2("CREATE", "SERVER"))
@@ -2166,29 +2167,19 @@ psql_completion_internal(const char *text, char **previous_words,
 /* FOREIGN TABLE */
 	if (TailMatches2("FOREIGN", "TABLE") &&
 			 !TailMatches3("CREATE", MatchAny, MatchAny))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 
 /* FOREIGN SERVER */
 	if (TailMatches2("FOREIGN", "SERVER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_servers, "");
 
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
 	if (TailMatches1("GRANT|REVOKE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'SELECT'"
-							" UNION SELECT 'INSERT'"
-							" UNION SELECT 'UPDATE'"
-							" UNION SELECT 'DELETE'"
-							" UNION SELECT 'TRUNCATE'"
-							" UNION SELECT 'REFERENCES'"
-							" UNION SELECT 'TRIGGER'"
-							" UNION SELECT 'CREATE'"
-							" UNION SELECT 'CONNECT'"
-							" UNION SELECT 'TEMPORARY'"
-							" UNION SELECT 'EXECUTE'"
-							" UNION SELECT 'USAGE'"
-							" UNION SELECT 'ALL'");
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+			ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
+					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
+					  "EXECUTE", "USAGE", "ALL"));
 
 	/*
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
@@ -2217,21 +2208,21 @@ psql_completion_internal(const char *text, char **previous_words,
 	 */
 	if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
-								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
-								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
-								   " UNION SELECT 'ALL TABLES IN SCHEMA'"
-								   " UNION SELECT 'DATABASE'"
-								   " UNION SELECT 'DOMAIN'"
-								   " UNION SELECT 'FOREIGN DATA WRAPPER'"
-								   " UNION SELECT 'FOREIGN SERVER'"
-								   " UNION SELECT 'FUNCTION'"
-								   " UNION SELECT 'LANGUAGE'"
-								   " UNION SELECT 'LARGE OBJECT'"
-								   " UNION SELECT 'SCHEMA'"
-								   " UNION SELECT 'SEQUENCE'"
-								   " UNION SELECT 'TABLE'"
-								   " UNION SELECT 'TABLESPACE'"
-								   " UNION SELECT 'TYPE'");
+			   ADDLIST15("ALL FUNCTIONS IN SCHEMA",
+						 "ALL SEQUENCES IN SCHEMA",
+						 "ALL TABLES IN SCHEMA",
+						 "DATABASE",
+						 "DOMAIN",
+						 "FOREIGN DATA WRAPPER",
+						 "FOREIGN SERVER",
+						 "FUNCTION",
+						 "LANGUAGE",
+						 "LARGE OBJECT",
+						 "SCHEMA",
+						 "SEQUENCE",
+						 "TABLE",
+						 "TABLESPACE",
+						 "TYPE"));
 
 	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
@@ -2249,23 +2240,23 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
 		if (TailMatches1("DATABASE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+			COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 		if (TailMatches1("DOMAIN"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, "");
 		if (TailMatches1("FUNCTION"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 		if (TailMatches1("LANGUAGE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
+			COMPLETE_WITH_QUERY(Query_for_list_of_languages, "");
 		if (TailMatches1("SCHEMA"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+			COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
 		if (TailMatches1("SEQUENCE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
 		if (TailMatches1("TABLE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 		if (TailMatches1("TABLESPACE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
 		if (TailMatches1("TYPE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, "");
 		if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
@@ -2278,7 +2269,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 */
 	if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
 			 (HeadMatches1("REVOKE") && TailMatches1("FROM")))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, "");
 
 	/* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */
 	if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
@@ -2329,7 +2320,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
 	if (TailMatches2("INSERT", "INTO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete "INSERT INTO <table> (" with attribute names */
 	if (TailMatches4("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
@@ -2357,7 +2348,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* Complete LOCK [TABLE] with a list of tables */
 	if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION SELECT 'TABLE'");
+								   ADDLIST1("TABLE"));
 	if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
@@ -2379,7 +2370,7 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
 	if (TailMatches1("NOTIFY"))
-		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
+		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'", "");
 
 /* OPTIONS */
 	if (TailMatches1("OPTIONS"))
@@ -2387,7 +2378,7 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* OWNER TO  - complete with available roles */
 	if (TailMatches2("OWNER", "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* ORDER BY */
 	if (TailMatches3("FROM", MatchAny, "ORDER"))
@@ -2410,11 +2401,11 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
 	if (Matches3("REASSIGN", "OWNED", "BY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 	if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 
 /* REFRESH MATERIALIZED VIEW */
 	if (Matches1("REFRESH"))
@@ -2423,9 +2414,9 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("VIEW");
 	if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'CONCURRENTLY'");
+								   ADDLIST1("CONCURRENTLY"));
 	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
 	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
@@ -2443,13 +2434,13 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
 	if (Matches2("REINDEX", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 	if (Matches2("REINDEX", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 	if (Matches2("REINDEX", "SCHEMA"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
 	if (Matches2("REINDEX", "SYSTEM|DATABASE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+		COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 
 /* SECURITY LABEL */
 	if (Matches1("SECURITY"))
@@ -2478,9 +2469,9 @@ psql_completion_internal(const char *text, char **previous_words,
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
 	if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars, "");
 	if (Matches1("SHOW"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars, "");
 	/* Complete "SET TRANSACTION" */
 	if (Matches2("SET", "TRANSACTION"))
 		COMPLETE_WITH_LIST5("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
@@ -2515,19 +2506,21 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
 	if (Matches2("SET", "CONSTRAINTS"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema,
+								   ADDLIST1("ALL"));
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
 	if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
 	if (Matches2("SET", "ROLE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
 	if (Matches2("SET", "SESSION"))
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
 	if (Matches3("SET", "SESSION", "AUTHORIZATION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
+							ADDLIST1("DEFAULT"));
 	/* Complete RESET SESSION with AUTHORIZATION */
 	if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
@@ -2555,8 +2548,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		if (TailMatches2("search_path", "TO|="))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
 								" AND nspname not like 'pg\\_toast%%' "
-								" AND nspname not like 'pg\\_temp%%' "
-								" UNION SELECT 'DEFAULT' ");
+								" AND nspname not like 'pg\\_temp%%' ",
+								ADDLIST1("DEFAULT"));
 		else
 		{
 			/* generic, type based, GUC support, guctype is malloc'ed */
@@ -2568,7 +2561,7 @@ psql_completion_internal(const char *text, char **previous_words,
 
 				free(guctype);
 				snprintf(querybuf, sizeof(querybuf), Query_for_enum, prev2_wd);
-				COMPLETE_WITH_QUERY(querybuf);
+				COMPLETE_WITH_QUERY(querybuf, "");
 			}
 
 			if (guctype && strcmp(guctype, "bool") == 0)
@@ -2591,26 +2584,26 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* TABLE, but not TABLE embedded in other commands */
 	if (Matches1("TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, "");
 
 /* TABLESAMPLE */
 	if (TailMatches1("TABLESAMPLE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods, "");
 	if (TailMatches2("TABLESAMPLE", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
 	if (Matches1("TRUNCATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 /* UNLISTEN */
 	if (Matches1("UNLISTEN"))
-		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
+		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'", "");
 
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
 	if (TailMatches1("UPDATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete UPDATE <table> with "SET" */
 	if (TailMatches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
@@ -2625,12 +2618,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
 	if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'CURRENT_USER'"
-							" UNION SELECT 'PUBLIC'"
-							" UNION SELECT 'USER'");
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, 
+			ADDLIST3("CURRENT_USER", "PUBLIC", "USER"));
 	if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, "");
 	if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("SERVER");
 	if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
@@ -2642,28 +2633,25 @@ psql_completion_internal(const char *text, char **previous_words,
  */
 	if (Matches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'FULL'"
-								   " UNION SELECT 'FREEZE'"
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+								   ADDLIST4("FULL", "FREEZE",
+											"ANALYZE", "VERBOSE"));
 	if (Matches2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+								   ADDLIST2("ANALYZE", "VERBOSE"));
 	if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+								   ADDLIST1("VERBOSE"));
 	if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+								   ADDLIST1("ANALYZE"));
 	if (Matches2("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+								   ADDLIST1("ANALYZE"));
 	if (Matches2("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+								   ADDLIST1("VERBOSE"));
 	if (HeadMatches1("VACUUM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 
 /* WITH [RECURSIVE] */
 
@@ -2677,7 +2665,7 @@ psql_completion_internal(const char *text, char **previous_words,
 /* ANALYZE */
 	/* Complete with list of tables */
 	if (Matches1("ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, "");
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
@@ -2687,11 +2675,11 @@ psql_completion_internal(const char *text, char **previous_words,
 /* ... FROM ... */
 /* TODO: also include SRF ? */
 	if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 
 /* ... JOIN ... */
 	if (TailMatches1("JOIN"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
@@ -2700,84 +2688,84 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatchesCS1("\\connect|\\c"))
 	{
 		if (!recognized_connection_string(text))
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+			COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 	}
 	if (TailMatchesCS2("\\connect|\\c", MatchAny))
 	{
 		if (!recognized_connection_string(prev_wd))
-			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+			COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	}
 	if (TailMatchesCS1("\\da*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, "");
 	if (TailMatchesCS1("\\dA*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, "");
 	if (TailMatchesCS1("\\db*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
 	if (TailMatchesCS1("\\dD*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, "");
 	if (TailMatchesCS1("\\des*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_servers, "");
 	if (TailMatchesCS1("\\deu*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, "");
 	if (TailMatchesCS1("\\dew*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, "");
 	if (TailMatchesCS1("\\df*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 
 	if (TailMatchesCS1("\\dFd*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries, "");
 	if (TailMatchesCS1("\\dFp*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers, "");
 	if (TailMatchesCS1("\\dFt*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates, "");
 	/* must be at end of \dF alternatives: */
 	if (TailMatchesCS1("\\dF*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations, "");
 
 	if (TailMatchesCS1("\\di*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 	if (TailMatchesCS1("\\dL*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
+		COMPLETE_WITH_QUERY(Query_for_list_of_languages, "");
 	if (TailMatchesCS1("\\dn*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
 	if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
 	if (TailMatchesCS1("\\ds*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
 	if (TailMatchesCS1("\\dt*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	if (TailMatchesCS1("\\dT*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, "");
 	if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	if (TailMatchesCS1("\\dv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	if (TailMatchesCS1("\\dx*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_extensions, "");
 	if (TailMatchesCS1("\\dm*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	if (TailMatchesCS1("\\dE*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, "");
 	if (TailMatchesCS1("\\dy*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
 
 	/* must be at end of \d alternatives: */
 	if (TailMatchesCS1("\\d*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, "");
 
 	if (TailMatchesCS1("\\ef"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 	if (TailMatchesCS1("\\ev"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 
 	if (TailMatchesCS1("\\encoding"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_encodings, "");
 	if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
 		COMPLETE_WITH_LIST(sql_commands);
 	if (TailMatchesCS1("\\l*") && !TailMatchesCS1("\\lo*"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+		COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
 	if (TailMatchesCS1("\\password"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
 	if (TailMatchesCS1("\\pset"))
 	{
 		static const char *const my_list[] =
@@ -2837,14 +2825,14 @@ psql_completion_internal(const char *text, char **previous_words,
 			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
 	}
 	if (TailMatchesCS1("\\sf*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
 	if (TailMatchesCS1("\\sv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
 							"\\ir|\\include_relative|\\o|\\out|"
 							"\\s|\\w|\\write|\\lo_import"))
 	{
-		completion_charp = "\\";
+		SET_COMP_CHARP("\\");
 		return completion_matches(text, complete_from_files);
 	}
 
@@ -2862,10 +2850,10 @@ psql_completion_internal(const char *text, char **previous_words,
 			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
 			{
 				if (words_after_create[i].query)
-					COMPLETE_WITH_QUERY(words_after_create[i].query);
+					COMPLETE_WITH_QUERY(words_after_create[i].query, "");
 				if (words_after_create[i].squery)
 					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
-											   NULL);
+											   "");
 				break;
 			}
 		}
@@ -3112,13 +3100,13 @@ _complete_from_query(int is_schema_query, const char *text, int state)
 							  char_length, e_text);
 
 			/* If an addon query was provided, use it */
-			if (completion_charp)
-				appendPQExpBuffer(&query_buffer, "\n%s", completion_charp);
+			if (COMPLETION_CHARP[0])
+				appendPQExpBuffer(&query_buffer, "\n%s", COMPLETION_CHARP);
 		}
 		else
 		{
 			/* completion_charp is an sprintf-style format string */
-			appendPQExpBuffer(&query_buffer, completion_charp,
+			appendPQExpBuffer(&query_buffer, COMPLETION_CHARP,
 							  char_length, e_text,
 							  e_info_charp, e_info_charp,
 							  e_info_charp2, e_info_charp2);
@@ -3233,18 +3221,17 @@ complete_from_list(const char *text, int state)
 static char *
 complete_from_const(const char *text, int state)
 {
-	Assert(completion_charp != NULL);
 	if (state == 0)
 	{
 		if (completion_case_sensitive)
-			return pg_strdup(completion_charp);
+			return pg_strdup(COMPLETION_CHARP);
 		else
 
 			/*
 			 * If case insensitive matching was requested initially, adjust
 			 * the case according to setting.
 			 */
-			return pg_strdup_keyword_case(completion_charp, text);
+			return pg_strdup_keyword_case(COMPLETION_CHARP, text);
 	}
 	else
 		return NULL;
@@ -3345,7 +3332,7 @@ complete_from_files(const char *text, int state)
 	if (state == 0)
 	{
 		/* Initialization: stash the unquoted input. */
-		unquoted_text = strtokx(text, "", NULL, "'", *completion_charp,
+		unquoted_text = strtokx(text, "", NULL, "'", COMPLETION_CHARP[0],
 								false, true, pset.encoding);
 		/* expect a NULL return for the empty string only */
 		if (!unquoted_text)
@@ -3366,7 +3353,7 @@ complete_from_files(const char *text, int state)
 		 * bother providing a macro to simplify this.
 		 */
 		ret = quote_if_needed(unquoted_match, " \t\r\n\"`",
-							  '\'', *completion_charp, pset.encoding);
+							  '\'', COMPLETION_CHARP[0], pset.encoding);
 		if (ret)
 			free(unquoted_match);
 		else
@@ -3410,6 +3397,39 @@ pg_strdup_keyword_case(const char *s, const char *ref)
 	return ret;
 }
 
+/* Construct codelet to append given keywords  */
+static char *
+additional_kw_query(const char *ref, int n, ...)
+{
+	va_list ap;
+	static PQExpBuffer qbuf = NULL;
+	int i;
+
+	if (qbuf == NULL)
+		qbuf = createPQExpBuffer();
+	else
+		resetPQExpBuffer(qbuf);
+
+	/* Construct an additional queriy to append keywords */
+	appendPQExpBufferStr(qbuf, " UNION SELECT * FROM (VALUES ");
+
+	va_start(ap, n);
+	for (i = 0 ; i < n ; i++)
+	{
+		char *item = pg_strdup_keyword_case(va_arg(ap, char *), ref);
+		if (i > 0) appendPQExpBufferChar(qbuf, ',');
+		appendPQExpBufferStr(qbuf, "('");
+		appendPQExpBufferStr(qbuf, item);
+		appendPQExpBufferStr(qbuf, "')");
+		pg_free(item);
+	}
+	va_end(ap);
+
+	appendPQExpBufferStr(qbuf, ") as x");
+
+	return qbuf->data;
+}
+
 
 /*
  * escape_string - Escape argument for use as string literal.
-- 
2.9.2

0003-Fix-suggested-keywords-to-follow-input-in-tab-comple.patchtext/x-patch; charset=us-asciiDownload
From 5a841113a60e70ca0ca329ce98cae7afa2a39bcb Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 16 Sep 2016 11:36:13 +0900
Subject: [PATCH 3/5] Fix suggested keywords to follow input in tab-completion
 session 2

The prevous fix left some completions unfixed. This commit moves
keywords of Query_for_list_of_set_vars/alter_system_set_vars/show_vars
out of the constant and process the keywords as the same as other
syntaxes.

Role name is a bother. PUBLIC, CURRENT_USER, SESSION_USER are
certainly roles and the last two are used in many places. In this
patch I renamed Query_for_list_of_roles to
Query_for_list_of_true_roles and added Query_for_list_of_roles that
having CURRENT_USER and SESSION_USER. But this might be too much.
---
 src/bin/psql/tab-complete-macros.h |   2 +
 src/bin/psql/tab-complete.c        | 102 ++++++++++++++++++++++---------------
 2 files changed, 63 insertions(+), 41 deletions(-)

diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
index d6b455b..33e2d58 100644
--- a/src/bin/psql/tab-complete-macros.h
+++ b/src/bin/psql/tab-complete-macros.h
@@ -417,6 +417,8 @@ do { \
 #define ADDLIST3(s1, s2, s3) additional_kw_query(text, 3, s1, s2, s3)
 #define ADDLIST4(s1, s2, s3, s4) \
 	additional_kw_query(text, 4, s1, s2, s3, s4)
+#define ADDLIST6(s1, s2, s3, s4, s5, s6)				\
+	additional_kw_query(text, 6, s1, s2, s3, s4, s5, s6)
 #define ADDLIST13(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
 	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
 						s8, s9, s10, s11, s12, s13)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index c0ab0a3..7c855db 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -449,43 +449,38 @@ static const SchemaQuery Query_for_list_of_matviews = {
 " WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s'"
 
 #define Query_for_list_of_alter_system_set_vars \
-"SELECT name FROM "\
-" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
-"  WHERE context != 'internal') ss "\
-" WHERE substring(name,1,%d)='%s'"\
-" UNION ALL SELECT 'all' ss"
-
-#define Query_for_list_of_set_vars \
-"SELECT name FROM "\
-" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
-"  WHERE context IN ('user', 'superuser') "\
-"  UNION ALL SELECT 'constraints' "\
-"  UNION ALL SELECT 'transaction' "\
-"  UNION ALL SELECT 'session' "\
-"  UNION ALL SELECT 'role' "\
-"  UNION ALL SELECT 'tablespace' "\
-"  UNION ALL SELECT 'all') ss "\
+"SELECT name FROM "													\
+" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings " \
+"  WHERE context != 'internal') ss "								\
 " WHERE substring(name,1,%d)='%s'"
 
+#define Query_for_list_of_set_vars									 \
+"SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings " \
+"  WHERE context IN ('user', 'superuser') "							\
+"   AND substring(name,1,%d)='%s'"
+
 #define Query_for_list_of_show_vars \
-"SELECT name FROM "\
-" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
-"  UNION ALL SELECT 'session authorization' "\
-"  UNION ALL SELECT 'all') ss "\
+"SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings " \
 " WHERE substring(name,1,%d)='%s'"
 
-#define Query_for_list_of_roles \
+#define Query_for_list_of_true_roles \
 " SELECT pg_catalog.quote_ident(rolname) "\
 "   FROM pg_catalog.pg_roles "\
 "  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"
 
+#define Query_for_list_of_roles \
+	concatenate_strings(												\
+		" SELECT pg_catalog.quote_ident(rolname) "						\
+		 "   FROM pg_catalog.pg_roles "									\
+		 "  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'",	\
+		ADDLIST2("CURRENT_USER", "SESSION_USER"))
+
 #define Query_for_list_of_grant_roles \
-" SELECT pg_catalog.quote_ident(rolname) "\
-"   FROM pg_catalog.pg_roles "\
-"  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"\
-" UNION ALL SELECT 'PUBLIC'"\
-" UNION ALL SELECT 'CURRENT_USER'"\
-" UNION ALL SELECT 'SESSION_USER'"
+	concatenate_strings(												\
+		" SELECT pg_catalog.quote_ident(rolname) "						\
+		 "   FROM pg_catalog.pg_roles "									\
+		 "  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'",	\
+		ADDLIST3("PUBLIC", "CURRENT_USER", "SESSION_USER"))
 
 /* the silly-looking length condition is just to eat up the current word */
 #define Query_for_table_owning_index \
@@ -681,13 +676,13 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "         WHERE pg_catalog.quote_ident(polname)='%s')"
 
 #define Query_for_enum \
-" SELECT name FROM ( "\
-"   SELECT pg_catalog.quote_ident(pg_catalog.unnest(enumvals)) AS name "\
-"     FROM pg_catalog.pg_settings "\
-"    WHERE pg_catalog.lower(name)=pg_catalog.lower('%s') "\
-"    UNION ALL " \
-"   SELECT 'DEFAULT' ) ss "\
-"  WHERE pg_catalog.substring(name,1,%%d)='%%s'"
+	concatenate_strings( \
+		" SELECT name FROM ( "											\
+	"   SELECT pg_catalog.quote_ident(pg_catalog.unnest(enumvals)) AS name " \
+	"     FROM pg_catalog.pg_settings "									\
+	"    WHERE pg_catalog.lower(name)=pg_catalog.lower('%s')) ss "		\
+	"  WHERE pg_catalog.substring(name,1,%%d)='%%s'", \
+		ADDLIST1("DEFAULT"))
 
 /*
  * This is a list of all "things" in Pgsql, which can show up after CREATE or
@@ -727,7 +722,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN DATA WRAPPER", NULL, NULL},
 	{"FOREIGN TABLE", NULL, NULL},
 	{"FUNCTION", NULL, &Query_for_list_of_functions},
-	{"GROUP", Query_for_list_of_roles},
+	{"GROUP", Query_for_list_of_true_roles},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"INDEX", NULL, &Query_for_list_of_indexes},
 	{"MATERIALIZED VIEW", NULL, &Query_for_list_of_matviews},
@@ -736,7 +731,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"OWNED", NULL, NULL, THING_NO_CREATE},		/* for DROP OWNED BY ... */
 	{"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
 	{"POLICY", NULL, NULL},
-	{"ROLE", Query_for_list_of_roles},
+	{"ROLE", Query_for_list_of_true_roles},
 	{"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"},
 	{"SCHEMA", Query_for_list_of_schemas},
 	{"SEQUENCE", NULL, &Query_for_list_of_sequences},
@@ -751,7 +746,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"UNIQUE", NULL, NULL, THING_NO_DROP},		/* for CREATE UNIQUE INDEX ... */
 	{"UNLOGGED", NULL, NULL, THING_NO_DROP},	/* for CREATE UNLOGGED TABLE
 												 * ... */
-	{"USER", Query_for_list_of_roles},
+	{"USER", Query_for_list_of_true_roles},
 	{"USER MAPPING FOR", NULL, NULL},
 	{"VIEW", NULL, &Query_for_list_of_views},
 	{NULL}						/* end of list */
@@ -776,6 +771,7 @@ static char **complete_from_variables(const char *text,
 static char *complete_from_files(const char *text, int state);
 
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
+static char *concatenate_strings(const char *s1, const char *s2);
 static char *additional_kw_query( const char *ref, int n, ...);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
@@ -1296,7 +1292,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
 	if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars, "");
+		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars,
+							ADDLIST1("ALL"));
 	/* ALTER VIEW <name> */
 	if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
@@ -2469,9 +2466,12 @@ psql_completion_internal(const char *text, char **previous_words,
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
 	if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars, "");
+		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars,
+							ADDLIST6("CONSTRAINTS", "TRANSACTION", "SESSION",
+									 "ROLE", "TABLESPACE", "ALL"));
 	if (Matches1("SHOW"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars, "");
+		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars, 
+							ADDLIST2("SESSION AUTHORIZATION", "ALL"));
 	/* Complete "SET TRANSACTION" */
 	if (Matches2("SET", "TRANSACTION"))
 		COMPLETE_WITH_LIST5("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
@@ -2693,7 +2693,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatchesCS2("\\connect|\\c", MatchAny))
 	{
 		if (!recognized_connection_string(prev_wd))
-			COMPLETE_WITH_QUERY(Query_for_list_of_roles, "");
+			COMPLETE_WITH_QUERY(Query_for_list_of_true_roles, "");
 	}
 	if (TailMatchesCS1("\\da*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, "");
@@ -3397,6 +3397,26 @@ pg_strdup_keyword_case(const char *s, const char *ref)
 	return ret;
 }
 
+/*
+ * Returns concatenated string.
+ * Note: the internal buffer is static so this cannot be called recursively.
+ */
+static char *
+concatenate_strings(const char *s1, const char *s2)
+{
+	static PQExpBuffer qbuf = NULL;
+	
+	if (qbuf == NULL)
+		qbuf = createPQExpBuffer();
+	else
+		resetPQExpBuffer(qbuf);
+
+	appendPQExpBufferStr(qbuf, s1);
+	appendPQExpBufferStr(qbuf, s2);
+
+	return qbuf->data;
+}
+
 /* Construct codelet to append given keywords  */
 static char *
 additional_kw_query(const char *ref, int n, ...)
-- 
2.9.2

0004-Introduce-word-shift-and-removal-feature-to-psql-com.patchtext/x-patch; charset=us-asciiDownload
From faf39d6f0a8e0a25321523f89b092489dd821b65 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Thu, 15 Sep 2016 14:44:55 +0900
Subject: [PATCH 4/5] Introduce word shift and removal feature to
 psql-completion

Currently completion of psql is vulnerable for noise words such like
temp/temporary, unlogged or concurrent. Addition to that, schema
elemsnts in CREATE SCHEMA syntax or some recursive syntaxes are
processed in somewhat bogus way.  To accommodate completion mechanism
for the cases, this patch introduces mainly two features.

1. Add a feature to ignore leading words to process.  New macros
  HEAD_SHIFT, HEAD_SET to shift or set the position of the first word
  to match using other macros. All *MatchesN macros follow
  this. SHIFT_TO_LAST1 is a macro to shift the head to the position
  where the specified word is found last.

2. Add a feature to remove intermediate words from previous_words
  list.  COLLAPSE(s, n) macro removes n words from the s'th position
  (1-based). Removing "noise" words let the succeeding operations
  simple.

Using this features, this patch implements the following things.

1. Properly treat schema elements by shifting head.

 Now we can treat schema elements as the same as normal syntax by
 isolating from leading words. This allow more complex completion for
 each create xxxs.

2. Simplify some "||"-connected matches required to cover noise words
  by removing noise wors.

 CREATE INDEX or some other syntaxes have rather many optional
 elements so appropriate to demonstrate how COLLAPSE can be used.
 ALTER TABLE/COMMENT/COPY/GRANT/REVOKE/BEGINs.

3. Process recursive of CREATE RULE/EXPLAIN/PREPARE using
  Matches/HeadMatches, not with TailMtaches.

 The recursive syntaxes are previously completed using TailMatches
 instead of Match/HeadMatch but that replacement increases the
 restriction in completing the inner commands.  psql_complete_internal
 can be called recursively and it can be used to resolve this.

The changes in this patch also allows us to encapsulate completion
code for common subsyntaxes in functions but this doesn't that.
---
 src/bin/psql/tab-complete-macros.h | 120 +++++++--
 src/bin/psql/tab-complete.c        | 493 +++++++++++++++++++++----------------
 2 files changed, 371 insertions(+), 242 deletions(-)

diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
index 33e2d58..0b63986 100644
--- a/src/bin/psql/tab-complete-macros.h
+++ b/src/bin/psql/tab-complete-macros.h
@@ -25,41 +25,69 @@
 #define prev8_wd  (previous_words[7])
 #define prev9_wd  (previous_words[8])
 
+/* Return the number of stored words counting head shift */
+#define WORD_COUNT() (previous_words_count - head_shift)
+
 /*
  * Return the index in previous_words for index from the beginning. n is
  * 1-based and the result is 0-based.
  */
-#define HEAD_INDEX(n) \
-	(previous_words_count - (n))
+#define HEAD_INDEX(n) (WORD_COUNT() - (n))
+
+/* Move the position of the beginning word for matching macros.  */
+#define HEAD_SHIFT(n) (head_shift += (n))
+
+/* Set the position of the beginning word for matching macros.  */
+#define HEAD_SET(n) (head_shift = (n))
+
+/*
+ * remove n words from current shifted position. This moves entire the
+ * previous_words regardless of head_shift.
+ */
+#define COLLAPSE(s, n)							\
+	do { \
+		memmove(previous_words + HEAD_INDEX((s) + (n) - 1), \
+				previous_words + HEAD_INDEX((s) - 1), \
+				sizeof(char *) * \
+				(previous_words_count - HEAD_INDEX((s) - 1)));	\
+		previous_words_count -= (n); \
+	} while (0)
+
+/*
+ * Find the position where the specified word appears last and shift to there.
+ * The words before the position will be ignored ever after.
+ */
+#define SHIFT_TO_LAST1(p1) \
+	HEAD_SHIFT(find_last_index_of(p1, previous_words, previous_words_count))
 
 /*
  * Macros for matching the last N words before point, and after head_sift,
  * case-insensitively.
  */
 #define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches(p1, prev_wd))
 
 #define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd))
 
 #define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
+	(WORD_COUNT() >= 3 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd))
 
 #define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
+	(WORD_COUNT() >= 4 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
 	 word_matches(p4, prev4_wd))
 
 #define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
+	(WORD_COUNT() >= 5 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -67,7 +95,7 @@
 	 word_matches(p5, prev5_wd))
 
 #define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
+	(WORD_COUNT() >= 6 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -76,7 +104,7 @@
 	 word_matches(p6, prev6_wd))
 
 #define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
+	(WORD_COUNT() >= 7 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -86,7 +114,7 @@
 	 word_matches(p7, prev7_wd))
 
 #define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
+	(WORD_COUNT() >= 8 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -97,7 +125,7 @@
 	 word_matches(p8, prev8_wd))
 
 #define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
+	(WORD_COUNT() >= 9 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -113,10 +141,10 @@
 	 * head_shift, case-sensitively.
 	 */
 #define TailMatchesCS1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches_cs(p1, prev_wd))
 #define TailMatchesCS2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches_cs(p1, prev_wd) && \
 	 word_matches_cs(p2, prev2_wd))
 
@@ -125,31 +153,31 @@
 	 * case-insensitively.
 	 */
 #define Matches1(p1) \
-	(previous_words_count == 1 && \
+	(WORD_COUNT() == 1 && \
 	 TailMatches1(p1))
 #define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
+	(WORD_COUNT() == 2 && \
 	 TailMatches2(p1, p2))
 #define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
+	(WORD_COUNT() == 3 && \
 	 TailMatches3(p1, p2, p3))
 #define Matches4(p1, p2, p3, p4) \
-	(previous_words_count == 4 && \
+	(WORD_COUNT() == 4 && \
 	 TailMatches4(p1, p2, p3, p4))
 #define Matches5(p1, p2, p3, p4, p5) \
-	(previous_words_count == 5 && \
+	(WORD_COUNT() == 5 && \
 	 TailMatches5(p1, p2, p3, p4, p5))
 #define Matches6(p1, p2, p3, p4, p5, p6) \
-	(previous_words_count == 6 && \
+	(WORD_COUNT() == 6 && \
 	 TailMatches6(p1, p2, p3, p4, p5, p6))
 #define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(previous_words_count == 7 && \
+	(WORD_COUNT() == 7 && \
 	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
 #define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(previous_words_count == 8 && \
+	(WORD_COUNT() == 8 && \
 	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
 #define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(previous_words_count == 9 && \
+	(WORD_COUNT() == 9 && \
 	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
 
 /*
@@ -195,16 +223,39 @@
 	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
 	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]))
 
-#define MidMatches7(s,p1, p2, p3, p4, p5, p6, p7)	\
+#define MidMatches7(s,p1, p2, p3, p4, p5, p6, p7)			\
 	(HEAD_INDEX((s) + 6) >= 0 &&							\
-	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
-	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
-	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&			\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&		\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&			\
 	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
 	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
 	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&			\
 	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]))
 
+#define MidMatches8(s,p1, p2, p3, p4, p5, p6, p7, p8)		\
+	(HEAD_INDEX((s) + 7) >= 0 &&							\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&				\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&		\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&		\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&		\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&		\
+	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]) &&		\
+	 word_matches(p8, previous_words[HEAD_INDEX((s) + 7)]))
+
+#define MidMatches9(s,p1, p2, p3, p4, p5, p6, p7, p8, p9)		\
+	(HEAD_INDEX((s) + 8) >= 0 &&							\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&				\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&		\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&		\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&		\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&		\
+	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]) &&		\
+	 word_matches(p8, previous_words[HEAD_INDEX((s) + 7)]) &&		\
+	 word_matches(p9, previous_words[HEAD_INDEX((s) + 8)]))
+
 #define HeadMatches1(p1) \
 	MidMatches1(1, p1)
 #define HeadMatches2(p1, p2) \
@@ -219,6 +270,10 @@
 	MidMatches6(1, p1, p2, p3, p4, p5, p6)
 #define HeadMatches7(p1, p2, p3, p4, p5, p6, p7) \
 	MidMatches7(1, p1, p2, p3, p4, p5, p6, p7)
+#define HeadMatches8(p1, p2, p3, p4, p5, p6, p7, p8)	\
+	MidMatches8(1, p1, p2, p3, p4, p5, p6, p7, p8)
+#define HeadMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9)	\
+	MidMatches9(1, p1, p2, p3, p4, p5, p6, p7, p8, p9)
 
 /*
  * A few macros to ease typing. You can use these to complete the given
@@ -426,4 +481,17 @@ do { \
 	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
 						s8, s9, s10, s11, s12, s13, s14, s15)
 
+#define COMPLETE_THING(p) \
+do { \
+	const pgsql_thing_t *ent = find_thing_entry(previous_words[-(p) - 1]);	\
+	if (ent) \
+	{ \
+		if (ent->query) \
+			COMPLETE_WITH_QUERY(ent->query, ""); \
+		else if (ent->squery) \
+			COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, ""); \
+	} \
+	return NULL; \
+} while (0)
+
 #endif   /* TAB_COMPLETE_MACROS_H */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 7c855db..1fb70e1 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -770,6 +770,7 @@ static char **complete_from_variables(const char *text,
 					const char *prefix, const char *suffix, bool need_value);
 static char *complete_from_files(const char *text, int state);
 
+static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static char *concatenate_strings(const char *s1, const char *s2);
 static char *additional_kw_query( const char *ref, int n, ...);
@@ -782,6 +783,7 @@ static char *get_guctype(const char *varname);
 
 static char **psql_completion_internal(const char *text, char **previous_words,
 										   int previous_words_count);
+static const pgsql_thing_t *find_thing_entry(char *word);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -990,6 +992,9 @@ static char **
 psql_completion_internal(const char *text, char **previous_words,
 						 int previous_words_count)
 {
+	/* The number of prefixing words to be ignored */
+	int			head_shift = 0;
+
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
@@ -1040,10 +1045,24 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
+	/*
+	 * If this is in CREATE SCHEMA, seek to the last CREATE and regard it as
+	 * current command to complete.
+	 */
+	if (HeadMatches2("CREATE", "SCHEMA"))
+		SHIFT_TO_LAST1("CREATE|GRANT");
+
 /* CREATE */
 	/* complete with something you can create */
 	if (Matches1("CREATE"))
-		return completion_matches(text, create_command_generator);
+	{
+		if (head_shift == 0)
+			return completion_matches(text, create_command_generator);
+		else
+			/* schema_element allows some kinds of object */
+			COMPLETE_WITH_LIST5("TABLE", "VIEW", "INDEX", "SEQUENCE",
+								"TRIGGER");
+	}			
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
@@ -1455,26 +1474,25 @@ psql_completion_internal(const char *text, char **previous_words,
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, "");
 	}
+	/* Remove COLUMN just after ALTER */
+	if (HeadMatches5("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN"))
+		COLLAPSE(5, 1);
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
-			 Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
 		COMPLETE_WITH_LIST4("TYPE", "SET", "RESET", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
-			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
 		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
-		 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
-	Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
 		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
-			 Matches8("ALTER", "TABLE", MatchAny, "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("DEFAULT", "NOT NULL");
+
 	if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
 	if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
@@ -1629,17 +1647,16 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   ADDLIST1("VERBOSE"));
-	if (Matches2("CLUSTER", "VERBOSE"))
+	/* Remove VERBOSE for further completion */
+	if (HeadMatches2("CLUSTER", "VERBOSE"))
+		COLLAPSE(2, 1);
+	if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
 	/* If we have CLUSTER <sth>, then add "USING" */
-	if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
-		COMPLETE_WITH_CONST("USING");
-	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	if (Matches3("CLUSTER", "VERBOSE", MatchAny))
+	if (Matches2("CLUSTER", MatchAny))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER <sth> USING, then add the index as well */
-	if (Matches3("CLUSTER", MatchAny, "USING") ||
-			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
+	if (Matches3("CLUSTER", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table, "");
@@ -1680,9 +1697,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 	if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, "");
-	if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
-		Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
-			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
+	if (HeadMatches3("COMMENT", "ON", MatchAny) &&
+		TailMatches1(MatchAnyExcept("IS")))
 		COMPLETE_WITH_CONST("IS");
 
 /* COPY */
@@ -1693,34 +1709,32 @@ psql_completion_internal(const char *text, char **previous_words,
 	 */
 	if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   ADDLIST1("("));
+								   ADDLIST2("(", "BINARY"));
 	/* If we have COPY BINARY, complete with list of tables */
 	if (Matches2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+	/* Remove BINARY of COPY for further completion */
+	if (HeadMatches2("COPY", "BINARY"))
 	/* If we have COPY (, complete it with legal commands */
 	if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
 	/* If we have COPY [BINARY] <sth>, complete it with "TO" or "FROM" */
-	if (Matches2("COPY|\\copy", MatchAny) ||
-			 Matches3("COPY", "BINARY", MatchAny))
+	if (Matches2("COPY|\\copy", MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "TO");
 	/* If we have COPY [BINARY] <sth> FROM|TO, complete with filename */
-	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
-			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
+	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO"))
 	{
 		SET_COMP_CHARP("");
 		return completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename */
-	if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
-			 Matches5("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
+	if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny))
 		COMPLETE_WITH_LIST6("BINARY", "OIDS", "DELIMITER", "NULL", "CSV",
 							"ENCODING");
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename CSV */
-	if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
-			 Matches6("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny, "CSV"))
+	if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV"))
 		COMPLETE_WITH_LIST5("HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE",
 							"FORCE NOT NULL");
 
@@ -1767,56 +1781,47 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
 
-	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	/* CREATE INDEX */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	if (TailMatches2("CREATE", "UNIQUE"))
+	if (Matches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
-	/*
-	 * If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY", and
-	 * existing indexes
-	 */
-	if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	/* Remove UNIQUE for further completion */
+	if (HeadMatches3("CREATE", "UNIQUE", "INDEX"))
+		COLLAPSE(2, 1);
+	/* Complete with index names as category suggestion and possible keywords */
+	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   ADDLIST2("ON", "CONCURRENTLY"));
-	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
-			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
-
-	/*
-	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
-	 * indexes
-	 */
-	if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, ADDLIST1("ON"));
-	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
-			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
+	/* Remove CONCURRENTLY for further completion */
+	if (HeadMatches3("CREATE", "INDEX", "CONCURRENTLY"))
+		COLLAPSE(3, 1);
+	/* Complete with existing index names as word category suggestion */
+	if (Matches2("CREATE", "INDEX"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   ADDLIST1("ON"));
+	/* Suggest ON just after index name */
+	if (Matches3("CREATE", "INDEX", MatchAnyExcept("ON")))
 		COMPLETE_WITH_CONST("ON");
-
-	/*
-	 * Complete INDEX <name> ON <table> with a list of table columns (which
-	 * should really be in parens)
-	 */
-	if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-		TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+	/* Specified index name does matter ever after */
+	if (HeadMatches4("CREATE", "INDEX", MatchAny, "ON"))
+		COLLAPSE(3, 1);
+	/* Complete with table names only*/
+	if (Matches3("CREATE", "INDEX", "ON"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "");
+	/* MatchAny is table name */
+	if (Matches4("CREATE", "INDEX", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-		TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
-	/* same if you put in USING */
-	if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
-			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
-			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
+	if (Matches5("CREATE", "INDEX", "ON", MatchAny, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, "");
-	if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
-			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
-			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
+	/*  Remove "Using xxx" for further completion*/
+	if (HeadMatches6("CREATE", "INDEX", "ON", MatchAny, "USING", MatchAny))
+		COLLAPSE(5, 2);
+	if (Matches4("CREATE", "INDEX", "ON", MatchAny))
 		COMPLETE_WITH_CONST("(");
+	if (Matches5("CREATE", "INDEX", "ON", MatchAny, "("))
+		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
@@ -1848,43 +1853,72 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
-	/* Complete "CREATE RULE <sth>" with "AS ON" */
-	if (Matches3("CREATE", "RULE", MatchAny))
-		COMPLETE_WITH_CONST("AS ON");
-	/* Complete "CREATE RULE <sth> AS" with "ON" */
-	if (Matches4("CREATE", "RULE", MatchAny, "AS"))
-		COMPLETE_WITH_CONST("ON");
-	/* Complete "CREATE RULE <sth> AS ON" with SELECT|UPDATE|INSERT|DELETE */
-	if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
-		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
-	/* Complete "AS ON SELECT|UPDATE|INSERT|DELETE" with a "TO" */
-	if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
-		COMPLETE_WITH_CONST("TO");
-	/* Complete "AS ON <sth> TO" with a table name */
-	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+	if (HeadMatches2("CREATE", "RULE"))
+	{
+		/* Complete "CREATE RULE <sth>" with "AS ON" */
+		if (Matches3("CREATE", "RULE", MatchAny))
+			COMPLETE_WITH_CONST("AS ON");
+		/* Complete "CREATE RULE <sth> AS" with "ON" */
+		if (Matches4("CREATE", "RULE", MatchAny, "AS"))
+			COMPLETE_WITH_CONST("ON");
+		/* Complete "CREATE RULE <sth> AS ON" with SELECT|UPDATE|INSERT|DELETE */
+		if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
+			COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
+		/* Complete "AS ON SELECT|UPDATE|INSERT|DELETE" with a "TO" */
+		if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
+			COMPLETE_WITH_CONST("TO");
+		/* Complete "AS ON <sth> TO" with a table name */
+		if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+		/* Complete "ON <sth> TO <name>" with DO INSTEAD*/
+		if (TailMatches4("ON", "SELECT|UPDATE|INSERT|DELETE", "TO", MatchAny))
+			COMPLETE_WITH_CONST("DO INSTEAD");
+		/* Complete DO INSTEAD with actions */
+		if (TailMatches2("DO", "INSTEAD"))
+			COMPLETE_WITH_LIST5("SELECT", "INSERT", "UPDATE", "DELETE", "NOTIFY");
+		/* Complete DO INSTEAD and further */
+		SHIFT_TO_LAST1("SELECT|INSERT|UPDATE|DELETE|NOTIFY");
+		if (HeadMatches1("SELECT|INSERT|UPDATE|DELETE|NOTIFY"))
+			return psql_completion_internal(text, previous_words, WORD_COUNT());
+		COMPLETE_WITH_CONST("");
+	}
+	
+	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
+	if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
+	/* Remove TEMPORARY/TEMP for further completion */
+	if (HeadMatches3("CREATE", "TEMP|TEMPORARY", "TABLE|VIEW|SEQUENCE"))
+		COLLAPSE(2, 1);
 
-/* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
-			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
-		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
-							"CYCLE", "OWNED BY", "START WITH");
-	if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
-		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
+	/* CREATE SEQUENCE */
+	if (Matches2("CREATE", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
+	if (Matches3("CREATE", "SEQUENCE", MatchAny))
+		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO",
+							"CACHE", "CYCLE", "OWNED BY", "START WITH");
+	if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
 	if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
-/* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
-		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
+/* CREATE SCHEMA <name> */
+	if (Matches2("CREATE", "SCHEMA"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
+
+/* CREATE TABLE  */
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	if (TailMatches2("CREATE", "UNLOGGED"))
+	if (Matches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
+	/* Remove UNLOGGED for further completion */
+	if (HeadMatches2("CREATE", "UNLOGGED"))
+		COLLAPSE(2, 1);
+	/* Complete CREATE TABLE with existing table names */
+	if (Matches2("CREATE", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+
 /* CREATE TABLESPACE */
 	if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
@@ -1898,7 +1932,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
-/* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
 	if (TailMatches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
@@ -1966,12 +2000,12 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
-/* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE VIEW */
 	/* Complete CREATE VIEW <name> with AS */
-	if (TailMatches3("CREATE", "VIEW", MatchAny))
+	if (Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
+	if (Matches4("CREATE", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
@@ -2001,15 +2035,15 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
 		COMPLETE_WITH_LIST3("WITH HOLD", "WITHOUT HOLD", "FOR");
 
-/* DELETE --- can be inside EXPLAIN, RULE, etc */
+/* DELETE */
 	/* ... despite which, only complete DELETE with FROM at start of line */
 	if (Matches1("DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
-	if (TailMatches2("DELETE", "FROM"))
+	if (Matches2("DELETE", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete DELETE FROM <table> */
-	if (TailMatches3("DELETE", "FROM", MatchAny))
+	if (Matches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
 	/* XXX: implement tab completion for DELETE ... USING */
 
@@ -2049,14 +2083,17 @@ psql_completion_internal(const char *text, char **previous_words,
 								   ADDLIST1("CONCURRENTLY"));
 	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
+	if (HeadMatches3("DROP", "INDEX", "CONCURRENTLY"))
+		COLLAPSE(3, 1);
 	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
-		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
 	if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+
+	/* DROP VIEW is suggested as a general thing */
+
 	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 
@@ -2128,13 +2165,23 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"ANALYZE", "VERBOSE");
-	if (Matches2("EXPLAIN", "ANALYZE"))
+	if (HeadMatches2("EXPLAIN", "ANALYZE"))
+		COLLAPSE(2, 1);
+	if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"VERBOSE");
-	if (Matches2("EXPLAIN", "VERBOSE") ||
-			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
+	if (HeadMatches2("EXPLAIN", "VERBOSE"))
+		COLLAPSE(2, 1);
+	if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 
+	/* complete on individual syntaxes here after */
+	if (Matches2("EXPLAIN", "SELECT|INSERT|DELETE|UPDATE|DECLARE"))
+	{
+		COLLAPSE(1, 1);
+		return psql_completion_internal(text, previous_words, WORD_COUNT());
+	}
+
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
 	if (Matches1("FETCH|MOVE"))
@@ -2170,9 +2217,9 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatches2("FOREIGN", "SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers, "");
 
-/* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* GRANT && REVOKE */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	if (TailMatches1("GRANT|REVOKE"))
+	if (Matches1("GRANT|REVOKE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles,
 			ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
 					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
@@ -2182,13 +2229,13 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	if (TailMatches2("GRANT|REVOKE", MatchAny))
+	if (Matches2("GRANT|REVOKE", MatchAny))
 	{
 		if (TailMatches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
-		if (TailMatches2("GRANT", MatchAny))
+		if (HeadMatches1("GRANT"))		/* GRANT roles */
 			COMPLETE_WITH_CONST("TO");
-		else
+		else							/* REVOKE roles */
 			COMPLETE_WITH_CONST("FROM");
 	}
 
@@ -2203,7 +2250,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
 	 */
-	if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
+	if (Matches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
 			   ADDLIST15("ALL FUNCTIONS IN SCHEMA",
 						 "ALL SEQUENCES IN SCHEMA",
@@ -2221,11 +2268,11 @@ psql_completion_internal(const char *text, char **previous_words,
 						 "TABLESPACE",
 						 "TYPE"));
 
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
+	if (Matches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
 							"TABLES IN SCHEMA");
 
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+	if (Matches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/*
@@ -2234,27 +2281,11 @@ psql_completion_internal(const char *text, char **previous_words,
 	 *
 	 * Complete "GRANT/REVOKE * ON *" with "TO/FROM".
 	 */
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
+	if (Matches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
-		if (TailMatches1("DATABASE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases, "");
-		if (TailMatches1("DOMAIN"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, "");
-		if (TailMatches1("FUNCTION"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, "");
-		if (TailMatches1("LANGUAGE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_languages, "");
-		if (TailMatches1("SCHEMA"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas, "");
-		if (TailMatches1("SEQUENCE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
-		if (TailMatches1("TABLE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, "");
-		if (TailMatches1("TABLESPACE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, "");
-		if (TailMatches1("TYPE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, "");
-		if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
+		if (TailMatches1("DATABASE|DOMAIN|FUNCTION|LANGUAGE|SCHEMA|SEQUENCE|TABLE|TABLESPACE|TYPE"))
+			COMPLETE_THING(-1);
+		if (HeadMatches1("GRANT"))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2275,27 +2306,13 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("FROM");
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
-	{
-		if (TailMatches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
-			COMPLETE_WITH_CONST("TO");
-		else
-			COMPLETE_WITH_CONST("FROM");
-	}
-
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
-	{
-		if (TailMatches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
-			COMPLETE_WITH_CONST("TO");
-		else
-			COMPLETE_WITH_CONST("FROM");
-	}
-
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
+	if (Matches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny) ||
+		Matches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny) ||
+		Matches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
 	{
-		if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (HeadMatches1("GRANT"))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2311,29 +2328,29 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("IMPORT", "FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
-/* INSERT --- can be inside EXPLAIN, RULE, etc */
+/* INSERT */
 	/* Complete INSERT with "INTO" */
-	if (TailMatches1("INSERT"))
+	if (Matches1("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
-	if (TailMatches2("INSERT", "INTO"))
+	if (Matches2("INSERT", "INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete "INSERT INTO <table> (" with attribute names */
-	if (TailMatches4("INSERT", "INTO", MatchAny, "("))
+	if (Matches4("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES"
 	 */
-	if (TailMatches3("INSERT", "INTO", MatchAny))
+	if (Matches3("INSERT", "INTO", MatchAny))
 		COMPLETE_WITH_LIST5("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES");
 
 	/*
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE"
 	 */
-	if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
+	if (Matches4("INSERT", "INTO", MatchAny, MatchAny) &&
 			 ends_with(prev_wd, ')'))
 		COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES");
 
@@ -2351,22 +2368,26 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* For the following, handle the case of a single table only for now */
 
+	/* Remove TABLE and ONLY from LOCK */
+	if (HeadMatches3("LOCK", "TABLE", MatchAny))
+		COLLAPSE(2, 1);
+	if (HeadMatches3("LOCK", "ONLY", MatchAny))
+		COLLAPSE(2, 1);
+
 	/* Complete LOCK [TABLE] <table> with "IN" */
-	if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
-			 Matches3("LOCK", "TABLE", MatchAny))
+	if (Matches2("LOCK", MatchAnyExcept("TABLE")))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	if (Matches3("LOCK", MatchAny, "IN") ||
-			 Matches4("LOCK", "TABLE", MatchAny, "IN"))
+	if (Matches3("LOCK", MatchAny, "IN"))
 		COMPLETE_WITH_LIST8("ACCESS SHARE MODE",
 							"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
 							"SHARE UPDATE EXCLUSIVE MODE", "SHARE MODE",
 							"SHARE ROW EXCLUSIVE MODE",
 							"EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE");
 
-/* NOTIFY --- can be inside EXPLAIN, RULE, etc */
-	if (TailMatches1("NOTIFY"))
+/* NOTIFY  */
+	if (Matches1("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'", "");
 
 /* OPTIONS */
@@ -2384,8 +2405,15 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 /* PREPARE xx AS */
-	if (Matches3("PREPARE", MatchAny, "AS"))
-		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
+	if (HeadMatches1("PREPARE"))
+	{
+		if (Matches3("PREPARE", MatchAny, "AS"))
+			COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
+
+		/* Complete for indivisual command */
+		SHIFT_TO_LAST1("SELECT|UPDATE|INSERT|DELETE");
+		return psql_completion_internal(text, previous_words, WORD_COUNT());
+	}
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -2446,8 +2474,9 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("ON", "FOR");
 	if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	if (Matches3("SECURITY", "LABEL", "ON") ||
-			 Matches5("SECURITY", "LABEL", "FOR", MatchAny, "ON"))
+	if (HeadMatches4("SECURITY", "LABEL", "FOR", MatchAny))
+		COLLAPSE(3, 2);
+	if (Matches3("SECURITY", "LABEL", "ON"))
 	{
 		static const char *const list_SECURITY_LABEL[] =
 		{"TABLE", "COLUMN", "AGGREGATE", "DATABASE", "DOMAIN",
@@ -2475,35 +2504,42 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* Complete "SET TRANSACTION" */
 	if (Matches2("SET", "TRANSACTION"))
 		COMPLETE_WITH_LIST5("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	if (Matches2("BEGIN|START", "TRANSACTION") ||
-		Matches2("BEGIN", "WORK") ||
-		Matches1("BEGIN") ||
-		Matches5("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
-		COMPLETE_WITH_LIST4("ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
-		Matches2("BEGIN", "NOT") ||
-		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
-		COMPLETE_WITH_CONST("DEFERRABLE");
-	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
-		Matches2("BEGIN", "ISOLATION") ||
-		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
-		COMPLETE_WITH_CONST("LEVEL");
-	if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
-		Matches3("BEGIN", "ISOLATION", "LEVEL") ||
-		Matches7("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
-		COMPLETE_WITH_LIST3("READ", "REPEATABLE READ", "SERIALIZABLE");
-	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
-		Matches4("BEGIN", "ISOLATION", "LEVEL", "READ") ||
-		Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
-		COMPLETE_WITH_LIST2("UNCOMMITTED", "COMMITTED");
-	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
-		Matches4("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
-		Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
-		COMPLETE_WITH_CONST("READ");
-	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
-		Matches2("BEGIN", "READ") ||
-		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
-		COMPLETE_WITH_LIST2("ONLY", "WRITE");
+	if (HeadMatches2("BEGIN", "WORK|TRANSACTION"))
+		COLLAPSE(2, 1);
+	{
+		int shift = 0;
+
+		if (HeadMatches2("START", "TRANSACTION"))
+			shift = 2;
+		if (HeadMatches1("BEGIN"))
+			shift = 1;
+		if (HeadMatches5("SET", "SESSION", "CHARACTERISTICS", "AS",
+						 "TRANSACTION"))
+			shift = 5;
+
+		if (shift > 0)
+		{
+			/* complete with transaction mode */
+			HEAD_SHIFT(shift);
+
+			if (WORD_COUNT() == 0)
+				COMPLETE_WITH_LIST4("ISOLATION LEVEL", "READ", "DEFERRABLE",
+									"NOT DEFERRABLE");
+			if (Matches1("NOT"))
+				COMPLETE_WITH_CONST("DEFERRABLE");
+			if (Matches1("ISOLATION"))
+				COMPLETE_WITH_CONST("LEVEL");
+			if (Matches2("ISOLATION", "LEVEL"))
+				COMPLETE_WITH_LIST3("READ", "REPEATABLE READ", "SERIALIZABLE");
+			if (Matches3("ISOLATION", "LEVEL", "REPEATABLE"))
+				COMPLETE_WITH_CONST("READ");
+			if (Matches3("ISOLATION", "LEVEL", "READ"))
+				COMPLETE_WITH_LIST2("UNCOMMITTED", "COMMITTED");
+			if (Matches1("READ"))
+				COMPLETE_WITH_LIST2("ONLY", "WRITE");
+			COMPLETE_WITH_CONST("");
+		}
+	}
 	/* SET CONSTRAINTS */
 	if (Matches2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema,
@@ -2600,18 +2636,18 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches1("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'", "");
 
-/* UPDATE --- can be inside EXPLAIN, RULE, etc */
+/* UPDATE  */
 	/* If prev. word is UPDATE suggest a list of tables */
-	if (TailMatches1("UPDATE"))
+	if (Matches1("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, "");
 	/* Complete UPDATE <table> with "SET" */
-	if (TailMatches2("UPDATE", MatchAny))
+	if (Matches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
 	/* Complete UPDATE <table> SET with list of attributes */
-	if (TailMatches3("UPDATE", MatchAny, "SET"))
+	if (Matches3("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* UPDATE <table> SET <attr> = */
-	if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
+	if (Matches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
@@ -2843,19 +2879,14 @@ psql_completion_internal(const char *text, char **previous_words,
 	 */
 	else
 	{
-		int			i;
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
 
-		for (i = 0; words_after_create[i].name; i++)
+		if (ent)
 		{
-			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
-			{
-				if (words_after_create[i].query)
-					COMPLETE_WITH_QUERY(words_after_create[i].query, "");
-				if (words_after_create[i].squery)
-					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
-											   "");
-				break;
-			}
+			if (ent->query)
+				COMPLETE_WITH_QUERY(ent->query, "");
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, "");
 		}
 	}
 
@@ -3366,6 +3397,18 @@ complete_from_files(const char *text, int state)
 
 /* HELPER FUNCTIONS */
 
+/*
+ * Return the index (reverse to the index of previous_words) of the tailmost
+ * (topmost in the array) appearance of w.
+ */
+static int
+find_last_index_of(char *w, char **previous_words, int len)
+{
+	int i;
+
+	for (i = 0 ; i < len && !word_matches(w, previous_words[i]) ; i++);
+	return i < len ? (len - i - 1) : 0;
+}
 
 /*
  * Make a pg_strdup copy of s and convert the case according to
@@ -3665,6 +3708,24 @@ get_guctype(const char *varname)
 	return guctype;
 }
 
+/*
+ * Finds the entry in words_after_create[] that matches the word.
+ * NULL if not found.
+ */
+static const pgsql_thing_t *
+find_thing_entry(char *word)
+{
+	int			i;
+
+	for (i = 0; words_after_create[i].name; i++)
+	{
+		if (pg_strcasecmp(word, words_after_create[i].name) == 0)
+			return words_after_create + i;
+	}
+
+	return NULL;
+}
+
 #ifdef NOT_USED
 
 /*
-- 
2.9.2

0005-Add-suggestion-for-IF-NOT-EXISTS-for-some-syntaxes.patchtext/x-patch; charset=us-asciiDownload
From 26fe0b403f9848f42845b435902c0661e3673a24 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Thu, 15 Sep 2016 20:49:45 +0900
Subject: [PATCH 5/5] Add suggestion for IF (NOT) EXISTS for some syntaxes

Add suggestion for IF EXISTS or IF NOT EXISTS for some arbitrary
syntaxes, ALTER TABLE/ALTER FOREIGN TABLE/ALTER INDEX/
ALTER MATERIALIZED VIEW/ALTER DOMAIN DROP CONSTRAINT/ALTER SEQUENCE/
ALTER VIEW/ALTER POLICY/ALTER TABLE DROP COLUMN/
ALTER TABLE DROP CONSTRAINT/CREATE INDEX/CREATE SEQUENCE/
CREATE TABLE/CREATE VIEW/DROP INDEX/DROP (MATERIALIZED) VIEW/
DROP USER MAPPING
---
 src/bin/psql/tab-complete.c | 178 +++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 167 insertions(+), 11 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 1fb70e1..528cfff 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1074,7 +1074,9 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* ALTER TABLE */
 	if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   ADDLIST1("ALL IN TABLESPACE"));
+								   ADDLIST2("ALL IN TABLESPACE", "IF EXISTS"));
+	if (HeadMatches4("ALTER", "TABLE", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
 
 	/* ALTER something */
 	if (Matches1("ALTER"))
@@ -1177,6 +1179,14 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
+	/* ALTER FOREIGN TABLE */
+	if (Matches3("ALTER", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables,
+								   ADDLIST1("IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (HeadMatches5("ALTER", "FOREIGN", "TABLE", "IF", "EXISTS"))
+		COLLAPSE(4, 2);
+
 	/* ALTER FOREIGN TABLE <name> */
 	if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
@@ -1191,7 +1201,11 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* ALTER INDEX */
 	if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   ADDLIST1("ALL IN TABLESPACE"));
+								   ADDLIST2("ALL IN TABLESPACE", "IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (HeadMatches4("ALTER", "INDEX", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+
 	/* ALTER INDEX <name> */
 	if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
@@ -1220,7 +1234,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* ALTER MATERIALIZED VIEW */
 	if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   ADDLIST1("ALL IN TABLESPACE"));
+								   ADDLIST2("ALL IN TABLESPACE", "IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (HeadMatches5("ALTER", "MATERIALIZED", "VIEW", "IF", "EXISTS"))
+		COLLAPSE(4, 2);
 
 	/* ALTER USER,ROLE <name> */
 	if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
@@ -1276,6 +1293,19 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
+		if (word_matches("DROP", prev2_wd))
+			COMPLETE_WITH_QUERY(Query_for_constraint_of_type,
+								ADDLIST1("IF EXISTS"));
+		else
+			COMPLETE_WITH_QUERY(Query_for_constraint_of_type, "");
+	}
+	/* Remove optional words for further completion */
+	if (HeadMatches7("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT", "IF", "EXISTS"))
+		COLLAPSE(6, 2);
+	/*  Complete constraint name again without IF EXISTS */
+	if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, "");
 	}
 	/* ALTER DOMAIN <sth> RENAME */
@@ -1288,6 +1318,16 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* ALTER DOMAIN <sth> SET */
 	if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
+	/* ALTER SEQUENCE */
+	if (Matches2("ALTER", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
+								   ADDLIST1("IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (HeadMatches4("ALTER", "SEQUENCE", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches2("ALTER", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
 	/* ALTER SEQUENCE <name> */
 	if (Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
@@ -1313,6 +1353,16 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars,
 							ADDLIST1("ALL"));
+	/* ALTER VIEW */
+	if (Matches2("ALTER", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   ADDLIST1("IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (HeadMatches4("ALTER", "VIEW", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches2("ALTER", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	/* ALTER VIEW <name> */
 	if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
@@ -1324,6 +1374,12 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER POLICY <name> */
 	if (Matches2("ALTER", "POLICY"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies, ADDLIST1("IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (Matches4("ALTER", "POLICY", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches2("ALTER", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies, "");
 	/* ALTER POLICY <name> ON */
 	if (Matches3("ALTER", "POLICY", MatchAny))
@@ -1463,8 +1519,13 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+		COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (HeadMatches7("ALTER", "TABLE", MatchAny, "DROP", "COLUMN", "IF", "EXISTS"))
+		COLLAPSE(6, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
-
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
@@ -1472,6 +1533,19 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
+		if (word_matches("DROP", prev2_wd))
+			COMPLETE_WITH_QUERY(Query_for_constraint_of_table,
+								ADDLIST1("IF EXISTS"));
+		else
+			COMPLETE_WITH_QUERY(Query_for_constraint_of_table, "");
+	}
+	/* Remove IF EXISTS for further completion */
+	if (HeadMatches7("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT", "IF", "EXISTS"))
+		COLLAPSE(6, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, "");
 	}
 	/* Remove COLUMN just after ALTER */
@@ -1614,7 +1688,18 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * of attributes
 	 */
 	if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+	{
+		if (word_matches("DROP", prev2_wd))
+			COMPLETE_WITH_ATTR(prev3_wd, ADDLIST1("IF EXISTS"));
+		else
+			COMPLETE_WITH_ATTR(prev3_wd, "");
+	}
+	/* Remove IF EXISTS for further completion */
+	if (HeadMatches7("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE", "IF", "EXISTS"))
+		COLLAPSE(6, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE"))
+			COMPLETE_WITH_ATTR(prev3_wd, "");	
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
 	if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
@@ -1762,6 +1847,13 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	if (Matches2("CREATE", "EXTENSION"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions,
+							ADDLIST1("IF NOT EXISTS"));
+	/* Remove IF NOT EXISTS for further completion*/
+	if (HeadMatches5("CREATE", "EXTENSION", "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+	/* Complete again without IF NOT EXISTS */
+	if (Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions, "");
 	/* CREATE EXTENSION <name> */
 	if (Matches3("CREATE", "EXTENSION", MatchAny))
@@ -1792,14 +1884,20 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* Complete with index names as category suggestion and possible keywords */
 	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   ADDLIST2("ON", "CONCURRENTLY"));
+						   ADDLIST3("ON", "CONCURRENTLY", "IF NOT EXISTS"));
 	/* Remove CONCURRENTLY for further completion */
 	if (HeadMatches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COLLAPSE(3, 1);
 	/* Complete with existing index names as word category suggestion */
 	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   ADDLIST1("ON"));
+								   ADDLIST2("ON", "IF NOT EXISTS"));
+	/* Remove IF NOT EXISTS for further completion */
+	if (HeadMatches5("CREATE", "INDEX", "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+	/* Complete again without IF NOT EXISTS */
+	if (Matches2("CREATE", "INDEX"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 	/* Suggest ON just after index name */
 	if (Matches3("CREATE", "INDEX", MatchAnyExcept("ON")))
 		COMPLETE_WITH_CONST("ON");
@@ -1892,6 +1990,13 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* CREATE SEQUENCE */
 	if (Matches2("CREATE", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences,
+								   ADDLIST1("IF NOT EXISTS"));
+	/* Remove IF NOT EXISTS for further completion */
+	if (HeadMatches5("CREATE", "SEQUENCE", "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+	/* Complete again without IF NOT EXISTS */
+	if (Matches2("CREATE", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, "");
 	if (Matches3("CREATE", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO",
@@ -1917,6 +2022,13 @@ psql_completion_internal(const char *text, char **previous_words,
 		COLLAPSE(2, 1);
 	/* Complete CREATE TABLE with existing table names */
 	if (Matches2("CREATE", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+								   ADDLIST1("IF NOT EXISTS"));
+	/* Remove IF NOT EXIST for further completion */
+	if (HeadMatches5("CREATE", "TABLE", "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+	/* Complete again without IF NOT EXISTS */
+	if (Matches2("CREATE", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 /* CREATE TABLESPACE */
@@ -2001,6 +2113,16 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
 /* CREATE VIEW */
+	/* Complete CREATE VIEW with name */
+	if (Matches2("CREATE", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   ADDLIST1("IF NOT EXISTS"));
+	/* Remove IF NOT EXISTS for further completion */
+	if (HeadMatches5("CREATE", "VIEW", "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+	/* Complete again without IF NOT EXISTS */
+	if (Matches2("CREATE", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, "");
 	/* Complete CREATE VIEW <name> with AS */
 	if (Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
@@ -2080,20 +2202,43 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* DROP INDEX */
 	if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   ADDLIST1("CONCURRENTLY"));
+								   ADDLIST2("CONCURRENTLY", "IF EXISTS"));
 	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+								   ADDLIST1("IF EXISTS"));
 	if (HeadMatches3("DROP", "INDEX", "CONCURRENTLY"))
 		COLLAPSE(3, 1);
+	/* Remove IF EXISTS for further completion */
+	if (HeadMatches4("DROP", "INDEX", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches2("DROP", "INDEX"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, "");
 	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
+	/* DROP VIEW */
+	if (Matches2("DROP", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views,
+								   ADDLIST1("IF EXISTS"));
+	/* Remove IF EXISTS for further completion */
+	if (HeadMatches4("DROP", "VIEW", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+	/* Complet again without IF EXISTS */
+	if (Matches2("DROP", "VIEW"))
+		COMPLETE_THING(-1);
+
 	/* DROP MATERIALIZED VIEW */
 	if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 
-	/* DROP VIEW is suggested as a general thing */
-
+	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+								   ADDLIST1("IF EXISTS"));
+	/* Remove IF EXISTS for further completion */
+	if (HeadMatches5("DROP", "MATERIALIZED", "VIEW", "IF", "EXISTS"))
+		COLLAPSE(4, 2);
+	/* Complet again without IF EXISTS */
 	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, "");
 
@@ -2652,6 +2797,17 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* USER MAPPING */
 	if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
+	{
+		if (word_matches("DROP", prev3_wd))
+			COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR");
+		else
+			COMPLETE_WITH_CONST("FOR");
+	}
+	/* Remove IF EXISTS for further completion */
+	if (HeadMatches5("DROP", "USER", "MAPPING", "IF", "EXISTS"))
+		COLLAPSE(4, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches3("DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
 	if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, 
-- 
2.9.2

#42Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#41)
Re: IF (NOT) EXISTS in psql-completion

Hi

2016-09-16 10:31 GMT+02:00 Kyotaro HORIGUCHI <
horiguchi.kyotaro@lab.ntt.co.jp>:

Hello, this is the new version.

At Tue, 13 Sep 2016 10:50:13 +0900 (Tokyo Standard Time), Kyotaro
HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <
20160913.105013.65452566.horiguchi.kyotaro@lab.ntt.co.jp>

This patch consists of the following files. Since these files are
splitted in strange criteria and order for historical reasons,
I'll reorganize this and post them later.

The focus of this patch has changed. The first motivation was
completing IF-EXISTS but the underlying issue was flexibility of
psql_completion. And as Pavel's suggestion, keywords suggested
along with database objects should follow the character case of
input.

For the purpose of resolving the issues, I reorganized the
confused patch set. The attached patches are organized as the
following.

1. Refactoring tab-complete to make psql_completion code

Does two things. One is moving out the macros that has grown to
be too large to stay in tab_completion.c to new file
tab-complete-macros.h The other is separating out the else-if
sequence in psql_completion() as a new function
psql_completion_internal(). This allows us to the following
things.

- Exit from arbitrary place in the former-else-if sequence just
by return.

- Do other than "if(matching) { completion }" in anywhere
convenient in the midst of the former-els...

- Recursively matching for sub syntaxes. EXPLAIN, RULE and
others are using this feature. (Needs the 4th patch to do
this, though)

2. Make keywords' case follow to input

Allow the keywords suggested along with databse objects to
follow the input letter case. The core part of this patch is a
new function additional_kw_query(), which dynamically generates
additional query string with specified keywords in the desired
letter case. COMPLETE_WITH_* macros are modified to accept the
function.

3. Fix suggested keywords to follow input in tab-completion session 2

The 2nd patch above leaves some query string containing static
keyword strings, which results in failure to follow input
letter cases. Most of them are naturally removed but role names
are a bother. This patch puts additional query strings for
several usage of roles but it might be overdone.

4. Introduce word shift and removal feature to psql-completion

This is the second core for the flexibility of completion code.
The word shift feature is the ability to omit first several
words in *MatchesN macros. For example this allows complete
create-schema's schema elements in a natural code. (Currently
those syntaxes that can be a schema elements are using
TailMatches instead of Matches, as the result HeadMatches are
not available there). The words removing feature is the ability
to (desructively) clip multiple suceessive words in the
previous_words list. This feature allows suceeding completion
code not to care about the removed words, such like UNIQUE,
CONCURRENTLY, VERBOSE and so on.

5. Add suggestion for IF (NOT) EXISTS for some syntaxes

This adds IF (NOT) EXISTS suggestion, as a PoC. This patch no
loger covers all adoptable syntaces since the places where more
than boilerplating is required are omitted.

I am working on some initial tests - a compilation, a patching is ok.
Autocomplete for DROP TABLE IF EXISTS doesn't work. CREATE TABLE IF NOT
EXIST works well

Regards

Pavel

Show quoted text

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#43Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#41)
Re: IF (NOT) EXISTS in psql-completion

2016-09-16 10:31 GMT+02:00 Kyotaro HORIGUCHI <
horiguchi.kyotaro@lab.ntt.co.jp>:

Hello, this is the new version.

At Tue, 13 Sep 2016 10:50:13 +0900 (Tokyo Standard Time), Kyotaro
HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <
20160913.105013.65452566.horiguchi.kyotaro@lab.ntt.co.jp>

This patch consists of the following files. Since these files are
splitted in strange criteria and order for historical reasons,
I'll reorganize this and post them later.

The focus of this patch has changed. The first motivation was
completing IF-EXISTS but the underlying issue was flexibility of
psql_completion. And as Pavel's suggestion, keywords suggested
along with database objects should follow the character case of
input.

For the purpose of resolving the issues, I reorganized the
confused patch set. The attached patches are organized as the
following.

1. Refactoring tab-complete to make psql_completion code

Does two things. One is moving out the macros that has grown to
be too large to stay in tab_completion.c to new file
tab-complete-macros.h The other is separating out the else-if
sequence in psql_completion() as a new function
psql_completion_internal(). This allows us to the following
things.

- Exit from arbitrary place in the former-else-if sequence just
by return.

- Do other than "if(matching) { completion }" in anywhere
convenient in the midst of the former-els...

- Recursively matching for sub syntaxes. EXPLAIN, RULE and
others are using this feature. (Needs the 4th patch to do
this, though)

This first patch looks well - although it is big patch - it doesn't do any
not trivial work. No problems with a patching or compilation. I didn't find
any issue.

Regards

Pavel

Show quoted text

2. Make keywords' case follow to input

Allow the keywords suggested along with databse objects to
follow the input letter case. The core part of this patch is a
new function additional_kw_query(), which dynamically generates
additional query string with specified keywords in the desired
letter case. COMPLETE_WITH_* macros are modified to accept the
function.

3. Fix suggested keywords to follow input in tab-completion session 2

The 2nd patch above leaves some query string containing static
keyword strings, which results in failure to follow input
letter cases. Most of them are naturally removed but role names
are a bother. This patch puts additional query strings for
several usage of roles but it might be overdone.

4. Introduce word shift and removal feature to psql-completion

This is the second core for the flexibility of completion code.
The word shift feature is the ability to omit first several
words in *MatchesN macros. For example this allows complete
create-schema's schema elements in a natural code. (Currently
those syntaxes that can be a schema elements are using
TailMatches instead of Matches, as the result HeadMatches are
not available there). The words removing feature is the ability
to (desructively) clip multiple suceessive words in the
previous_words list. This feature allows suceeding completion
code not to care about the removed words, such like UNIQUE,
CONCURRENTLY, VERBOSE and so on.

5. Add suggestion for IF (NOT) EXISTS for some syntaxes

This adds IF (NOT) EXISTS suggestion, as a PoC. This patch no
loger covers all adoptable syntaces since the places where more
than boilerplating is required are omitted.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#44Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#43)
Re: IF (NOT) EXISTS in psql-completion

Hi

Pavel

2. Make keywords' case follow to input

Allow the keywords suggested along with databse objects to
follow the input letter case. The core part of this patch is a
new function additional_kw_query(), which dynamically generates
additional query string with specified keywords in the desired
letter case. COMPLETE_WITH_* macros are modified to accept the
function.

second patch is working, but I don't think it is enough documented

what is addon in COMPLETE_WITH_QUERY(query, addon)? semantics, usage?

in 99% the addon is "" when macro
COMPLETE_WITH_SCHEMA_QUERY,COMPLETE_WITH_QUERY is used. Maybe a
introduction of new macros with nonempty addon parameter should be better.

3. Fix suggested keywords to follow input in tab-completion session 2

The 2nd patch above leaves some query string containing static
keyword strings, which results in failure to follow input
letter cases. Most of them are naturally removed but role names
are a bother. This patch puts additional query strings for
several usage of roles but it might be overdone.

this patch looks well

4. Introduce word shift and removal feature to psql-completion

This is the second core for the flexibility of completion code.
The word shift feature is the ability to omit first several
words in *MatchesN macros. For example this allows complete
create-schema's schema elements in a natural code. (Currently
those syntaxes that can be a schema elements are using
TailMatches instead of Matches, as the result HeadMatches are
not available there). The words removing feature is the ability
to (desructively) clip multiple suceessive words in the
previous_words list. This feature allows suceeding completion
code not to care about the removed words, such like UNIQUE,
CONCURRENTLY, VERBOSE and so on.

I am thinking so commit's description should be inside README

Regards

Pavel

Show quoted text

5. Add suggestion for IF (NOT) EXISTS for some syntaxes

This adds IF (NOT) EXISTS suggestion, as a PoC. This patch no
loger covers all adoptable syntaces since the places where more
than boilerplating is required are omitted.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#45Michael Paquier
michael.paquier@gmail.com
In reply to: Pavel Stehule (#44)
Re: IF (NOT) EXISTS in psql-completion

On Mon, Sep 19, 2016 at 6:11 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I am thinking so commit's description should be inside README

Horiguchi-san, your patch has some whitespace issues, you may want to
get a run with git diff --check. Here are some things I have spotted:
src/bin/psql/tab-complete.c:1074: trailing whitespace.
+        "MATERIALIZED VIEW",
src/bin/psql/tab-complete.c:2621: trailing whitespace.
+       COMPLETE_WITH_QUERY(Query_for_list_of_roles,

This set of patches is making psql tab completion move into a better
shape, particularly with 0001 that removes the legendary huge if-elif
and just the routine return immediately in case of a keyword match.
Things could be a little bit more shortened by for example not doing
the refactoring of the tab macros because they are just needed in
tab-complete.c. The other patches introduce further improvements for
the existing infrastructure, but that's a lot of things just for
adding IF [NOT] EXISTS to be honest.

Testing a bit, I have noticed that for example trying to after typing
"create table if", if I attempt to do a tab completion "not exists"
does not show up. I suspect that the other commands are failing at
that as well.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#46Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#45)
Re: IF (NOT) EXISTS in psql-completion

On Tue, Sep 20, 2016 at 3:50 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Mon, Sep 19, 2016 at 6:11 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I am thinking so commit's description should be inside README

Horiguchi-san, your patch has some whitespace issues, you may want to
get a run with git diff --check. Here are some things I have spotted:
src/bin/psql/tab-complete.c:1074: trailing whitespace.
+        "MATERIALIZED VIEW",
src/bin/psql/tab-complete.c:2621: trailing whitespace.
+       COMPLETE_WITH_QUERY(Query_for_list_of_roles,

This set of patches is making psql tab completion move into a better
shape, particularly with 0001 that removes the legendary huge if-elif
and just the routine return immediately in case of a keyword match.
Things could be a little bit more shortened by for example not doing
the refactoring of the tab macros because they are just needed in
tab-complete.c. The other patches introduce further improvements for
the existing infrastructure, but that's a lot of things just for
adding IF [NOT] EXISTS to be honest.

Testing a bit, I have noticed that for example trying to after typing
"create table if", if I attempt to do a tab completion "not exists"
does not show up. I suspect that the other commands are failing at
that as well.

This patch hasn't been updated in over a week and we're just about out
of time for this CommitFest, so I've marked it "Returned with
Feedback" for now. If it gets updated, it can be resubmitted for the
next CommitFest.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#47Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Robert Haas (#46)
Re: IF (NOT) EXISTS in psql-completion

Hello,

At Wed, 28 Sep 2016 12:49:25 -0400, Robert Haas <robertmhaas@gmail.com> wrote in <CA+TgmoargP0PpbUKZFGsbx0yR=6OH8iBp8SPFHMaDaGy1CWKOQ@mail.gmail.com>

This patch hasn't been updated in over a week and we're just about out
of time for this CommitFest, so I've marked it "Returned with
Feedback" for now. If it gets updated, it can be resubmitted for the
next CommitFest.

Thanks, I will do it.

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#48Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Pavel Stehule (#44)
Re: IF (NOT) EXISTS in psql-completion

Thank you for reviewing!

At Mon, 19 Sep 2016 11:11:03 +0200, Pavel Stehule <pavel.stehule@gmail.com> wrote in <CAFj8pRCpoYMoUzZ74p0JvX=orUxs7o88UR0z0-Lqt6W6bS9DaQ@mail.gmail.com>

2. Make keywords' case follow to input

Allow the keywords suggested along with databse objects to
follow the input letter case. The core part of this patch is a
new function additional_kw_query(), which dynamically generates
additional query string with specified keywords in the desired
letter case. COMPLETE_WITH_* macros are modified to accept the
function.

second patch is working, but I don't think it is enough documented

Mmm. I should admit that. I will add comments in it.

what is addon in COMPLETE_WITH_QUERY(query, addon)? semantics, usage?

Original COMPLETE_WITH_QUERY gets only query, but it is used in
the form when additional keywords are needed.

| COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");

This is a string literal concatenation which is available only on
compile time. Letter case modification needs this done in
runtime. So it should be given as a separate parameter from the
main query.

in 99% the addon is "" when macro
COMPLETE_WITH_SCHEMA_QUERY,COMPLETE_WITH_QUERY is used. Maybe a
introduction of new macros with nonempty addon parameter should be better.

That looks better. I'll change the API as the following.

COMPLETE_WITH_QUERY(query);
COMPLETE_WITH_QUERY_KW(query, kwlist);
COMPLETE_WITH_SCHEMA_QUERY(squery);
COMPLETE_WITH_SCHEMA_QUERY_KW(squery, kwlist);

3. Fix suggested keywords to follow input in tab-completion session 2

The 2nd patch above leaves some query string containing static
keyword strings, which results in failure to follow input
letter cases. Most of them are naturally removed but role names
are a bother. This patch puts additional query strings for
several usage of roles but it might be overdone.

this patch looks well

4. Introduce word shift and removal feature to psql-completion

This is the second core for the flexibility of completion code.
The word shift feature is the ability to omit first several
words in *MatchesN macros. For example this allows complete
create-schema's schema elements in a natural code. (Currently
those syntaxes that can be a schema elements are using
TailMatches instead of Matches, as the result HeadMatches are
not available there). The words removing feature is the ability
to (desructively) clip multiple suceessive words in the
previous_words list. This feature allows suceeding completion
code not to care about the removed words, such like UNIQUE,
CONCURRENTLY, VERBOSE and so on.

I am thinking so commit's description should be inside README

Currently psql or tab-complete.c/psql_completion() have no such
document. If this should be written as README, perhaps I should
write about completion in general. On the other hand, per-macro
explanations are written in tab-complete-macros.h but the usages
are not. I'll try to write README.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#49Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Michael Paquier (#45)
Re: IF (NOT) EXISTS in psql-completion

Hello,

At Tue, 20 Sep 2016 16:50:29 +0900, Michael Paquier <michael.paquier@gmail.com> wrote in <CAB7nPqRY1B++XJ26Mb+AUJxZQhS_1qWMi+MOWqJTDUBKXuuGTw@mail.gmail.com>

On Mon, Sep 19, 2016 at 6:11 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I am thinking so commit's description should be inside README

Horiguchi-san, your patch has some whitespace issues, you may want to
get a run with git diff --check. Here are some things I have spotted:
src/bin/psql/tab-complete.c:1074: trailing whitespace.
+        "MATERIALIZED VIEW",
src/bin/psql/tab-complete.c:2621: trailing whitespace.
+       COMPLETE_WITH_QUERY(Query_for_list_of_roles,

Thank you very much for pointing it out. I put a pre-commit hook
to check that not to do such a mistake again.

http://stackoverflow.com/questions/591923/make-git-automatically-remove-trailing-whitespace-before-committing/22704385#22704385

This set of patches is making psql tab completion move into a better
shape, particularly with 0001 that removes the legendary huge if-elif
and just the routine return immediately in case of a keyword match.
Things could be a little bit more shortened by for example not doing
the refactoring of the tab macros because they are just needed in
tab-complete.c. The other patches introduce further improvements for
the existing infrastructure, but that's a lot of things just for
adding IF [NOT] EXISTS to be honest.

It was the motive for this, but even excluding it, some syntaxes
with optional keywords can be simplified or enriched with the new
macros. CREATE SCHEMA's schema elements, CREATE INDEX and some
other syntaxes are simplified using the feature.

Testing a bit, I have noticed that for example trying to after typing
"create table if", if I attempt to do a tab completion "not exists"
does not show up. I suspect that the other commands are failing at
that as well.

I suppose it is "create table if ", with a space at the tail. It
is a general issue on combined keywords(?) suggestion in the
whole tab-completion mechanism (or readline's limitation). Some
sytaxes have explicit complition for such cases. For examle,
"create foreign " gets a suggestion of "DATA WRAPPER" since it
has an explcit suggestion step.

/* ALTER FOREIGN */
if (Matches2("ALTER", "FOREIGN"))
COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");

It is apparently solvable, but needs additional code to suggest
the rest words for every steps. It should be another issue.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#50Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#48)
Re: IF (NOT) EXISTS in psql-completion

At Thu, 29 Sep 2016 16:16:00 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <20160929.161600.224338668.horiguchi.kyotaro@lab.ntt.co.jp>

That looks better. I'll change the API as the following.

COMPLETE_WITH_QUERY(query);
COMPLETE_WITH_QUERY_KW(query, kwlist);
COMPLETE_WITH_SCHEMA_QUERY(squery);
COMPLETE_WITH_SCHEMA_QUERY_KW(squery, kwlist);

Done on my environment.

4. Introduce word shift and removal feature to psql-completion

This is the second core for the flexibility of completion code.
The word shift feature is the ability to omit first several
words in *MatchesN macros. For example this allows complete
create-schema's schema elements in a natural code. (Currently
those syntaxes that can be a schema elements are using
TailMatches instead of Matches, as the result HeadMatches are
not available there). The words removing feature is the ability
to (desructively) clip multiple suceessive words in the
previous_words list. This feature allows suceeding completion
code not to care about the removed words, such like UNIQUE,
CONCURRENTLY, VERBOSE and so on.

I am thinking so commit's description should be inside README

Currently psql or tab-complete.c/psql_completion() have no such
document. If this should be written as README, perhaps I should
write about completion in general. On the other hand, per-macro
explanations are written in tab-complete-macros.h but the usages
are not. I'll try to write README.

Before proposing new patch including this. Since I'm totally
inconfident for my capability to write a documentation, I'd like
to ask anyone here of what shape we are to aim..

The following is the first part of the document I have written up
to now. Please help me by giving me any corrections, suggestions,
opinions, or anything else!

# The completion macro part would be better to be moved as
# comments for the macros in tab-complete-macros.h.

======================
src/bin/psql/README.completion

Word completion of interactive psql
===================================

psql supports word completion on interactive input. The core function
of the feature is psql_completion_internal in tab-complete.c. A bunch
of macros are provided in order to make it easier to read and maintain
the completion code. The console input to refer is stored in char **
previous_words in reverse order but maintaiers of
psql_completion_internal don't need to be aware of the detail of
it. Most of the operation can be described using the provided macros.

Basic structure of the completion code
--------------------------------------

The main part of the function is just a series of completion
definitions, where the first match wins. Each definition basically is
in the following shape.

if (*matching expression*)
*corresponding completion, then return*

The matching expression checks against all input words before the word
currently being entered. Completion chooses the words prefixed by
letters already entered. For example, for "CREATE <tab>" the word list
to be matched is ["CREATE"] and the prefix for completion is
nothing. For "CREATE INDEX id", the list is ["CREATE", "INDEX"] and
the prefix is "id".

Matching expression macros
--------------------------
There are four types of matching expression macros.

- MatchesN(word1, word2 .. , wordN)

true iff the word list is exactly the same as the paremeter.

- HeadMatchesN(word1, word2 .., wordN)

true iff the first N words in the word list matches the parameter.

- TailMatchesN(word1, word2 .., wordN)

true iff the last N words in the word list matches the parameter.

- MidMatchesN(pos, word1, word2 .., wordN)

true iff N successive words starts from pos in the word list matches
the parameter. The position is 1-based.

Completion macros
-----------------
There are N types of completion macros.

- COMPLETE_WITH_QUERY(query), COMPLETE_WITH_QUERY_KW(query, addon)

Suggest completion words acquired using the given query. The details
of the query is seen in the comment for _complete_from_query(). Word
matching is case-sensitive.

The latter takes an additional parameter, which should be a fragment
of query starts with " UNION " followed by a query string which
gives some additional words. This addon can be ADDLISTN() macro for
case-sensitive suggestion.

- COMPLETE_WITH_SCHEMA_QUERY(squery),
COMPLETE_WITH_SCHEMA_QUERY_KW(squery, addon)

Suggest based on a "schema query", which is a struct containing
parameters. You will see the details in the comment for
_complete_from_query(). Word maching is case-sensitive.

Just same as COMPLETE_WITH_QUERY_KW, the latter form takes a
fragment query same to that for COMPLETE_WITH_QUERY_KW.

- COMPLETE_WITH_LIST_CS(list)

Suggest completion words given as an array of strings. Word matching
is case-sensitive.

- COMPLETE_WITH_LIST_CSN(s1, s2.. ,sN)

Shortcut for COMPLETE_WITH_LIST_CS.

- COMPLETE_WITH_LIST(list)

Same as COMPLETE_WITH_LIST_CS except that word matching is
case-insensitive and the letter case of suggestions is decided
according to COMP_KEYWORD_CASE.

- COMPLETE_WITH_LISTN(s1, s2.. ,sN)

Shortcut for COMPLETE_WITH_LIST.

- COMPLETE_WITH_CONST(string)

Same as COMPLETE_WITH_LIST but with just one suggestion.

- COMPLETE_WITH_ATTR(relation, addon)

Suggest completion attribute names for the given relation. Word
matching is case-sensitve.

- COMPLETE_WITH_FUNCTION_ARG(function)

Suggest function name for the given SQL function. Word matching is
case-sensitve.

Additional keywords for COMPLETE_WITH(_SCHEMA)_QUERY
----------------------------------------------------

(snip, or done till here..)

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#51Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#50)
6 attachment(s)
Re: IF (NOT) EXISTS in psql-completion

Hello,

At Fri, 30 Sep 2016 14:43:03 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <20160930.144303.91443471.horiguchi.kyotaro@lab.ntt.co.jp>

The following is the first part of the document I have written up
to now. Please help me by giving me any corrections, suggestions,
opinions, or anything else!

Anyway, I fixed space issues and addressed the
COMPLETE_WITH_QUERY()'s almost unused parameter problem. And
tried to write a README files.

Some incomplete suggestions are not of this patch and maybe
proposed as another patch.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

0001-Refactoring-tab-complete-to-make-psql_completion-cod.patchtext/x-patch; charset=us-asciiDownload
From 54f0ab6d145ccbfc9ebdf950b3d207e5a3cd15e6 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Wed, 14 Sep 2016 11:59:04 +0900
Subject: [PATCH 1/6] Refactoring tab-complete to make psql_completion code
 simpler.

psql_completion consists of a long-long sequence of "else-if"s and
succeeding finishing code. This structure enforces us to use a very
tricky way to put something other than matching-completion pairs in
the midst of the sequence. This patch separates the
matching-completion pairs into individual function
"psql_completion_internal". Returning at the points of completion let
us free from the else-if sequence and let us put anything we want into
the matching-completion sequence.

Addition to that the amount of the code seems to be a good reason to
move it out of tab-complete.c. This patch searates it out as
tab-complete-macros.h
---
 src/bin/psql/Makefile              |    2 +
 src/bin/psql/tab-complete-macros.h |  405 +++++++++++
 src/bin/psql/tab-complete.c        | 1347 ++++++++++++++----------------------
 3 files changed, 932 insertions(+), 822 deletions(-)
 create mode 100644 src/bin/psql/tab-complete-macros.h

diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index 1f6a289..51f88ba 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -47,6 +47,8 @@ ifeq ($(GCC),yes)
 psqlscanslash.o: CFLAGS += -Wno-error
 endif
 
+tab-complete.o: tab-complete-macros.h
+
 distprep: sql_help.h psqlscanslash.c
 
 install: all installdirs
diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
new file mode 100644
index 0000000..8ee52b8
--- /dev/null
+++ b/src/bin/psql/tab-complete-macros.h
@@ -0,0 +1,405 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2016, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/tab-complete-macros.h
+ */
+#ifndef TAB_COMPLETE_MACROS_H
+#define TAB_COMPLETE_MACROS_H
+
+/*
+ * For compactness, we use these macros to reference previous_words[].
+ * Caution: do not access a previous_words[] entry without having checked
+ * previous_words_count to be sure it's valid.  In most cases below, that
+ * check is implicit in a TailMatches() or similar macro, but in some places
+ * we have to check it explicitly.
+ */
+#define prev_wd   (previous_words[0])
+#define prev2_wd  (previous_words[1])
+#define prev3_wd  (previous_words[2])
+#define prev4_wd  (previous_words[3])
+#define prev5_wd  (previous_words[4])
+#define prev6_wd  (previous_words[5])
+#define prev7_wd  (previous_words[6])
+#define prev8_wd  (previous_words[7])
+#define prev9_wd  (previous_words[8])
+
+/*
+ * Return the index in previous_words for index from the beginning. n is
+ * 1-based and the result is 0-based.
+ */
+#define HEAD_INDEX(n) \
+	(previous_words_count - (n))
+
+/*
+ * Macros for matching the last N words before point, and after head_sift,
+ * case-insensitively.
+ */
+#define TailMatches1(p1) \
+	(previous_words_count >= 1 && \
+	 word_matches(p1, prev_wd))
+
+#define TailMatches2(p2, p1) \
+	(previous_words_count >= 2 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd))
+
+#define TailMatches3(p3, p2, p1) \
+	(previous_words_count >= 3 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd))
+
+#define TailMatches4(p4, p3, p2, p1) \
+	(previous_words_count >= 4 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd))
+
+#define TailMatches5(p5, p4, p3, p2, p1) \
+	(previous_words_count >= 5 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd))
+
+#define TailMatches6(p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 6 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd))
+
+#define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 7 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd))
+
+#define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 8 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd) && \
+	 word_matches(p8, prev8_wd))
+
+#define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 9 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd) && \
+	 word_matches(p8, prev8_wd) && \
+	 word_matches(p9, prev9_wd))
+
+	/*
+	 * Macros for matching the last N words before point, and after
+	 * head_shift, case-sensitively.
+	 */
+#define TailMatchesCS1(p1) \
+	(previous_words_count >= 1 && \
+	 word_matches_cs(p1, prev_wd))
+#define TailMatchesCS2(p2, p1) \
+	(previous_words_count >= 2 && \
+	 word_matches_cs(p1, prev_wd) && \
+	 word_matches_cs(p2, prev2_wd))
+
+	/*
+	 * Macros for matching N words exactly to the line, and after head_shift,
+	 * case-insensitively.
+	 */
+#define Matches1(p1) \
+	(previous_words_count == 1 && \
+	 TailMatches1(p1))
+#define Matches2(p1, p2) \
+	(previous_words_count == 2 && \
+	 TailMatches2(p1, p2))
+#define Matches3(p1, p2, p3) \
+	(previous_words_count == 3 && \
+	 TailMatches3(p1, p2, p3))
+#define Matches4(p1, p2, p3, p4) \
+	(previous_words_count == 4 && \
+	 TailMatches4(p1, p2, p3, p4))
+#define Matches5(p1, p2, p3, p4, p5) \
+	(previous_words_count == 5 && \
+	 TailMatches5(p1, p2, p3, p4, p5))
+#define Matches6(p1, p2, p3, p4, p5, p6) \
+	(previous_words_count == 6 && \
+	 TailMatches6(p1, p2, p3, p4, p5, p6))
+#define Matches7(p1, p2, p3, p4, p5, p6, p7) \
+	(previous_words_count == 7 && \
+	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
+#define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
+	(previous_words_count == 8 && \
+	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
+#define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
+	(previous_words_count == 9 && \
+	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
+
+/*
+ * Macros for matching N words after head_shift + sth in the line, regardless
+ * of what is after them, case-insensitively.
+ */
+#define MidMatches1(s, p1) \
+	(HEAD_INDEX(s) >=0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]))
+
+#define MidMatches2(s, p1, p2) \
+	(HEAD_INDEX((s) + 1) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]))
+
+#define MidMatches3(s, p1, p2, p3) \
+	(HEAD_INDEX((s) + 2) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]))
+
+#define MidMatches4(s, p1, p2, p3, p4) \
+	(HEAD_INDEX((s) + 3) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]))
+
+#define MidMatches5(s, p1, p2, p3, p4, p5) \
+	(HEAD_INDEX((s) + 4) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&	\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&		\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]))
+
+#define MidMatches6(s,p1, p2, p3, p4, p5, p6)		\
+	(HEAD_INDEX((s) + 5) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]))
+
+#define MidMatches7(s,p1, p2, p3, p4, p5, p6, p7)	\
+	(HEAD_INDEX((s) + 6) >= 0 &&							\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&			\
+	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]))
+
+#define HeadMatches1(p1) \
+	MidMatches1(1, p1)
+#define HeadMatches2(p1, p2) \
+	MidMatches2(1, p1, p2)
+#define HeadMatches3(p1, p2, p3) \
+	MidMatches3(1, p1, p2, p3)
+#define HeadMatches4(p1, p2, p3, p4) \
+	MidMatches4(1, p1, p2, p3, p4)
+#define HeadMatches5(p1, p2, p3, p4, p5) \
+	MidMatches5(1, p1, p2, p3, p4, p5)
+#define HeadMatches6(p1, p2, p3, p4, p5, p6) \
+	MidMatches6(1, p1, p2, p3, p4, p5, p6)
+#define HeadMatches7(p1, p2, p3, p4, p5, p6, p7) \
+	MidMatches7(1, p1, p2, p3, p4, p5, p6, p7)
+
+/*
+ * A few macros to ease typing. You can use these to complete the given
+ * string with
+ * 1) The results from a query you pass it. (Perhaps one of those below?)
+ * 2) The results from a schema query you pass it.
+ * 3) The items from a null-pointer-terminated list (with or without
+ *	  case-sensitive comparison; see also COMPLETE_WITH_LISTn, below).
+ * 4) A string constant.
+ * 5) The list of attributes of the given table (possibly schema-qualified).
+ * 6) The list of arguments to the given function (possibly schema-qualified).
+ */
+#define COMPLETE_WITH_QUERY(query) \
+do { \
+	completion_charp = query;	\
+	return completion_matches(text, complete_from_query);	\
+} while (0)
+
+#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
+do { \
+	completion_squery = &(query); \
+	completion_charp = addon; \
+	return completion_matches(text, complete_from_schema_query); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS(list) \
+do { \
+	completion_charpp = list; \
+	completion_case_sensitive = true; \
+	return completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST(list) \
+do { \
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	return completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_CONST(string) \
+do { \
+	completion_charp = string;	\
+	completion_case_sensitive = false; \
+	return completion_matches(text, complete_from_const);	\
+} while (0)
+
+#define COMPLETE_WITH_ATTR(relation, addon) \
+do { \
+	char   *_completion_schema; \
+	char   *_completion_table; \
+\
+	_completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \
+								 false, false, pset.encoding); \
+	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+				   false, false, pset.encoding); \
+	_completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+								false, false, pset.encoding); \
+	if (_completion_table == NULL) \
+	{ \
+		completion_charp = Query_for_list_of_attributes addon; \
+		completion_info_charp = relation; \
+	} \
+	else \
+	{ \
+		completion_charp = Query_for_list_of_attributes_with_schema addon; \
+		completion_info_charp = _completion_table; \
+		completion_info_charp2 = _completion_schema; \
+	} \
+	return completion_matches(text, complete_from_query); \
+} while (0)
+
+#define COMPLETE_WITH_FUNCTION_ARG(function) \
+do { \
+	char   *_completion_schema; \
+	char   *_completion_function; \
+\
+	_completion_schema = strtokx(function, " \t\n\r", ".", "\"", 0, \
+								 false, false, pset.encoding); \
+	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+				   false, false, pset.encoding); \
+	_completion_function = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+								   false, false, pset.encoding); \
+	if (_completion_function == NULL) \
+	{ \
+		completion_charp = Query_for_list_of_arguments; \
+		completion_info_charp = function; \
+	} \
+	else \
+	{ \
+		completion_charp = Query_for_list_of_arguments_with_schema;	\
+		completion_info_charp = _completion_function; \
+		completion_info_charp2 = _completion_schema; \
+	} \
+	return completion_matches(text, complete_from_query); \
+} while (0)
+
+/*
+ * These macros simplify use of COMPLETE_WITH_LIST for short, fixed lists.
+ * There is no COMPLETE_WITH_LIST1; use COMPLETE_WITH_CONST for that case.
+ */
+#define COMPLETE_WITH_LIST2(s1, s2) \
+do { \
+	static const char *const list[] = { s1, s2, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST3(s1, s2, s3) \
+do { \
+	static const char *const list[] = { s1, s2, s3, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST4(s1, s2, s3, s4) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST5(s1, s2, s3, s4, s5) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST6(s1, s2, s3, s4, s5, s6) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST7(s1, s2, s3, s4, s5, s6, s7) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST8(s1, s2, s3, s4, s5, s6, s7, s8) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST9(s1, s2, s3, s4, s5, s6, s7, s8, s9) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST10(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+/*
+ * Likewise for COMPLETE_WITH_LIST_CS.
+ */
+#define COMPLETE_WITH_LIST_CS2(s1, s2) \
+do { \
+	static const char *const list[] = { s1, s2, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS3(s1, s2, s3) \
+do { \
+	static const char *const list[] = { s1, s2, s3, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS4(s1, s2, s3, s4) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS5(s1, s2, s3, s4, s5) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+
+
+#endif   /* TAB_COMPLETE_MACROS_H */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 50a45eb..679e58f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -34,6 +34,7 @@
 
 #include "postgres_fe.h"
 #include "tab-complete.h"
+#include "tab-complete-macros.h"
 #include "input.h"
 
 /* If we don't have this, we might as well forget about the whole thing: */
@@ -133,186 +134,6 @@ static const SchemaQuery *completion_squery;	/* to pass a SchemaQuery */
 static bool completion_case_sensitive;	/* completion is case sensitive */
 
 /*
- * A few macros to ease typing. You can use these to complete the given
- * string with
- * 1) The results from a query you pass it. (Perhaps one of those below?)
- * 2) The results from a schema query you pass it.
- * 3) The items from a null-pointer-terminated list (with or without
- *	  case-sensitive comparison; see also COMPLETE_WITH_LISTn, below).
- * 4) A string constant.
- * 5) The list of attributes of the given table (possibly schema-qualified).
- * 6/ The list of arguments to the given function (possibly schema-qualified).
- */
-#define COMPLETE_WITH_QUERY(query) \
-do { \
-	completion_charp = query; \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
-do { \
-	completion_squery = &(query); \
-	completion_charp = addon; \
-	matches = completion_matches(text, complete_from_schema_query); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS(list) \
-do { \
-	completion_charpp = list; \
-	completion_case_sensitive = true; \
-	matches = completion_matches(text, complete_from_list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST(list) \
-do { \
-	completion_charpp = list; \
-	completion_case_sensitive = false; \
-	matches = completion_matches(text, complete_from_list); \
-} while (0)
-
-#define COMPLETE_WITH_CONST(string) \
-do { \
-	completion_charp = string; \
-	completion_case_sensitive = false; \
-	matches = completion_matches(text, complete_from_const); \
-} while (0)
-
-#define COMPLETE_WITH_ATTR(relation, addon) \
-do { \
-	char   *_completion_schema; \
-	char   *_completion_table; \
-\
-	_completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-								false, false, pset.encoding); \
-	if (_completion_table == NULL) \
-	{ \
-		completion_charp = Query_for_list_of_attributes  addon; \
-		completion_info_charp = relation; \
-	} \
-	else \
-	{ \
-		completion_charp = Query_for_list_of_attributes_with_schema  addon; \
-		completion_info_charp = _completion_table; \
-		completion_info_charp2 = _completion_schema; \
-	} \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-#define COMPLETE_WITH_FUNCTION_ARG(function) \
-do { \
-	char   *_completion_schema; \
-	char   *_completion_function; \
-\
-	_completion_schema = strtokx(function, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_function = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-								   false, false, pset.encoding); \
-	if (_completion_function == NULL) \
-	{ \
-		completion_charp = Query_for_list_of_arguments; \
-		completion_info_charp = function; \
-	} \
-	else \
-	{ \
-		completion_charp = Query_for_list_of_arguments_with_schema; \
-		completion_info_charp = _completion_function; \
-		completion_info_charp2 = _completion_schema; \
-	} \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-/*
- * These macros simplify use of COMPLETE_WITH_LIST for short, fixed lists.
- * There is no COMPLETE_WITH_LIST1; use COMPLETE_WITH_CONST for that case.
- */
-#define COMPLETE_WITH_LIST2(s1, s2) \
-do { \
-	static const char *const list[] = { s1, s2, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST3(s1, s2, s3) \
-do { \
-	static const char *const list[] = { s1, s2, s3, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST4(s1, s2, s3, s4) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST5(s1, s2, s3, s4, s5) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST6(s1, s2, s3, s4, s5, s6) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST7(s1, s2, s3, s4, s5, s6, s7) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST8(s1, s2, s3, s4, s5, s6, s7, s8) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST9(s1, s2, s3, s4, s5, s6, s7, s8, s9) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST10(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-/*
- * Likewise for COMPLETE_WITH_LIST_CS.
- */
-#define COMPLETE_WITH_LIST_CS2(s1, s2) \
-do { \
-	static const char *const list[] = { s1, s2, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS3(s1, s2, s3) \
-do { \
-	static const char *const list[] = { s1, s2, s3, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS4(s1, s2, s3, s4) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS5(s1, s2, s3, s4, s5) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-/*
  * Assembly instructions for schema queries
  */
 
@@ -962,6 +783,8 @@ static char **get_previous_words(int point, char **buffer, int *nwords);
 
 static char *get_guctype(const char *varname);
 
+static char **psql_completion_internal(const char *text, char **previous_words,
+										   int previous_words_count);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -1117,156 +940,58 @@ psql_completion(const char *text, int start, int end)
 	/* The number of words found on the input line. */
 	int			previous_words_count;
 
-	/*
-	 * For compactness, we use these macros to reference previous_words[].
-	 * Caution: do not access a previous_words[] entry without having checked
-	 * previous_words_count to be sure it's valid.  In most cases below, that
-	 * check is implicit in a TailMatches() or similar macro, but in some
-	 * places we have to check it explicitly.
-	 */
-#define prev_wd   (previous_words[0])
-#define prev2_wd  (previous_words[1])
-#define prev3_wd  (previous_words[2])
-#define prev4_wd  (previous_words[3])
-#define prev5_wd  (previous_words[4])
-#define prev6_wd  (previous_words[5])
-#define prev7_wd  (previous_words[6])
-#define prev8_wd  (previous_words[7])
-#define prev9_wd  (previous_words[8])
-
-	/* Macros for matching the last N words before point, case-insensitively. */
-#define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, prev_wd))
-
-#define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd))
-
-#define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd))
-
-#define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd))
-
-#define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd))
-
-#define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd))
-
-#define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd))
-
-#define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd) && \
-	 word_matches(p8, prev8_wd))
-
-#define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd) && \
-	 word_matches(p8, prev8_wd) && \
-	 word_matches(p9, prev9_wd))
-
-	/* Macros for matching the last N words before point, case-sensitively. */
-#define TailMatchesCS1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches_cs(p1, prev_wd))
-#define TailMatchesCS2(p2, p1) \
-	(previous_words_count >= 2 && \
-	 word_matches_cs(p1, prev_wd) && \
-	 word_matches_cs(p2, prev2_wd))
+	(void) end;					/* "end" is not used */
 
-	/*
-	 * Macros for matching N words beginning at the start of the line,
-	 * case-insensitively.
-	 */
-#define Matches1(p1) \
-	(previous_words_count == 1 && \
-	 TailMatches1(p1))
-#define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
-	 TailMatches2(p1, p2))
-#define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
-	 TailMatches3(p1, p2, p3))
-#define Matches4(p1, p2, p3, p4) \
-	(previous_words_count == 4 && \
-	 TailMatches4(p1, p2, p3, p4))
-#define Matches5(p1, p2, p3, p4, p5) \
-	(previous_words_count == 5 && \
-	 TailMatches5(p1, p2, p3, p4, p5))
-#define Matches6(p1, p2, p3, p4, p5, p6) \
-	(previous_words_count == 6 && \
-	 TailMatches6(p1, p2, p3, p4, p5, p6))
-#define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(previous_words_count == 7 && \
-	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
-#define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(previous_words_count == 8 && \
-	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
-#define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(previous_words_count == 9 && \
-	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
+#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
+	rl_completion_append_character = ' ';
+#endif
+
+	/* Clear a few things. */
+	completion_charp = NULL;
+	completion_charpp = NULL;
+	completion_info_charp = NULL;
+	completion_info_charp2 = NULL;
 
 	/*
-	 * Macros for matching N words at the start of the line, regardless of
-	 * what is after them, case-insensitively.
+	 * Scan the input line to extract the words before our current position.
+	 * According to those we'll make some smart decisions on what the user is
+	 * probably intending to type.
 	 */
-#define HeadMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]))
+	previous_words = get_previous_words(start,
+										&words_buffer,
+										&previous_words_count);
+
+	matches  = psql_completion_internal(text, previous_words,
+										previous_words_count);
 
-#define HeadMatches2(p1, p2) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]))
+	/* free storage */
+	free(previous_words);
+	free(words_buffer);
 
-#define HeadMatches3(p1, p2, p3) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]) && \
-	 word_matches(p3, previous_words[previous_words_count - 3]))
+	if (matches != NULL)
+		return matches;
+
+	/*
+	 * If we still don't have anything to match we have to fabricate some sort
+	 * of default list. If we were to just return NULL, readline automatically
+	 * attempts filename completion, and that's usually no good.
+	 */
+#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
+	rl_completion_append_character = '\0';
+#endif
+	COMPLETE_WITH_CONST("");		/* This returns matches */
+}
 
+/*
+ * The completion function.
+ *
+ * Makes completion list.
+ * Note: COMPLETE_WITH_* macros immediately return to the caller.
+ */
+static char **
+psql_completion_internal(const char *text, char **previous_words,
+						 int previous_words_count)
+{
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
@@ -1298,90 +1023,80 @@ psql_completion(const char *text, int start, int end)
 		"\\timing", "\\unset", "\\x", "\\w", "\\watch", "\\z", "\\!", NULL
 	};
 
-	(void) end;					/* "end" is not used */
-
-#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
-	rl_completion_append_character = ' ';
-#endif
-
-	/* Clear a few things. */
-	completion_charp = NULL;
-	completion_charpp = NULL;
-	completion_info_charp = NULL;
-	completion_info_charp2 = NULL;
-
-	/*
-	 * Scan the input line to extract the words before our current position.
-	 * According to those we'll make some smart decisions on what the user is
-	 * probably intending to type.
-	 */
-	previous_words = get_previous_words(start,
-										&words_buffer,
-										&previous_words_count);
-
 	/* If current word is a backslash command, offer completions for that */
 	if (text[0] == '\\')
 		COMPLETE_WITH_LIST_CS(backslash_commands);
 
 	/* If current word is a variable interpolation, handle that case */
-	else if (text[0] == ':' && text[1] != ':')
+	if (text[0] == ':' && text[1] != ':')
 	{
 		if (text[1] == '\'')
-			matches = complete_from_variables(text, ":'", "'", true);
-		else if (text[1] == '"')
-			matches = complete_from_variables(text, ":\"", "\"", true);
-		else
-			matches = complete_from_variables(text, ":", "", true);
+			return complete_from_variables(text, ":'", "'", true);
+		if (text[1] == '"')
+			return complete_from_variables(text, ":\"", "\"", true);
+
+		return complete_from_variables(text, ":", "", true);
 	}
 
 	/* If no previous word, suggest one of the basic sql commands */
-	else if (previous_words_count == 0)
+	if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
 /* CREATE */
 	/* complete with something you can create */
-	else if (TailMatches1("CREATE"))
-		matches = completion_matches(text, create_command_generator);
+	if (Matches1("CREATE"))
+		return completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
-	else if (Matches1("DROP"))
-		matches = completion_matches(text, drop_command_generator);
+	if (Matches1("DROP"))
+		return completion_matches(text, drop_command_generator);
 
 /* ALTER */
 
 	/* ALTER TABLE */
-	else if (Matches2("ALTER", "TABLE"))
+	if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
 	/* ALTER something */
-	else if (Matches1("ALTER"))
+	if (Matches1("ALTER"))
 	{
 		static const char *const list_ALTER[] =
-		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
-			"EVENT TRIGGER", "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
-			"GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW", "OPERATOR",
-			"POLICY", "ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM", "TABLE",
-			"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
-		"USER", "USER MAPPING FOR", "VIEW", NULL};
+		{"AGGREGATE",
+		 "COLLATION", "CONVERSION",
+		 "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
+		 "EVENT TRIGGER", "EXTENSION",
+		 "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
+		 "GROUP",
+		 "INDEX",
+		 "LANGUAGE", "LARGE OBJECT",
+		 "MATERIALIZED VIEW", 
+		 "OPERATOR",
+		 "POLICY",
+		 "ROLE", "RULE",
+		 "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM",
+		 "TABLE", "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
+		 "USER", "USER MAPPING FOR",
+		 "VIEW",
+		 NULL};
 
 		COMPLETE_WITH_LIST(list_ALTER);
 	}
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx */
-	else if (TailMatches4("ALL", "IN", "TABLESPACE", MatchAny))
+	if (TailMatches4("ALL", "IN", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("SET TABLESPACE", "OWNED BY");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY */
-	else if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
+	if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY xxx */
-	else if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
+	if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("SET TABLESPACE");
 	/* ALTER AGGREGATE,FUNCTION <name> */
-	else if (Matches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
+	if (Matches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION <name> (...) */
-	else if (Matches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
+	if (Matches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
 	{
 		if (ends_with(prev_wd, ')'))
 			COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
@@ -1390,63 +1105,63 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER SCHEMA <name> */
-	else if (Matches3("ALTER", "SCHEMA", MatchAny))
+	if (Matches3("ALTER", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER TO", "RENAME TO");
 
 	/* ALTER COLLATION <name> */
-	else if (Matches3("ALTER", "COLLATION", MatchAny))
+	if (Matches3("ALTER", "COLLATION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER CONVERSION <name> */
-	else if (Matches3("ALTER", "CONVERSION", MatchAny))
+	if (Matches3("ALTER", "CONVERSION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER DATABASE <name> */
-	else if (Matches3("ALTER", "DATABASE", MatchAny))
+	if (Matches3("ALTER", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST7("RESET", "SET", "OWNER TO", "RENAME TO",
 							"IS_TEMPLATE", "ALLOW_CONNECTIONS",
 							"CONNECTION LIMIT");
 
 	/* ALTER EVENT TRIGGER */
-	else if (Matches3("ALTER", "EVENT", "TRIGGER"))
+	if (Matches3("ALTER", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
+	if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST4("DISABLE", "ENABLE", "OWNER TO", "RENAME TO");
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
+	if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST2("REPLICA", "ALWAYS");
 
 	/* ALTER EXTENSION <name> */
-	else if (Matches3("ALTER", "EXTENSION", MatchAny))
+	if (Matches3("ALTER", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST4("ADD", "DROP", "UPDATE", "SET SCHEMA");
 
 	/* ALTER EXTENSION <name> UPDATE */
-	else if (Matches4("ALTER", "EXTENSION", MatchAny, "UPDATE"))
+	if (Matches4("ALTER", "EXTENSION", MatchAny, "UPDATE"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions_with_TO);
 	}
 
 	/* ALTER EXTENSION <name> UPDATE TO */
-	else if (Matches5("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
+	if (Matches5("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
 	}
 
 	/* ALTER FOREIGN */
-	else if (Matches2("ALTER", "FOREIGN"))
+	if (Matches2("ALTER", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
 	/* ALTER FOREIGN TABLE <name> */
-	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
+	if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
 		{"ADD", "ALTER", "DISABLE TRIGGER", "DROP", "ENABLE", "INHERIT",
@@ -1457,42 +1172,42 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER INDEX */
-	else if (Matches2("ALTER", "INDEX"))
+	if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 	/* ALTER INDEX <name> */
-	else if (Matches3("ALTER", "INDEX", MatchAny))
+	if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
 	/* ALTER INDEX <name> SET */
-	else if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
+	if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
 		COMPLETE_WITH_LIST2("(", "TABLESPACE");
 	/* ALTER INDEX <name> RESET */
-	else if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
+	if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER INDEX <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
+	if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
 		COMPLETE_WITH_LIST3("fillfactor", "fastupdate",
 							"gin_pending_list_limit");
-	else if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
+	if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST3("fillfactor =", "fastupdate =",
 							"gin_pending_list_limit =");
 
 	/* ALTER LANGUAGE <name> */
-	else if (Matches3("ALTER", "LANGUAGE", MatchAny))
+	if (Matches3("ALTER", "LANGUAGE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER_TO", "RENAME TO");
 
 	/* ALTER LARGE OBJECT <oid> */
-	else if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
+	if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
 		COMPLETE_WITH_CONST("OWNER TO");
 
 	/* ALTER MATERIALIZED VIEW */
-	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
+	if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
 	/* ALTER USER,ROLE <name> */
-	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
-			 !TailMatches2("USER", "MAPPING"))
+	if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
+		!TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_ALTERUSER[] =
 		{"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -1506,7 +1221,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER USER,ROLE <name> WITH */
-	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
+	if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_ALTERUSER_WITH[] =
@@ -1521,43 +1236,43 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
-	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	if (Matches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* ALTER DEFAULT PRIVILEGES */
-	else if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
+	if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
 		COMPLETE_WITH_LIST3("FOR ROLE", "FOR USER", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR */
-	else if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
+	if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
 		COMPLETE_WITH_LIST2("ROLE", "USER");
 	/* ALTER DEFAULT PRIVILEGES { FOR ROLE ... | IN SCHEMA ... } */
-	else if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER", MatchAny) ||
+	if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER", MatchAny) ||
 		Matches6("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("GRANT", "REVOKE");
 	/* ALTER DOMAIN <name> */
-	else if (Matches3("ALTER", "DOMAIN", MatchAny))
+	if (Matches3("ALTER", "DOMAIN", MatchAny))
 		COMPLETE_WITH_LIST6("ADD", "DROP", "OWNER TO", "RENAME", "SET",
 							"VALIDATE CONSTRAINT");
 	/* ALTER DOMAIN <sth> DROP */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
 	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
 	}
 	/* ALTER DOMAIN <sth> RENAME */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("CONSTRAINT", "TO");
 	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
-	else if (Matches6("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
+	if (Matches6("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER DOMAIN <sth> SET */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	/* ALTER SEQUENCE <name> */
-	else if (Matches3("ALTER", "SEQUENCE", MatchAny))
+	if (Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1566,74 +1281,74 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
 	/* ALTER SEQUENCE <name> NO */
-	else if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
+	if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 	/* ALTER SERVER <name> */
-	else if (Matches3("ALTER", "SERVER", MatchAny))
+	if (Matches3("ALTER", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST4("VERSION", "OPTIONS", "OWNER TO", "RENAME TO");
 	/* ALTER SERVER <name> VERSION <version> */
-	else if (Matches5("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
+	if (Matches5("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
 		COMPLETE_WITH_CONST("OPTIONS");
 	/* ALTER SYSTEM SET, RESET, RESET ALL */
-	else if (Matches2("ALTER", "SYSTEM"))
+	if (Matches2("ALTER", "SYSTEM"))
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
-	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
+	if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
 	/* ALTER VIEW <name> */
-	else if (Matches3("ALTER", "VIEW", MatchAny))
+	if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
-	else if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
 	/* ALTER POLICY <name> */
-	else if (Matches2("ALTER", "POLICY"))
+	if (Matches2("ALTER", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* ALTER POLICY <name> ON */
-	else if (Matches3("ALTER", "POLICY", MatchAny))
+	if (Matches3("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
-	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
+	if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 	/* ALTER POLICY <name> ON <table> - show options */
-	else if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING (", "WITH CHECK (");
 	/* ALTER POLICY <name> ON <table> TO <role> */
-	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* ALTER POLICY <name> ON <table> USING ( */
-	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
-	else if (Matches7("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
+	if (Matches7("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
 		COMPLETE_WITH_CONST("(");
 
 	/* ALTER RULE <name>, add ON */
-	else if (Matches3("ALTER", "RULE", MatchAny))
+	if (Matches3("ALTER", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
-	else if (Matches4("ALTER", "RULE", MatchAny, "ON"))
+	if (Matches4("ALTER", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
 
 	/* ALTER RULE <name> ON <name> */
-	else if (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/* ALTER TRIGGER <name>, add ON */
-	else if (Matches3("ALTER", "TRIGGER", MatchAny))
+	if (Matches3("ALTER", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
-	else if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
+	if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
@@ -1642,17 +1357,17 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
-	else if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
+	if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER TRIGGER <name> ON <name> */
-	else if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/*
 	 * If we detect ALTER TABLE <name>, suggest sub commands
 	 */
-	else if (Matches3("ALTER", "TABLE", MatchAny))
+	if (Matches3("ALTER", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER2[] =
 		{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
@@ -1662,114 +1377,114 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
 	/* ALTER TABLE xxx ENABLE */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST5("ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE",
 							"TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
 		COMPLETE_WITH_LIST2("RULE", "TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
 	/* ALTER TABLE xxx INHERIT */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx NO INHERIT */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx DISABLE */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
 		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
 
 	/* ALTER TABLE xxx ALTER */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
 
 	/* ALTER TABLE xxx RENAME */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/* ALTER TABLE xxx RENAME yyy */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
+	if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
-	else if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
+	if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* If we have ALTER TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
+	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
 			 Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
 		COMPLETE_WITH_LIST4("TYPE", "SET", "RESET", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
 			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
 		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
+	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
 		 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
+	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
 	Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
 		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
 			 Matches8("ALTER", "TABLE", MatchAny, "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("DEFAULT", "NOT NULL");
-	else if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
 	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
 		COMPLETE_WITH_LIST7("(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED",
 							"WITH", "WITHOUT");
 
@@ -1777,19 +1492,19 @@ psql_completion(const char *text, int start, int end)
 	 * If we have ALTER TABLE <sth> SET TABLESPACE provide a list of
 	 * tablespaces
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	/* If we have ALTER TABLE <sth> SET WITH provide OIDS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
 		COMPLETE_WITH_CONST("OIDS");
 	/* If we have ALTER TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
 		COMPLETE_WITH_LIST2("CLUSTER", "OIDS");
 	/* ALTER TABLE <foo> RESET */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLE <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
 	{
 		static const char *const list_TABLEOPTIONS[] =
 		{
@@ -1827,103 +1542,103 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
-	else if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
+	if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
 		COMPLETE_WITH_LIST4("FULL", "NOTHING", "DEFAULT", "USING");
-	else if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
 		COMPLETE_WITH_CONST("IDENTITY");
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
-	else if (Matches3("ALTER", "TABLESPACE", MatchAny))
+	if (Matches3("ALTER", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "OWNER TO", "SET", "RESET");
 	/* ALTER TABLESPACE <foo> SET|RESET */
-	else if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
+	if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
+	if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
 		COMPLETE_WITH_LIST3("seq_page_cost", "random_page_cost",
 							"effective_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
-	else if (Matches3("ALTER", "TEXT", "SEARCH"))
+	if (Matches3("ALTER", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
 		COMPLETE_WITH_LIST2("RENAME TO", "SET SCHEMA");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING",
 							"DROP MAPPING FOR",
 							"OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* complete ALTER TYPE <foo> with actions */
-	else if (Matches3("ALTER", "TYPE", MatchAny))
+	if (Matches3("ALTER", "TYPE", MatchAny))
 		COMPLETE_WITH_LIST7("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE",
 							"DROP ATTRIBUTE",
 							"OWNER TO", "RENAME", "SET SCHEMA");
 	/* complete ALTER TYPE <foo> ADD with actions */
-	else if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
+	if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
 	/* ALTER TYPE <foo> RENAME	*/
-	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "TO");
 	/* ALTER TYPE xxx RENAME ATTRIBUTE yyy */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE", MatchAny))
+	if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/*
 	 * If we have ALTER TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list
 	 * of attributes
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
+	if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
+	if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* complete ALTER GROUP <foo> */
-	else if (Matches3("ALTER", "GROUP", MatchAny))
+	if (Matches3("ALTER", "GROUP", MatchAny))
 		COMPLETE_WITH_LIST3("ADD USER", "DROP USER", "RENAME TO");
 	/* complete ALTER GROUP <foo> ADD|DROP with USER */
-	else if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
+	if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
 		COMPLETE_WITH_CONST("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
-	else if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
+	if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* BEGIN */
-	else if (Matches1("BEGIN"))
+	if (Matches1("BEGIN"))
 		COMPLETE_WITH_LIST6("WORK", "TRANSACTION", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
 /* END, ABORT */
-	else if (Matches1("END|ABORT"))
+	if (Matches1("END|ABORT"))
 		COMPLETE_WITH_LIST2("WORK", "TRANSACTION");
 /* COMMIT */
-	else if (Matches1("COMMIT"))
+	if (Matches1("COMMIT"))
 		COMPLETE_WITH_LIST3("WORK", "TRANSACTION", "PREPARED");
 /* RELEASE SAVEPOINT */
-	else if (Matches1("RELEASE"))
+	if (Matches1("RELEASE"))
 		COMPLETE_WITH_CONST("SAVEPOINT");
 /* ROLLBACK */
-	else if (Matches1("ROLLBACK"))
+	if (Matches1("ROLLBACK"))
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
-	else if (Matches1("CLUSTER"))
+	if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
-	else if (Matches2("CLUSTER", "VERBOSE"))
+	if (Matches2("CLUSTER", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 	/* If we have CLUSTER <sth>, then add "USING" */
-	else if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
+	if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	else if (Matches3("CLUSTER", "VERBOSE", MatchAny))
+	if (Matches3("CLUSTER", "VERBOSE", MatchAny))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER <sth> USING, then add the index as well */
-	else if (Matches3("CLUSTER", MatchAny, "USING") ||
+	if (Matches3("CLUSTER", MatchAny, "USING") ||
 			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
@@ -1931,9 +1646,9 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* COMMENT */
-	else if (Matches1("COMMENT"))
+	if (Matches1("COMMENT"))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches2("COMMENT", "ON"))
+	if (Matches2("COMMENT", "ON"))
 	{
 		static const char *const list_COMMENT[] =
 		{"ACCESS METHOD", "CAST", "COLLATION", "CONVERSION", "DATABASE",
@@ -1946,26 +1661,26 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
-	else if (Matches4("COMMENT", "ON", "ACCESS", "METHOD"))
+	if (Matches4("COMMENT", "ON", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (Matches3("COMMENT", "ON", "FOREIGN"))
+	if (Matches3("COMMENT", "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
-	else if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
+	if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches3("COMMENT", "ON", "CONSTRAINT"))
+	if (Matches3("COMMENT", "ON", "CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
-	else if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
+	if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
+	if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
 	}
-	else if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
+	if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
+	if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	else if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
+	if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
 		Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
 			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
 		COMPLETE_WITH_CONST("IS");
@@ -1976,97 +1691,97 @@ psql_completion(const char *text, int start, int end)
 	 * If we have COPY, offer list of tables or "(" (Also cover the analogous
 	 * backslash command).
 	 */
-	else if (Matches1("COPY|\\copy"))
+	if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION ALL SELECT '('");
 	/* If we have COPY BINARY, complete with list of tables */
-	else if (Matches2("COPY", "BINARY"))
+	if (Matches2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* If we have COPY (, complete it with legal commands */
-	else if (Matches2("COPY|\\copy", "("))
+	if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
 	/* If we have COPY [BINARY] <sth>, complete it with "TO" or "FROM" */
-	else if (Matches2("COPY|\\copy", MatchAny) ||
+	if (Matches2("COPY|\\copy", MatchAny) ||
 			 Matches3("COPY", "BINARY", MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "TO");
 	/* If we have COPY [BINARY] <sth> FROM|TO, complete with filename */
-	else if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
+	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
 			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
 	{
 		completion_charp = "";
-		matches = completion_matches(text, complete_from_files);
+		return completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename */
-	else if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
+	if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
 			 Matches5("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
 		COMPLETE_WITH_LIST6("BINARY", "OIDS", "DELIMITER", "NULL", "CSV",
 							"ENCODING");
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename CSV */
-	else if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
+	if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
 			 Matches6("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny, "CSV"))
 		COMPLETE_WITH_LIST5("HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE",
 							"FORCE NOT NULL");
 
 	/* CREATE ACCESS METHOD */
 	/* Complete "CREATE ACCESS METHOD <name>" */
-	else if (Matches4("CREATE", "ACCESS", "METHOD", MatchAny))
+	if (Matches4("CREATE", "ACCESS", "METHOD", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE" */
-	else if (Matches5("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
+	if (Matches5("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
 		COMPLETE_WITH_CONST("INDEX");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE <type>" */
-	else if (Matches6("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
+	if (Matches6("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
 		COMPLETE_WITH_CONST("HANDLER");
 
 	/* CREATE DATABASE */
-	else if (Matches3("CREATE", "DATABASE", MatchAny))
+	if (Matches3("CREATE", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST9("OWNER", "TEMPLATE", "ENCODING", "TABLESPACE",
 							"IS_TEMPLATE",
 							"ALLOW_CONNECTIONS", "CONNECTION LIMIT",
 							"LC_COLLATE", "LC_CTYPE");
 
-	else if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
+	if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
-	else if (Matches2("CREATE", "EXTENSION"))
+	if (Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
-	else if (Matches3("CREATE", "EXTENSION", MatchAny))
+	if (Matches3("CREATE", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
 	/* CREATE EXTENSION <name> VERSION */
-	else if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
+	if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
 	}
 
 	/* CREATE FOREIGN */
-	else if (Matches2("CREATE", "FOREIGN"))
+	if (Matches2("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* CREATE FOREIGN DATA WRAPPER */
-	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	else if (TailMatches2("CREATE", "UNIQUE"))
+	if (TailMatches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
 	/*
 	 * If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY", and
 	 * existing indexes
 	 */
-	else if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	if (TailMatches2("CREATE|UNIQUE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
 								   " UNION SELECT 'CONCURRENTLY'");
 	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	else if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
+	if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
 			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
@@ -2074,11 +1789,11 @@ psql_completion(const char *text, int start, int end)
 	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
 	 * indexes
 	 */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+	if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'");
 	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
+	if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
 			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
@@ -2086,117 +1801,117 @@ psql_completion(const char *text, int start, int end)
 	 * Complete INDEX <name> ON <table> with a list of table columns (which
 	 * should really be in parens)
 	 */
-	else if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-			 TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+	if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
+		TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-			 TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
+	if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
+		TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* same if you put in USING */
-	else if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
+	if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	else if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
+	if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
+	if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
 			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
 			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-	else if (Matches3("CREATE", "POLICY", MatchAny))
+	if (Matches3("CREATE", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
-	else if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
+	if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
-	else if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
+	if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING (", "WITH CHECK (");
 	/* CREATE POLICY <name> ON <table> FOR ALL|SELECT|INSERT|UPDATE|DELETE */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
 		COMPLETE_WITH_LIST5("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
 	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
 		COMPLETE_WITH_LIST2("TO", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> FOR SELECT|DELETE TO|USING" */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
 		COMPLETE_WITH_LIST2("TO", "USING (");
 	/* CREATE POLICY <name> ON <table> FOR ALL|UPDATE TO|USING|WITH CHECK */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
 		COMPLETE_WITH_LIST3("TO", "USING (", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS ON" */
-	else if (Matches3("CREATE", "RULE", MatchAny))
+	if (Matches3("CREATE", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("AS ON");
 	/* Complete "CREATE RULE <sth> AS" with "ON" */
-	else if (Matches4("CREATE", "RULE", MatchAny, "AS"))
+	if (Matches4("CREATE", "RULE", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE RULE <sth> AS ON" with SELECT|UPDATE|INSERT|DELETE */
-	else if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
+	if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
 	/* Complete "AS ON SELECT|UPDATE|INSERT|DELETE" with a "TO" */
-	else if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
+	if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
-	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
+	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	else if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
+	if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
 			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	else if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
+	if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
 		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
-	else if (Matches3("CREATE", "SERVER", MatchAny))
+	if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
 /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	else if (TailMatches2("CREATE", "UNLOGGED"))
+	if (TailMatches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
 /* CREATE TABLESPACE */
-	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
+	if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
 	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
-	else if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
+	if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
 		COMPLETE_WITH_CONST("LOCATION");
 
 /* CREATE TEXT SEARCH */
-	else if (Matches3("CREATE", "TEXT", "SEARCH"))
+	if (Matches3("CREATE", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	else if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	if (TailMatches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+	if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
 	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
@@ -2204,17 +1919,17 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
 	 * tables
 	 */
-	else if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
+	if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
-	else if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
+	if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
 
 /* CREATE ROLE,USER,GROUP <name> */
-	else if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
+	if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_CREATEROLE[] =
@@ -2229,7 +1944,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE ROLE,USER,GROUP <name> WITH */
-	else if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
+	if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_CREATEROLE_WITH[] =
@@ -2247,70 +1962,70 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
 	 * PASSWORD
 	 */
-	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
-	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
+	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE VIEW <name> with AS */
-	else if (TailMatches3("CREATE", "VIEW", MatchAny))
+	if (TailMatches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
+	if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches2("CREATE", "MATERIALIZED"))
+	if (Matches2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE EVENT TRIGGER */
-	else if (Matches2("CREATE", "EVENT"))
+	if (Matches2("CREATE", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	/* Complete CREATE EVENT TRIGGER <name> with ON */
-	else if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
+	if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
-	else if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
+	if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_LIST3("ddl_command_start", "ddl_command_end", "sql_drop");
 
 /* DECLARE */
-	else if (Matches2("DECLARE", MatchAny))
+	if (Matches2("DECLARE", MatchAny))
 		COMPLETE_WITH_LIST5("BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL",
 							"CURSOR");
-	else if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
+	if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
 		COMPLETE_WITH_LIST3("WITH HOLD", "WITHOUT HOLD", "FOR");
 
 /* DELETE --- can be inside EXPLAIN, RULE, etc */
 	/* ... despite which, only complete DELETE with FROM at start of line */
-	else if (Matches1("DELETE"))
+	if (Matches1("DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
-	else if (TailMatches2("DELETE", "FROM"))
+	if (TailMatches2("DELETE", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete DELETE FROM <table> */
-	else if (TailMatches3("DELETE", "FROM", MatchAny))
+	if (TailMatches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
 	/* XXX: implement tab completion for DELETE ... USING */
 
 /* DISCARD */
-	else if (Matches1("DISCARD"))
+	if (Matches1("DISCARD"))
 		COMPLETE_WITH_LIST4("ALL", "PLANS", "SEQUENCES", "TEMP");
 
 /* DO */
-	else if (Matches1("DO"))
+	if (Matches1("DO"))
 		COMPLETE_WITH_CONST("LANGUAGE");
 
 /* DROP */
 	/* Complete DROP object with CASCADE / RESTRICT */
-	else if (Matches3("DROP",
+	if (Matches3("DROP",
 					  "COLLATION|CONVERSION|DOMAIN|EXTENSION|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW",
 					  MatchAny) ||
 			 Matches4("DROP", "ACCESS", "METHOD", MatchAny) ||
@@ -2323,88 +2038,88 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
+	if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
-	else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
+	if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
-	else if (Matches2("DROP", "FOREIGN"))
+	if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* DROP INDEX */
-	else if (Matches2("DROP", "INDEX"))
+	if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
+	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches3("DROP", "INDEX", MatchAny))
+	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	else if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
+	if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
-	else if (Matches2("DROP", "MATERIALIZED"))
+	if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
 	/* DROP OWNED BY */
-	else if (Matches2("DROP", "OWNED"))
+	if (Matches2("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (Matches3("DROP", "OWNED", "BY"))
+	if (Matches3("DROP", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
-	else if (Matches3("DROP", "TEXT", "SEARCH"))
+	if (Matches3("DROP", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (Matches3("DROP", "TRIGGER", MatchAny))
+	if (Matches3("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
+	if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
 	}
-	else if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
+	if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP ACCESS METHOD */
-	else if (Matches2("DROP", "ACCESS"))
+	if (Matches2("DROP", "ACCESS"))
 		COMPLETE_WITH_CONST("METHOD");
-	else if (Matches3("DROP", "ACCESS", "METHOD"))
+	if (Matches3("DROP", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 
 	/* DROP EVENT TRIGGER */
-	else if (Matches2("DROP", "EVENT"))
+	if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
-	else if (Matches3("DROP", "EVENT", "TRIGGER"))
+	if (Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* DROP POLICY <name>  */
-	else if (Matches2("DROP", "POLICY"))
+	if (Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* DROP POLICY <name> ON */
-	else if (Matches3("DROP", "POLICY", MatchAny))
+	if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* DROP POLICY <name> ON <table> */
-	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
+	if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 
 	/* DROP RULE */
-	else if (Matches3("DROP", "RULE", MatchAny))
+	if (Matches3("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
+	if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
-	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
+	if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 /* EXECUTE */
-	else if (Matches1("EXECUTE"))
+	if (Matches1("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
 
 /* EXPLAIN */
@@ -2412,22 +2127,22 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
-	else if (Matches1("EXPLAIN"))
+	if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"ANALYZE", "VERBOSE");
-	else if (Matches2("EXPLAIN", "ANALYZE"))
+	if (Matches2("EXPLAIN", "ANALYZE"))
 		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"VERBOSE");
-	else if (Matches2("EXPLAIN", "VERBOSE") ||
+	if (Matches2("EXPLAIN", "VERBOSE") ||
 			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
 		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-	else if (Matches1("FETCH|MOVE"))
+	if (Matches1("FETCH|MOVE"))
 		COMPLETE_WITH_LIST4("ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE");
 	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
-	else if (Matches2("FETCH|MOVE", MatchAny))
+	if (Matches2("FETCH|MOVE", MatchAny))
 		COMPLETE_WITH_LIST3("ALL", "NEXT", "PRIOR");
 
 	/*
@@ -2435,31 +2150,31 @@ psql_completion(const char *text, int start, int end)
 	 * but we may as well tab-complete both: perhaps some users prefer one
 	 * variant or the other.
 	 */
-	else if (Matches3("FETCH|MOVE", MatchAny, MatchAny))
+	if (Matches3("FETCH|MOVE", MatchAny, MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "IN");
 
 /* FOREIGN DATA WRAPPER */
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
-	else if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
+	if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
 			 !TailMatches4("CREATE", MatchAny, MatchAny, MatchAny))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 	/* applies in CREATE SERVER */
-	else if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
+	if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
 			 HeadMatches2("CREATE", "SERVER"))
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /* FOREIGN TABLE */
-	else if (TailMatches2("FOREIGN", "TABLE") &&
+	if (TailMatches2("FOREIGN", "TABLE") &&
 			 !TailMatches3("CREATE", MatchAny, MatchAny))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* FOREIGN SERVER */
-	else if (TailMatches2("FOREIGN", "SERVER"))
+	if (TailMatches2("FOREIGN", "SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
 
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	else if (TailMatches1("GRANT|REVOKE"))
+	if (TailMatches1("GRANT|REVOKE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'SELECT'"
 							" UNION SELECT 'INSERT'"
@@ -2479,11 +2194,11 @@ psql_completion(const char *text, int start, int end)
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	else if (TailMatches2("GRANT|REVOKE", MatchAny))
+	if (TailMatches2("GRANT|REVOKE", MatchAny))
 	{
 		if (TailMatches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
-		else if (TailMatches2("GRANT", MatchAny))
+		if (TailMatches2("GRANT", MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2500,7 +2215,7 @@ psql_completion(const char *text, int start, int end)
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
 	 */
-	else if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
+	if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
 								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
 								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
@@ -2518,11 +2233,11 @@ psql_completion(const char *text, int start, int end)
 								   " UNION SELECT 'TABLESPACE'"
 								   " UNION SELECT 'TYPE'");
 
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
 							"TABLES IN SCHEMA");
 
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/*
@@ -2531,27 +2246,27 @@ psql_completion(const char *text, int start, int end)
 	 *
 	 * Complete "GRANT/REVOKE * ON *" with "TO/FROM".
 	 */
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
 		if (TailMatches1("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-		else if (TailMatches1("DOMAIN"))
+		if (TailMatches1("DOMAIN"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-		else if (TailMatches1("FUNCTION"))
+		if (TailMatches1("FUNCTION"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-		else if (TailMatches1("LANGUAGE"))
+		if (TailMatches1("LANGUAGE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-		else if (TailMatches1("SCHEMA"))
+		if (TailMatches1("SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-		else if (TailMatches1("SEQUENCE"))
+		if (TailMatches1("SEQUENCE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-		else if (TailMatches1("TABLE"))
+		if (TailMatches1("TABLE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-		else if (TailMatches1("TABLESPACE"))
+		if (TailMatches1("TABLESPACE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-		else if (TailMatches1("TYPE"))
+		if (TailMatches1("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-		else if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
+		if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2561,18 +2276,18 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "GRANT/REVOKE ... TO/FROM" with username, PUBLIC,
 	 * CURRENT_USER, or SESSION_USER.
 	 */
-	else if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
+	if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
 			 (HeadMatches1("REVOKE") && TailMatches1("FROM")))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 
 	/* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */
-	else if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
+	if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (HeadMatches1("REVOKE") && TailMatches3("ON", MatchAny, MatchAny))
+	if (HeadMatches1("REVOKE") && TailMatches3("ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("FROM");
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	else if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
+	if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
 	{
 		if (TailMatches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2581,7 +2296,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	else if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 	{
 		if (TailMatches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2590,7 +2305,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	else if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
+	if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
 	{
 		if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2599,62 +2314,62 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* GROUP BY */
-	else if (TailMatches3("FROM", MatchAny, "GROUP"))
+	if (TailMatches3("FROM", MatchAny, "GROUP"))
 		COMPLETE_WITH_CONST("BY");
 
 /* IMPORT FOREIGN SCHEMA */
-	else if (Matches1("IMPORT"))
+	if (Matches1("IMPORT"))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
-	else if (Matches2("IMPORT", "FOREIGN"))
+	if (Matches2("IMPORT", "FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT --- can be inside EXPLAIN, RULE, etc */
 	/* Complete INSERT with "INTO" */
-	else if (TailMatches1("INSERT"))
+	if (TailMatches1("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
-	else if (TailMatches2("INSERT", "INTO"))
+	if (TailMatches2("INSERT", "INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete "INSERT INTO <table> (" with attribute names */
-	else if (TailMatches4("INSERT", "INTO", MatchAny, "("))
+	if (TailMatches4("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES"
 	 */
-	else if (TailMatches3("INSERT", "INTO", MatchAny))
+	if (TailMatches3("INSERT", "INTO", MatchAny))
 		COMPLETE_WITH_LIST5("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES");
 
 	/*
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE"
 	 */
-	else if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
+	if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
 			 ends_with(prev_wd, ')'))
 		COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES");
 
 	/* Insert an open parenthesis after "VALUES" */
-	else if (TailMatches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
+	if (TailMatches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
 		COMPLETE_WITH_CONST("(");
 
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
-	else if (Matches1("LOCK"))
+	if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION SELECT 'TABLE'");
-	else if (Matches2("LOCK", "TABLE"))
+	if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* For the following, handle the case of a single table only for now */
 
 	/* Complete LOCK [TABLE] <table> with "IN" */
-	else if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
+	if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
 			 Matches3("LOCK", "TABLE", MatchAny))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	else if (Matches3("LOCK", MatchAny, "IN") ||
+	if (Matches3("LOCK", MatchAny, "IN") ||
 			 Matches4("LOCK", "TABLE", MatchAny, "IN"))
 		COMPLETE_WITH_LIST8("ACCESS SHARE MODE",
 							"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
@@ -2663,25 +2378,25 @@ psql_completion(const char *text, int start, int end)
 							"EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE");
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
-	else if (TailMatches1("NOTIFY"))
+	if (TailMatches1("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
 
 /* OPTIONS */
-	else if (TailMatches1("OPTIONS"))
+	if (TailMatches1("OPTIONS"))
 		COMPLETE_WITH_CONST("(");
 
 /* OWNER TO  - complete with available roles */
-	else if (TailMatches2("OWNER", "TO"))
+	if (TailMatches2("OWNER", "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* ORDER BY */
-	else if (TailMatches3("FROM", MatchAny, "ORDER"))
+	if (TailMatches3("FROM", MatchAny, "ORDER"))
 		COMPLETE_WITH_CONST("BY");
-	else if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
+	if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 /* PREPARE xx AS */
-	else if (Matches3("PREPARE", MatchAny, "AS"))
+	if (Matches3("PREPARE", MatchAny, "AS"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
 
 /*
@@ -2690,60 +2405,60 @@ psql_completion(const char *text, int start, int end)
  */
 
 /* REASSIGN OWNED BY xxx TO yyy */
-	else if (Matches1("REASSIGN"))
+	if (Matches1("REASSIGN"))
 		COMPLETE_WITH_CONST("OWNED BY");
-	else if (Matches2("REASSIGN", "OWNED"))
+	if (Matches2("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (Matches3("REASSIGN", "OWNED", "BY"))
+	if (Matches3("REASSIGN", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
+	if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
+	if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* REFRESH MATERIALIZED VIEW */
-	else if (Matches1("REFRESH"))
+	if (Matches1("REFRESH"))
 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
-	else if (Matches2("REFRESH", "MATERIALIZED"))
+	if (Matches2("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
+	if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
+	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
+	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
+	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
-	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
+	if (Matches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
-	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
+	if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
-	else if (Matches7("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
+	if (Matches7("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
 
 /* REINDEX */
-	else if (Matches1("REINDEX"))
+	if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
-	else if (Matches2("REINDEX", "TABLE"))
+	if (Matches2("REINDEX", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-	else if (Matches2("REINDEX", "INDEX"))
+	if (Matches2("REINDEX", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches2("REINDEX", "SCHEMA"))
+	if (Matches2("REINDEX", "SCHEMA"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-	else if (Matches2("REINDEX", "SYSTEM|DATABASE"))
+	if (Matches2("REINDEX", "SYSTEM|DATABASE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 
 /* SECURITY LABEL */
-	else if (Matches1("SECURITY"))
+	if (Matches1("SECURITY"))
 		COMPLETE_WITH_CONST("LABEL");
-	else if (Matches2("SECURITY", "LABEL"))
+	if (Matches2("SECURITY", "LABEL"))
 		COMPLETE_WITH_LIST2("ON", "FOR");
-	else if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
+	if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches3("SECURITY", "LABEL", "ON") ||
+	if (Matches3("SECURITY", "LABEL", "ON") ||
 			 Matches5("SECURITY", "LABEL", "FOR", MatchAny, "ON"))
 	{
 		static const char *const list_SECURITY_LABEL[] =
@@ -2754,7 +2469,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_SECURITY_LABEL);
 	}
-	else if (Matches5("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
+	if (Matches5("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("IS");
 
 /* SELECT */
@@ -2762,69 +2477,69 @@ psql_completion(const char *text, int start, int end)
 
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
-	else if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
+	if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
-	else if (Matches1("SHOW"))
+	if (Matches1("SHOW"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
 	/* Complete "SET TRANSACTION" */
-	else if (Matches2("SET", "TRANSACTION"))
+	if (Matches2("SET", "TRANSACTION"))
 		COMPLETE_WITH_LIST5("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	else if (Matches2("BEGIN|START", "TRANSACTION") ||
-			 Matches2("BEGIN", "WORK") ||
-			 Matches1("BEGIN") ||
-		  Matches5("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
+	if (Matches2("BEGIN|START", "TRANSACTION") ||
+		Matches2("BEGIN", "WORK") ||
+		Matches1("BEGIN") ||
+		Matches5("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
 		COMPLETE_WITH_LIST4("ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
-			 Matches2("BEGIN", "NOT") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
+		Matches2("BEGIN", "NOT") ||
+		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
 		COMPLETE_WITH_CONST("DEFERRABLE");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
-			 Matches2("BEGIN", "ISOLATION") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
+		Matches2("BEGIN", "ISOLATION") ||
+		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
 		COMPLETE_WITH_CONST("LEVEL");
-	else if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
-			 Matches3("BEGIN", "ISOLATION", "LEVEL") ||
-			 Matches7("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
+	if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
+		Matches3("BEGIN", "ISOLATION", "LEVEL") ||
+		Matches7("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
 		COMPLETE_WITH_LIST3("READ", "REPEATABLE READ", "SERIALIZABLE");
-	else if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
-			 Matches4("BEGIN", "ISOLATION", "LEVEL", "READ") ||
-			 Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
+	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
+		Matches4("BEGIN", "ISOLATION", "LEVEL", "READ") ||
+		Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
 		COMPLETE_WITH_LIST2("UNCOMMITTED", "COMMITTED");
-	else if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
-			 Matches4("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
-			 Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
+	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
+		Matches4("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
+		Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
 		COMPLETE_WITH_CONST("READ");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
-			 Matches2("BEGIN", "READ") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
+		Matches2("BEGIN", "READ") ||
+		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
-	else if (Matches2("SET", "CONSTRAINTS"))
+	if (Matches2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
-	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
+	if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
-	else if (Matches2("SET", "ROLE"))
+	if (Matches2("SET", "ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
-	else if (Matches2("SET", "SESSION"))
+	if (Matches2("SET", "SESSION"))
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
-	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
+	if (Matches3("SET", "SESSION", "AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
 	/* Complete RESET SESSION with AUTHORIZATION */
-	else if (Matches2("RESET", "SESSION"))
+	if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
 	/* Complete SET <var> with "TO" */
-	else if (Matches2("SET", MatchAny))
+	if (Matches2("SET", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete ALTER DATABASE|FUNCTION|ROLE|USER ... SET <name> */
-	else if (HeadMatches2("ALTER", "DATABASE|FUNCTION|ROLE|USER") &&
+	if (HeadMatches2("ALTER", "DATABASE|FUNCTION|ROLE|USER") &&
 			 TailMatches2("SET", MatchAny))
 		COMPLETE_WITH_LIST2("FROM CURRENT", "TO");
 	/* Suggest possible variable values */
-	else if (TailMatches3("SET", MatchAny, "TO|="))
+	if (TailMatches3("SET", MatchAny, "TO|="))
 	{
 		/* special cased code for individual GUCs */
 		if (TailMatches2("DateStyle", "TO|="))
@@ -2837,112 +2552,117 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST(my_list);
 		}
-		else if (TailMatches2("search_path", "TO|="))
+		if (TailMatches2("search_path", "TO|="))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
 								" AND nspname not like 'pg\\_toast%%' "
 								" AND nspname not like 'pg\\_temp%%' "
 								" UNION SELECT 'DEFAULT' ");
 		else
 		{
-			/* generic, type based, GUC support */
+			/* generic, type based, GUC support, guctype is malloc'ed */
 			char	   *guctype = get_guctype(prev2_wd);
 
 			if (guctype && strcmp(guctype, "enum") == 0)
 			{
 				char		querybuf[1024];
 
+				free(guctype);
 				snprintf(querybuf, sizeof(querybuf), Query_for_enum, prev2_wd);
 				COMPLETE_WITH_QUERY(querybuf);
 			}
-			else if (guctype && strcmp(guctype, "bool") == 0)
+
+			if (guctype && strcmp(guctype, "bool") == 0)
+			{
+				free(guctype);
 				COMPLETE_WITH_LIST9("on", "off", "true", "false", "yes", "no",
 									"1", "0", "DEFAULT");
-			else
-				COMPLETE_WITH_CONST("DEFAULT");
+			}
 
 			if (guctype)
 				free(guctype);
+
+			COMPLETE_WITH_CONST("DEFAULT");
 		}
 	}
 
 /* START TRANSACTION */
-	else if (Matches1("START"))
+	if (Matches1("START"))
 		COMPLETE_WITH_CONST("TRANSACTION");
 
 /* TABLE, but not TABLE embedded in other commands */
-	else if (Matches1("TABLE"))
+	if (Matches1("TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
 /* TABLESAMPLE */
-	else if (TailMatches1("TABLESAMPLE"))
+	if (TailMatches1("TABLESAMPLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods);
-	else if (TailMatches2("TABLESAMPLE", MatchAny))
+	if (TailMatches2("TABLESAMPLE", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
-	else if (Matches1("TRUNCATE"))
+	if (Matches1("TRUNCATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* UNLISTEN */
-	else if (Matches1("UNLISTEN"))
+	if (Matches1("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
 
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
-	else if (TailMatches1("UPDATE"))
+	if (TailMatches1("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete UPDATE <table> with "SET" */
-	else if (TailMatches2("UPDATE", MatchAny))
+	if (TailMatches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
 	/* Complete UPDATE <table> SET with list of attributes */
-	else if (TailMatches3("UPDATE", MatchAny, "SET"))
+	if (TailMatches3("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* UPDATE <table> SET <attr> = */
-	else if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
+	if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
+	if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
-	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
+	if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
 							" UNION SELECT 'PUBLIC'"
 							" UNION SELECT 'USER'");
-	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
+	if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
+	if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("SERVER");
-	else if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
+	if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /*
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
-	else if (Matches1("VACUUM"))
+	if (Matches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'FULL'"
 								   " UNION SELECT 'FREEZE'"
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches2("VACUUM", "FULL|FREEZE"))
+	if (Matches2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
+	if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
+	if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (Matches2("VACUUM", "VERBOSE"))
+	if (Matches2("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (Matches2("VACUUM", "ANALYZE"))
+	if (Matches2("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (HeadMatches1("VACUUM"))
+	if (HeadMatches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 /* WITH [RECURSIVE] */
@@ -2951,114 +2671,114 @@ psql_completion(const char *text, int start, int end)
 	 * Only match when WITH is the first word, as WITH may appear in many
 	 * other contexts.
 	 */
-	else if (Matches1("WITH"))
+	if (Matches1("WITH"))
 		COMPLETE_WITH_CONST("RECURSIVE");
 
 /* ANALYZE */
 	/* Complete with list of tables */
-	else if (Matches1("ANALYZE"))
+	if (Matches1("ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
-	else if (TailMatches2(MatchAny, "WHERE"))
+	if (TailMatches2(MatchAny, "WHERE"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
-	else if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
+	if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* ... JOIN ... */
-	else if (TailMatches1("JOIN"))
+	if (TailMatches1("JOIN"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
-	else if (TailMatchesCS1("\\?"))
+	if (TailMatchesCS1("\\?"))
 		COMPLETE_WITH_LIST_CS3("commands", "options", "variables");
-	else if (TailMatchesCS1("\\connect|\\c"))
+	if (TailMatchesCS1("\\connect|\\c"))
 	{
 		if (!recognized_connection_string(text))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 	}
-	else if (TailMatchesCS2("\\connect|\\c", MatchAny))
+	if (TailMatchesCS2("\\connect|\\c", MatchAny))
 	{
 		if (!recognized_connection_string(prev_wd))
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	}
-	else if (TailMatchesCS1("\\da*"))
+	if (TailMatchesCS1("\\da*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
-	else if (TailMatchesCS1("\\dA*"))
+	if (TailMatchesCS1("\\dA*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (TailMatchesCS1("\\db*"))
+	if (TailMatchesCS1("\\db*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-	else if (TailMatchesCS1("\\dD*"))
+	if (TailMatchesCS1("\\dD*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-	else if (TailMatchesCS1("\\des*"))
+	if (TailMatchesCS1("\\des*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
-	else if (TailMatchesCS1("\\deu*"))
+	if (TailMatchesCS1("\\deu*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (TailMatchesCS1("\\dew*"))
+	if (TailMatchesCS1("\\dew*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
-	else if (TailMatchesCS1("\\df*"))
+	if (TailMatchesCS1("\\df*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 
-	else if (TailMatchesCS1("\\dFd*"))
+	if (TailMatchesCS1("\\dFd*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
-	else if (TailMatchesCS1("\\dFp*"))
+	if (TailMatchesCS1("\\dFp*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
-	else if (TailMatchesCS1("\\dFt*"))
+	if (TailMatchesCS1("\\dFt*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
 	/* must be at end of \dF alternatives: */
-	else if (TailMatchesCS1("\\dF*"))
+	if (TailMatchesCS1("\\dF*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
 
-	else if (TailMatchesCS1("\\di*"))
+	if (TailMatchesCS1("\\di*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (TailMatchesCS1("\\dL*"))
+	if (TailMatchesCS1("\\dL*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-	else if (TailMatchesCS1("\\dn*"))
+	if (TailMatchesCS1("\\dn*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-	else if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
+	if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-	else if (TailMatchesCS1("\\ds*"))
+	if (TailMatchesCS1("\\ds*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-	else if (TailMatchesCS1("\\dt*"))
+	if (TailMatchesCS1("\\dt*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-	else if (TailMatchesCS1("\\dT*"))
+	if (TailMatchesCS1("\\dT*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-	else if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
+	if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (TailMatchesCS1("\\dv*"))
+	if (TailMatchesCS1("\\dv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (TailMatchesCS1("\\dx*"))
+	if (TailMatchesCS1("\\dx*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
-	else if (TailMatchesCS1("\\dm*"))
+	if (TailMatchesCS1("\\dm*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (TailMatchesCS1("\\dE*"))
+	if (TailMatchesCS1("\\dE*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
-	else if (TailMatchesCS1("\\dy*"))
+	if (TailMatchesCS1("\\dy*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* must be at end of \d alternatives: */
-	else if (TailMatchesCS1("\\d*"))
+	if (TailMatchesCS1("\\d*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
-	else if (TailMatchesCS1("\\ef"))
+	if (TailMatchesCS1("\\ef"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (TailMatchesCS1("\\ev"))
+	if (TailMatchesCS1("\\ev"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
-	else if (TailMatchesCS1("\\encoding"))
+	if (TailMatchesCS1("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
-	else if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
+	if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
 		COMPLETE_WITH_LIST(sql_commands);
-	else if (TailMatchesCS1("\\l*") && !TailMatchesCS1("\\lo*"))
+	if (TailMatchesCS1("\\l*") && !TailMatchesCS1("\\lo*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-	else if (TailMatchesCS1("\\password"))
+	if (TailMatchesCS1("\\password"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (TailMatchesCS1("\\pset"))
+	if (TailMatchesCS1("\\pset"))
 	{
 		static const char *const my_list[] =
 		{"border", "columns", "expanded", "fieldsep", "fieldsep_zero",
@@ -3069,7 +2789,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (TailMatchesCS2("\\pset", MatchAny))
+	if (TailMatchesCS2("\\pset", MatchAny))
 	{
 		if (TailMatchesCS1("format"))
 		{
@@ -3079,53 +2799,53 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (TailMatchesCS1("linestyle"))
+		if (TailMatchesCS1("linestyle"))
 			COMPLETE_WITH_LIST_CS3("ascii", "old-ascii", "unicode");
-		else if (TailMatchesCS1("unicode_border_linestyle|"
+		if (TailMatchesCS1("unicode_border_linestyle|"
 								"unicode_column_linestyle|"
 								"unicode_header_linestyle"))
 			COMPLETE_WITH_LIST_CS2("single", "double");
 	}
-	else if (TailMatchesCS1("\\unset"))
+	if (TailMatchesCS1("\\unset"))
 	{
-		matches = complete_from_variables(text, "", "", true);
+		return complete_from_variables(text, "", "", true);
 	}
-	else if (TailMatchesCS1("\\set"))
+	if (TailMatchesCS1("\\set"))
 	{
-		matches = complete_from_variables(text, "", "", false);
+		return complete_from_variables(text, "", "", false);
 	}
-	else if (TailMatchesCS2("\\set", MatchAny))
+	if (TailMatchesCS2("\\set", MatchAny))
 	{
 		if (TailMatchesCS1("AUTOCOMMIT|ON_ERROR_STOP|QUIET|"
 						   "SINGLELINE|SINGLESTEP"))
 			COMPLETE_WITH_LIST_CS2("on", "off");
-		else if (TailMatchesCS1("COMP_KEYWORD_CASE"))
+		if (TailMatchesCS1("COMP_KEYWORD_CASE"))
 			COMPLETE_WITH_LIST_CS4("lower", "upper",
 								   "preserve-lower", "preserve-upper");
-		else if (TailMatchesCS1("ECHO"))
+		if (TailMatchesCS1("ECHO"))
 			COMPLETE_WITH_LIST_CS4("errors", "queries", "all", "none");
-		else if (TailMatchesCS1("ECHO_HIDDEN"))
+		if (TailMatchesCS1("ECHO_HIDDEN"))
 			COMPLETE_WITH_LIST_CS3("noexec", "off", "on");
-		else if (TailMatchesCS1("HISTCONTROL"))
+		if (TailMatchesCS1("HISTCONTROL"))
 			COMPLETE_WITH_LIST_CS4("ignorespace", "ignoredups",
 								   "ignoreboth", "none");
-		else if (TailMatchesCS1("ON_ERROR_ROLLBACK"))
+		if (TailMatchesCS1("ON_ERROR_ROLLBACK"))
 			COMPLETE_WITH_LIST_CS3("on", "off", "interactive");
-		else if (TailMatchesCS1("SHOW_CONTEXT"))
+		if (TailMatchesCS1("SHOW_CONTEXT"))
 			COMPLETE_WITH_LIST_CS3("never", "errors", "always");
-		else if (TailMatchesCS1("VERBOSITY"))
+		if (TailMatchesCS1("VERBOSITY"))
 			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
 	}
-	else if (TailMatchesCS1("\\sf*"))
+	if (TailMatchesCS1("\\sf*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (TailMatchesCS1("\\sv*"))
+	if (TailMatchesCS1("\\sv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
+	if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
 							"\\ir|\\include_relative|\\o|\\out|"
 							"\\s|\\w|\\write|\\lo_import"))
 	{
 		completion_charp = "\\";
-		matches = completion_matches(text, complete_from_files);
+		return completion_matches(text, complete_from_files);
 	}
 
 	/*
@@ -3143,7 +2863,7 @@ psql_completion(const char *text, int start, int end)
 			{
 				if (words_after_create[i].query)
 					COMPLETE_WITH_QUERY(words_after_create[i].query);
-				else if (words_after_create[i].squery)
+				if (words_after_create[i].squery)
 					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
 											   NULL);
 				break;
@@ -3151,25 +2871,8 @@ psql_completion(const char *text, int start, int end)
 		}
 	}
 
-	/*
-	 * If we still don't have anything to match we have to fabricate some sort
-	 * of default list. If we were to just return NULL, readline automatically
-	 * attempts filename completion, and that's usually no good.
-	 */
-	if (matches == NULL)
-	{
-		COMPLETE_WITH_CONST("");
-#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
-		rl_completion_append_character = '\0';
-#endif
-	}
-
-	/* free storage */
-	free(previous_words);
-	free(words_buffer);
-
-	/* Return our Grand List O' Matches */
-	return matches;
+	/* We found no match */
+	return NULL;
 }
 
 
-- 
2.9.2

0002-Make-keywords-case-follow-to-input.patchtext/x-patch; charset=us-asciiDownload
From d9015e8d52fc11305e42a704ee9eb3459a62a882 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Wed, 14 Sep 2016 12:48:16 +0900
Subject: [PATCH 2/6] Make keywords' case follow to input

Currently some keywords suggested along with database objects are
always in upper case. This patch changes the behavior so that the case
of the additional keywords follow the setting of COMP_KEYWORD_CASE.

COMPLETE_WITH_ATTR needs completion_charp to be appendable, so this
patch changes it to a PQExpBuffer and adjust COMPLET_WITH_* macros to
that change.

This leaves keywords contained in Query_for_list_of_grant_roles and
Query_for_enum, but it is another problem.
---
 src/bin/psql/tab-complete-macros.h |  62 ++++++--
 src/bin/psql/tab-complete.c        | 300 ++++++++++++++++++++-----------------
 2 files changed, 212 insertions(+), 150 deletions(-)

diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
index 8ee52b8..3e91bbe 100644
--- a/src/bin/psql/tab-complete-macros.h
+++ b/src/bin/psql/tab-complete-macros.h
@@ -231,16 +231,46 @@
  * 5) The list of attributes of the given table (possibly schema-qualified).
  * 6) The list of arguments to the given function (possibly schema-qualified).
  */
-#define COMPLETE_WITH_QUERY(query) \
+#define APPEND_COMP_CHARP(charp) \
+	appendPQExpBufferStr(completion_charp, charp);
+
+#define SET_COMP_CHARP(charp) \
+	resetPQExpBuffer(completion_charp);	\
+	APPEND_COMP_CHARP(charp);
+
+#define COMPLETION_CHARP (completion_charp->data)
+
+#define COMPLETE_WITH_QUERY(query)				\
+do { \
+	SET_COMP_CHARP(query);	\
+	return completion_matches(text, complete_from_query);	\
+} while (0)
+
+/*
+ * COMPLETE_WITH_QUERY with additional keywords. Keywords are complete
+ * case-sensitively
+ */
+#define COMPLETE_WITH_QUERY_KW(query, addon)				\
 do { \
-	completion_charp = query;	\
+	SET_COMP_CHARP(query);	\
+	APPEND_COMP_CHARP(addon); \
 	return completion_matches(text, complete_from_query);	\
 } while (0)
 
-#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
+#define COMPLETE_WITH_SCHEMA_QUERY(query) \
 do { \
 	completion_squery = &(query); \
-	completion_charp = addon; \
+	return completion_matches(text, complete_from_schema_query); \
+} while (0)
+
+/*
+ * COMPLETE_WITH_SCHEMA_QUERY with additional keywords. Keywords are complete
+ * case-sensitively
+ */
+#define COMPLETE_WITH_SCHEMA_QUERY_KW(query, addon)	\
+do { \
+	completion_squery = &(query); \
+	SET_COMP_CHARP(addon); \
 	return completion_matches(text, complete_from_schema_query); \
 } while (0)
 
@@ -260,7 +290,7 @@ do { \
 
 #define COMPLETE_WITH_CONST(string) \
 do { \
-	completion_charp = string;	\
+	SET_COMP_CHARP(string);	\
 	completion_case_sensitive = false; \
 	return completion_matches(text, complete_from_const);	\
 } while (0)
@@ -278,12 +308,14 @@ do { \
 								false, false, pset.encoding); \
 	if (_completion_table == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_attributes addon; \
+		SET_COMP_CHARP(Query_for_list_of_attributes); \
+		APPEND_COMP_CHARP(addon);					  \
 		completion_info_charp = relation; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_attributes_with_schema addon; \
+		SET_COMP_CHARP(Query_for_list_of_attributes_with_schema); \
+		APPEND_COMP_CHARP(addon); \
 		completion_info_charp = _completion_table; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -303,12 +335,12 @@ do { \
 								   false, false, pset.encoding); \
 	if (_completion_function == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_arguments; \
+		SET_COMP_CHARP(Query_for_list_of_arguments); \
 		completion_info_charp = function; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_arguments_with_schema;	\
+		SET_COMP_CHARP(Query_for_list_of_arguments_with_schema); \
 		completion_info_charp = _completion_function; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -400,6 +432,16 @@ do { \
 	COMPLETE_WITH_LIST_CS(list); \
 } while (0)
 
-
+#define ADDLIST1(s1) additional_kw_query(text, 1, s1)
+#define ADDLIST2(s1, s2) additional_kw_query(text, 2, s1, s2)
+#define ADDLIST3(s1, s2, s3) additional_kw_query(text, 3, s1, s2, s3)
+#define ADDLIST4(s1, s2, s3, s4) \
+	additional_kw_query(text, 4, s1, s2, s3, s4)
+#define ADDLIST13(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
+	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13)
+#define ADDLIST15(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
+	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13, s14, s15)
 
 #endif   /* TAB_COMPLETE_MACROS_H */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 679e58f..85bb363 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -126,7 +126,7 @@ static int	completion_max_records;
  * Communication variables set by COMPLETE_WITH_FOO macros and then used by
  * the completion callback functions.  Ugly but there is no better way.
  */
-static const char *completion_charp;	/* to pass a string */
+static PQExpBuffer completion_charp = NULL;		/* to pass a string */
 static const char *const * completion_charpp;	/* to pass a list of strings */
 static const char *completion_info_charp;		/* to pass a second string */
 static const char *completion_info_charp2;		/* to pass a third string */
@@ -776,6 +776,7 @@ static char **complete_from_variables(const char *text,
 static char *complete_from_files(const char *text, int state);
 
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
+static char *additional_kw_query( const char *ref, int n, ...);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
 
@@ -947,7 +948,8 @@ psql_completion(const char *text, int start, int end)
 #endif
 
 	/* Clear a few things. */
-	completion_charp = NULL;
+	if (completion_charp == NULL)
+		completion_charp = createPQExpBuffer();
 	completion_charpp = NULL;
 	completion_info_charp = NULL;
 	completion_info_charp2 = NULL;
@@ -1056,8 +1058,8 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER TABLE */
 	if (Matches2("ALTER", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
+									  ADDLIST1("ALL IN TABLESPACE"));
 
 	/* ALTER something */
 	if (Matches1("ALTER"))
@@ -1173,8 +1175,8 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER INDEX */
 	if (Matches2("ALTER", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+									  ADDLIST1("ALL IN TABLESPACE"));
 	/* ALTER INDEX <name> */
 	if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
@@ -1202,8 +1204,8 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER MATERIALIZED VIEW */
 	if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_matviews,
+									  ADDLIST1("ALL IN TABLESPACE"));
 
 	/* ALTER USER,ROLE <name> */
 	if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
@@ -1358,7 +1360,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
 	if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 	/* ALTER TRIGGER <name> ON <name> */
 	if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
@@ -1404,10 +1406,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	}
 	/* ALTER TABLE xxx INHERIT */
 	if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* ALTER TABLE xxx NO INHERIT */
 	if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* ALTER TABLE xxx DISABLE */
 	if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
 		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
@@ -1424,11 +1426,11 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER TABLE xxx ALTER */
 	if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
+		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST2("COLUMN", "CONSTRAINT"));
 
 	/* ALTER TABLE xxx RENAME */
 	if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
+		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST3("COLUMN", "CONSTRAINT", "TO"));
 	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
@@ -1628,9 +1630,10 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
 	if (Matches1("CLUSTER"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("VERBOSE"));
 	if (Matches2("CLUSTER", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 	/* If we have CLUSTER <sth>, then add "USING" */
 	if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
 		COMPLETE_WITH_CONST("USING");
@@ -1677,7 +1680,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
 	}
 	if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 	if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 	if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
@@ -1692,11 +1695,11 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * backslash command).
 	 */
 	if (Matches1("COPY|\\copy"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION ALL SELECT '('");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
+									  ADDLIST1("("));
 	/* If we have COPY BINARY, complete with list of tables */
 	if (Matches2("COPY", "BINARY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* If we have COPY (, complete it with legal commands */
 	if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
@@ -1708,7 +1711,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
 			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
 	{
-		completion_charp = "";
+		SET_COMP_CHARP("");
 		return completion_matches(text, complete_from_files);
 	}
 
@@ -1777,21 +1780,20 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * existing indexes
 	 */
 	if (TailMatches2("CREATE|UNIQUE", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'ON'"
-								   " UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+									  ADDLIST2("ON", "CONCURRENTLY"));
 	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
 	if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
 			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 
 	/*
 	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
 	 * indexes
 	 */
 	if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'ON'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+									  ADDLIST1("ON"));
 	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
 	if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
 			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
@@ -1826,7 +1828,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
 	if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
 	if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING (", "WITH CHECK (");
@@ -1864,7 +1866,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
 	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 /* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
@@ -1920,10 +1922,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * tables
 	 */
 	if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
 	if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
 	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
@@ -2009,7 +2011,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
 	if (TailMatches2("DELETE", "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete DELETE FROM <table> */
 	if (TailMatches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
@@ -2047,10 +2049,10 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* DROP INDEX */
 	if (Matches2("DROP", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+									  ADDLIST1("CONCURRENTLY"));
 	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 	if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
@@ -2060,7 +2062,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 
 	/* DROP OWNED BY */
 	if (Matches2("DROP", "OWNED"))
@@ -2166,7 +2168,7 @@ psql_completion_internal(const char *text, char **previous_words,
 /* FOREIGN TABLE */
 	if (TailMatches2("FOREIGN", "TABLE") &&
 			 !TailMatches3("CREATE", MatchAny, MatchAny))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables);
 
 /* FOREIGN SERVER */
 	if (TailMatches2("FOREIGN", "SERVER"))
@@ -2175,20 +2177,10 @@ psql_completion_internal(const char *text, char **previous_words,
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
 	if (TailMatches1("GRANT|REVOKE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'SELECT'"
-							" UNION SELECT 'INSERT'"
-							" UNION SELECT 'UPDATE'"
-							" UNION SELECT 'DELETE'"
-							" UNION SELECT 'TRUNCATE'"
-							" UNION SELECT 'REFERENCES'"
-							" UNION SELECT 'TRIGGER'"
-							" UNION SELECT 'CREATE'"
-							" UNION SELECT 'CONNECT'"
-							" UNION SELECT 'TEMPORARY'"
-							" UNION SELECT 'EXECUTE'"
-							" UNION SELECT 'USAGE'"
-							" UNION SELECT 'ALL'");
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+			ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
+					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
+					  "EXECUTE", "USAGE", "ALL"));
 
 	/*
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
@@ -2216,22 +2208,22 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * privilege.
 	 */
 	if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
-								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
-								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
-								   " UNION SELECT 'ALL TABLES IN SCHEMA'"
-								   " UNION SELECT 'DATABASE'"
-								   " UNION SELECT 'DOMAIN'"
-								   " UNION SELECT 'FOREIGN DATA WRAPPER'"
-								   " UNION SELECT 'FOREIGN SERVER'"
-								   " UNION SELECT 'FUNCTION'"
-								   " UNION SELECT 'LANGUAGE'"
-								   " UNION SELECT 'LARGE OBJECT'"
-								   " UNION SELECT 'SCHEMA'"
-								   " UNION SELECT 'SEQUENCE'"
-								   " UNION SELECT 'TABLE'"
-								   " UNION SELECT 'TABLESPACE'"
-								   " UNION SELECT 'TYPE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tsvmf,
+			   ADDLIST15("ALL FUNCTIONS IN SCHEMA",
+						 "ALL SEQUENCES IN SCHEMA",
+						 "ALL TABLES IN SCHEMA",
+						 "DATABASE",
+						 "DOMAIN",
+						 "FOREIGN DATA WRAPPER",
+						 "FOREIGN SERVER",
+						 "FUNCTION",
+						 "LANGUAGE",
+						 "LARGE OBJECT",
+						 "SCHEMA",
+						 "SEQUENCE",
+						 "TABLE",
+						 "TABLESPACE",
+						 "TYPE"));
 
 	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
@@ -2251,21 +2243,21 @@ psql_completion_internal(const char *text, char **previous_words,
 		if (TailMatches1("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 		if (TailMatches1("DOMAIN"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains);
 		if (TailMatches1("FUNCTION"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 		if (TailMatches1("LANGUAGE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
 		if (TailMatches1("SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
 		if (TailMatches1("SEQUENCE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
 		if (TailMatches1("TABLE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf);
 		if (TailMatches1("TABLESPACE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 		if (TailMatches1("TYPE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 		if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
@@ -2329,7 +2321,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
 	if (TailMatches2("INSERT", "INTO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete "INSERT INTO <table> (" with attribute names */
 	if (TailMatches4("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
@@ -2356,10 +2348,10 @@ psql_completion_internal(const char *text, char **previous_words,
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
 	if (Matches1("LOCK"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION SELECT 'TABLE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
+									  ADDLIST1("TABLE"));
 	if (Matches2("LOCK", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 	/* For the following, handle the case of a single table only for now */
 
@@ -2422,10 +2414,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_matviews,
+									  ADDLIST1("CONCURRENTLY"));
 	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
 	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
@@ -2443,9 +2435,9 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
 	if (Matches2("REINDEX", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 	if (Matches2("REINDEX", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	if (Matches2("REINDEX", "SCHEMA"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
 	if (Matches2("REINDEX", "SYSTEM|DATABASE"))
@@ -2515,7 +2507,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
 	if (Matches2("SET", "CONSTRAINTS"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_constraints_with_schema,
+									  ADDLIST1("ALL"));
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
 	if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
@@ -2527,7 +2520,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
 	if (Matches3("SET", "SESSION", "AUTHORIZATION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+							   ADDLIST1("DEFAULT"));
 	/* Complete RESET SESSION with AUTHORIZATION */
 	if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
@@ -2553,10 +2547,10 @@ psql_completion_internal(const char *text, char **previous_words,
 			COMPLETE_WITH_LIST(my_list);
 		}
 		if (TailMatches2("search_path", "TO|="))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
-								" AND nspname not like 'pg\\_toast%%' "
-								" AND nspname not like 'pg\\_temp%%' "
-								" UNION SELECT 'DEFAULT' ");
+			COMPLETE_WITH_QUERY_KW(Query_for_list_of_schemas
+								   " AND nspname not like 'pg\\_toast%%' "
+								   " AND nspname not like 'pg\\_temp%%' ",
+								   ADDLIST1("DEFAULT"));
 		else
 		{
 			/* generic, type based, GUC support, guctype is malloc'ed */
@@ -2591,7 +2585,7 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* TABLE, but not TABLE embedded in other commands */
 	if (Matches1("TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations);
 
 /* TABLESAMPLE */
 	if (TailMatches1("TABLESAMPLE"))
@@ -2601,7 +2595,7 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* TRUNCATE */
 	if (Matches1("TRUNCATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 /* UNLISTEN */
 	if (Matches1("UNLISTEN"))
@@ -2610,7 +2604,7 @@ psql_completion_internal(const char *text, char **previous_words,
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
 	if (TailMatches1("UPDATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete UPDATE <table> with "SET" */
 	if (TailMatches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
@@ -2625,10 +2619,8 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
 	if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'CURRENT_USER'"
-							" UNION SELECT 'PUBLIC'"
-							" UNION SELECT 'USER'");
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+			ADDLIST3("CURRENT_USER", "PUBLIC", "USER"));
 	if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
 	if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
@@ -2641,29 +2633,26 @@ psql_completion_internal(const char *text, char **previous_words,
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
 	if (Matches1("VACUUM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'FULL'"
-								   " UNION SELECT 'FREEZE'"
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST4("FULL", "FREEZE",
+											   "ANALYZE", "VERBOSE"));
 	if (Matches2("VACUUM", "FULL|FREEZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST2("ANALYZE", "VERBOSE"));
 	if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("VERBOSE"));
 	if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("ANALYZE"));
 	if (Matches2("VACUUM", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("ANALYZE"));
 	if (Matches2("VACUUM", "ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("VERBOSE"));
 	if (HeadMatches1("VACUUM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 
 /* WITH [RECURSIVE] */
 
@@ -2677,7 +2666,7 @@ psql_completion_internal(const char *text, char **previous_words,
 /* ANALYZE */
 	/* Complete with list of tables */
 	if (Matches1("ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf);
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
@@ -2687,11 +2676,11 @@ psql_completion_internal(const char *text, char **previous_words,
 /* ... FROM ... */
 /* TODO: also include SRF ? */
 	if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf);
 
 /* ... JOIN ... */
 	if (TailMatches1("JOIN"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf);
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
@@ -2708,13 +2697,13 @@ psql_completion_internal(const char *text, char **previous_words,
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	}
 	if (TailMatchesCS1("\\da*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates);
 	if (TailMatchesCS1("\\dA*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 	if (TailMatchesCS1("\\db*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	if (TailMatchesCS1("\\dD*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains);
 	if (TailMatchesCS1("\\des*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
 	if (TailMatchesCS1("\\deu*"))
@@ -2722,7 +2711,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatchesCS1("\\dew*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 	if (TailMatchesCS1("\\df*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 
 	if (TailMatchesCS1("\\dFd*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
@@ -2735,40 +2724,40 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
 
 	if (TailMatchesCS1("\\di*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	if (TailMatchesCS1("\\dL*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
 	if (TailMatchesCS1("\\dn*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
 	if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf);
 	if (TailMatchesCS1("\\ds*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
 	if (TailMatchesCS1("\\dt*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	if (TailMatchesCS1("\\dT*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 	if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	if (TailMatchesCS1("\\dv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	if (TailMatchesCS1("\\dx*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
 	if (TailMatchesCS1("\\dm*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 	if (TailMatchesCS1("\\dE*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables);
 	if (TailMatchesCS1("\\dy*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* must be at end of \d alternatives: */
 	if (TailMatchesCS1("\\d*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations);
 
 	if (TailMatchesCS1("\\ef"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 	if (TailMatchesCS1("\\ev"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 
 	if (TailMatchesCS1("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
@@ -2837,14 +2826,14 @@ psql_completion_internal(const char *text, char **previous_words,
 			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
 	}
 	if (TailMatchesCS1("\\sf*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 	if (TailMatchesCS1("\\sv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
 							"\\ir|\\include_relative|\\o|\\out|"
 							"\\s|\\w|\\write|\\lo_import"))
 	{
-		completion_charp = "\\";
+		SET_COMP_CHARP("\\");
 		return completion_matches(text, complete_from_files);
 	}
 
@@ -2864,8 +2853,7 @@ psql_completion_internal(const char *text, char **previous_words,
 				if (words_after_create[i].query)
 					COMPLETE_WITH_QUERY(words_after_create[i].query);
 				if (words_after_create[i].squery)
-					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
-											   NULL);
+					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery);
 				break;
 			}
 		}
@@ -3112,13 +3100,13 @@ _complete_from_query(int is_schema_query, const char *text, int state)
 							  char_length, e_text);
 
 			/* If an addon query was provided, use it */
-			if (completion_charp)
-				appendPQExpBuffer(&query_buffer, "\n%s", completion_charp);
+			if (COMPLETION_CHARP[0])
+				appendPQExpBuffer(&query_buffer, "\n%s", COMPLETION_CHARP);
 		}
 		else
 		{
 			/* completion_charp is an sprintf-style format string */
-			appendPQExpBuffer(&query_buffer, completion_charp,
+			appendPQExpBuffer(&query_buffer, COMPLETION_CHARP,
 							  char_length, e_text,
 							  e_info_charp, e_info_charp,
 							  e_info_charp2, e_info_charp2);
@@ -3233,18 +3221,17 @@ complete_from_list(const char *text, int state)
 static char *
 complete_from_const(const char *text, int state)
 {
-	Assert(completion_charp != NULL);
 	if (state == 0)
 	{
 		if (completion_case_sensitive)
-			return pg_strdup(completion_charp);
+			return pg_strdup(COMPLETION_CHARP);
 		else
 
 			/*
 			 * If case insensitive matching was requested initially, adjust
 			 * the case according to setting.
 			 */
-			return pg_strdup_keyword_case(completion_charp, text);
+			return pg_strdup_keyword_case(COMPLETION_CHARP, text);
 	}
 	else
 		return NULL;
@@ -3345,7 +3332,7 @@ complete_from_files(const char *text, int state)
 	if (state == 0)
 	{
 		/* Initialization: stash the unquoted input. */
-		unquoted_text = strtokx(text, "", NULL, "'", *completion_charp,
+		unquoted_text = strtokx(text, "", NULL, "'", COMPLETION_CHARP[0],
 								false, true, pset.encoding);
 		/* expect a NULL return for the empty string only */
 		if (!unquoted_text)
@@ -3366,7 +3353,7 @@ complete_from_files(const char *text, int state)
 		 * bother providing a macro to simplify this.
 		 */
 		ret = quote_if_needed(unquoted_match, " \t\r\n\"`",
-							  '\'', *completion_charp, pset.encoding);
+							  '\'', COMPLETION_CHARP[0], pset.encoding);
 		if (ret)
 			free(unquoted_match);
 		else
@@ -3410,6 +3397,39 @@ pg_strdup_keyword_case(const char *s, const char *ref)
 	return ret;
 }
 
+/* Construct codelet to append given keywords  */
+static char *
+additional_kw_query(const char *ref, int n, ...)
+{
+	va_list ap;
+	static PQExpBuffer qbuf = NULL;
+	int i;
+
+	if (qbuf == NULL)
+		qbuf = createPQExpBuffer();
+	else
+		resetPQExpBuffer(qbuf);
+
+	/* Construct an additional queriy to append keywords */
+	appendPQExpBufferStr(qbuf, " UNION ALL SELECT * FROM (VALUES ");
+
+	va_start(ap, n);
+	for (i = 0 ; i < n ; i++)
+	{
+		char *item = pg_strdup_keyword_case(va_arg(ap, char *), ref);
+		if (i > 0) appendPQExpBufferChar(qbuf, ',');
+		appendPQExpBufferStr(qbuf, "('");
+		appendPQExpBufferStr(qbuf, item);
+		appendPQExpBufferStr(qbuf, "')");
+		pg_free(item);
+	}
+	va_end(ap);
+
+	appendPQExpBufferStr(qbuf, ") as x");
+
+	return qbuf->data;
+}
+
 
 /*
  * escape_string - Escape argument for use as string literal.
-- 
2.9.2

0003-Fix-suggested-keywords-to-follow-input-in-tab-comple.patchtext/x-patch; charset=us-asciiDownload
From 8d68e7102e2200bb99fa913fa936f9bd16b52092 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 16 Sep 2016 11:36:13 +0900
Subject: [PATCH 3/6] Fix suggested keywords to follow input in tab-completion
 session 2

The prevous fix left some completions unfixed. This commit moves
keywords of Query_for_list_of_set_vars/alter_system_set_vars/show_vars
out of the constant and process the keywords as the same as other
syntaxes.

Role name is a bother. PUBLIC, CURRENT_USER, SESSION_USER are
certainly roles and the last two are used in many places. In this
patch I renamed Query_for_list_of_roles to
Query_for_list_of_true_roles and added Query_for_list_of_roles that
having CURRENT_USER and SESSION_USER. But this might be too much.
---
 src/bin/psql/tab-complete-macros.h |   2 +
 src/bin/psql/tab-complete.c        | 100 ++++++++++++++++++++++---------------
 2 files changed, 62 insertions(+), 40 deletions(-)

diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
index 3e91bbe..3597004 100644
--- a/src/bin/psql/tab-complete-macros.h
+++ b/src/bin/psql/tab-complete-macros.h
@@ -437,6 +437,8 @@ do { \
 #define ADDLIST3(s1, s2, s3) additional_kw_query(text, 3, s1, s2, s3)
 #define ADDLIST4(s1, s2, s3, s4) \
 	additional_kw_query(text, 4, s1, s2, s3, s4)
+#define ADDLIST6(s1, s2, s3, s4, s5, s6)				\
+	additional_kw_query(text, 6, s1, s2, s3, s4, s5, s6)
 #define ADDLIST13(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
 	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
 						s8, s9, s10, s11, s12, s13)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 85bb363..3af623c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -449,43 +449,38 @@ static const SchemaQuery Query_for_list_of_matviews = {
 " WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s'"
 
 #define Query_for_list_of_alter_system_set_vars \
-"SELECT name FROM "\
-" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
-"  WHERE context != 'internal') ss "\
-" WHERE substring(name,1,%d)='%s'"\
-" UNION ALL SELECT 'all' ss"
-
-#define Query_for_list_of_set_vars \
-"SELECT name FROM "\
-" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
-"  WHERE context IN ('user', 'superuser') "\
-"  UNION ALL SELECT 'constraints' "\
-"  UNION ALL SELECT 'transaction' "\
-"  UNION ALL SELECT 'session' "\
-"  UNION ALL SELECT 'role' "\
-"  UNION ALL SELECT 'tablespace' "\
-"  UNION ALL SELECT 'all') ss "\
+"SELECT name FROM "													\
+" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings " \
+"  WHERE context != 'internal') ss "								\
 " WHERE substring(name,1,%d)='%s'"
 
+#define Query_for_list_of_set_vars									 \
+"SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings " \
+"  WHERE context IN ('user', 'superuser') "							\
+"   AND substring(name,1,%d)='%s'"
+
 #define Query_for_list_of_show_vars \
-"SELECT name FROM "\
-" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
-"  UNION ALL SELECT 'session authorization' "\
-"  UNION ALL SELECT 'all') ss "\
+"SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings " \
 " WHERE substring(name,1,%d)='%s'"
 
-#define Query_for_list_of_roles \
+#define Query_for_list_of_true_roles \
 " SELECT pg_catalog.quote_ident(rolname) "\
 "   FROM pg_catalog.pg_roles "\
 "  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"
 
+#define Query_for_list_of_roles \
+	concatenate_strings(												\
+		" SELECT pg_catalog.quote_ident(rolname) "						\
+		 "   FROM pg_catalog.pg_roles "									\
+		 "  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'",	\
+		ADDLIST2("CURRENT_USER", "SESSION_USER"))
+
 #define Query_for_list_of_grant_roles \
-" SELECT pg_catalog.quote_ident(rolname) "\
-"   FROM pg_catalog.pg_roles "\
-"  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"\
-" UNION ALL SELECT 'PUBLIC'"\
-" UNION ALL SELECT 'CURRENT_USER'"\
-" UNION ALL SELECT 'SESSION_USER'"
+	concatenate_strings(												\
+		" SELECT pg_catalog.quote_ident(rolname) "						\
+		 "   FROM pg_catalog.pg_roles "									\
+		 "  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'",	\
+		ADDLIST3("PUBLIC", "CURRENT_USER", "SESSION_USER"))
 
 /* the silly-looking length condition is just to eat up the current word */
 #define Query_for_table_owning_index \
@@ -681,13 +676,13 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "         WHERE pg_catalog.quote_ident(polname)='%s')"
 
 #define Query_for_enum \
-" SELECT name FROM ( "\
-"   SELECT pg_catalog.quote_ident(pg_catalog.unnest(enumvals)) AS name "\
-"     FROM pg_catalog.pg_settings "\
-"    WHERE pg_catalog.lower(name)=pg_catalog.lower('%s') "\
-"    UNION ALL " \
-"   SELECT 'DEFAULT' ) ss "\
-"  WHERE pg_catalog.substring(name,1,%%d)='%%s'"
+	concatenate_strings( \
+		" SELECT name FROM ( "											\
+	"   SELECT pg_catalog.quote_ident(pg_catalog.unnest(enumvals)) AS name " \
+	"     FROM pg_catalog.pg_settings "									\
+	"    WHERE pg_catalog.lower(name)=pg_catalog.lower('%s')) ss "		\
+	"  WHERE pg_catalog.substring(name,1,%%d)='%%s'", \
+		ADDLIST1("DEFAULT"))
 
 /*
  * This is a list of all "things" in Pgsql, which can show up after CREATE or
@@ -727,7 +722,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN DATA WRAPPER", NULL, NULL},
 	{"FOREIGN TABLE", NULL, NULL},
 	{"FUNCTION", NULL, &Query_for_list_of_functions},
-	{"GROUP", Query_for_list_of_roles},
+	{"GROUP", Query_for_list_of_true_roles},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"INDEX", NULL, &Query_for_list_of_indexes},
 	{"MATERIALIZED VIEW", NULL, &Query_for_list_of_matviews},
@@ -736,7 +731,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"OWNED", NULL, NULL, THING_NO_CREATE},		/* for DROP OWNED BY ... */
 	{"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
 	{"POLICY", NULL, NULL},
-	{"ROLE", Query_for_list_of_roles},
+	{"ROLE", Query_for_list_of_true_roles},
 	{"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"},
 	{"SCHEMA", Query_for_list_of_schemas},
 	{"SEQUENCE", NULL, &Query_for_list_of_sequences},
@@ -751,7 +746,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"UNIQUE", NULL, NULL, THING_NO_DROP},		/* for CREATE UNIQUE INDEX ... */
 	{"UNLOGGED", NULL, NULL, THING_NO_DROP},	/* for CREATE UNLOGGED TABLE
 												 * ... */
-	{"USER", Query_for_list_of_roles},
+	{"USER", Query_for_list_of_true_roles},
 	{"USER MAPPING FOR", NULL, NULL},
 	{"VIEW", NULL, &Query_for_list_of_views},
 	{NULL}						/* end of list */
@@ -776,6 +771,7 @@ static char **complete_from_variables(const char *text,
 static char *complete_from_files(const char *text, int state);
 
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
+static char *concatenate_strings(const char *s1, const char *s2);
 static char *additional_kw_query( const char *ref, int n, ...);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
@@ -1296,7 +1292,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
 	if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars,
+							ADDLIST1("ALL"));
 	/* ALTER VIEW <name> */
 	if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
@@ -2470,9 +2467,12 @@ psql_completion_internal(const char *text, char **previous_words,
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
 	if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars,
+							ADDLIST6("CONSTRAINTS", "TRANSACTION", "SESSION",
+									 "ROLE", "TABLESPACE", "ALL"));
 	if (Matches1("SHOW"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars, 
+							ADDLIST2("SESSION AUTHORIZATION", "ALL"));
 	/* Complete "SET TRANSACTION" */
 	if (Matches2("SET", "TRANSACTION"))
 		COMPLETE_WITH_LIST5("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
@@ -3397,6 +3397,26 @@ pg_strdup_keyword_case(const char *s, const char *ref)
 	return ret;
 }
 
+/*
+ * Returns concatenated string.
+ * Note: the internal buffer is static so this cannot be called recursively.
+ */
+static char *
+concatenate_strings(const char *s1, const char *s2)
+{
+	static PQExpBuffer qbuf = NULL;
+	
+	if (qbuf == NULL)
+		qbuf = createPQExpBuffer();
+	else
+		resetPQExpBuffer(qbuf);
+
+	appendPQExpBufferStr(qbuf, s1);
+	appendPQExpBufferStr(qbuf, s2);
+
+	return qbuf->data;
+}
+
 /* Construct codelet to append given keywords  */
 static char *
 additional_kw_query(const char *ref, int n, ...)
-- 
2.9.2

0004-Introduce-word-shift-and-removal-feature-to-psql-com.patchtext/x-patch; charset=us-asciiDownload
From 1a6d928cfc2eac3aeeeeb19099efccb8f4359abf Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Thu, 15 Sep 2016 14:44:55 +0900
Subject: [PATCH 4/6] Introduce word shift and removal feature to
 psql-completion

Currently completion of psql is vulnerable for noise words such like
temp/temporary, unlogged or concurrent. Addition to that, schema
elemsnts in CREATE SCHEMA syntax or some recursive syntaxes are
processed in somewhat bogus way.  To accommodate completion mechanism
for the cases, this patch introduces mainly two features.

1. Add a feature to ignore leading words to process.  New macros
  HEAD_SHIFT, HEAD_SET to shift or set the position of the first word
  to match using other macros. All *MatchesN macros follow
  this. SHIFT_TO_LAST1 is a macro to shift the head to the position
  where the specified word is found last.

2. Add a feature to remove intermediate words from previous_words
  list.  COLLAPSE(s, n) macro removes n words from the s'th position
  (1-based). Removing "noise" words let the succeeding operations
  simple.

Using this features, this patch implements the following things.

1. Properly treat schema elements by shifting head.

 Now we can treat schema elements as the same as normal syntax by
 isolating from leading words. This allow more complex completion for
 each create xxxs.

2. Simplify some "||"-connected matches required to cover noise words
  by removing noise wors.

 CREATE INDEX or some other syntaxes have rather many optional
 elements so appropriate to demonstrate how COLLAPSE can be used.
 ALTER TABLE/COMMENT/COPY/GRANT/REVOKE/BEGINs.

3. Process recursive of CREATE RULE/EXPLAIN/PREPARE using
  Matches/HeadMatches, not with TailMtaches.

 The recursive syntaxes are previously completed using TailMatches
 instead of Match/HeadMatch but that replacement increases the
 restriction in completing the inner commands.  psql_complete_internal
 can be called recursively and it can be used to resolve this.

The changes in this patch also allows us to encapsulate completion
code for common subsyntaxes in functions but this doesn't that.
---
 src/bin/psql/tab-complete-macros.h | 139 +++++++---
 src/bin/psql/tab-complete.c        | 521 +++++++++++++++++++++----------------
 2 files changed, 392 insertions(+), 268 deletions(-)

diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
index 3597004..3a18aa4 100644
--- a/src/bin/psql/tab-complete-macros.h
+++ b/src/bin/psql/tab-complete-macros.h
@@ -25,41 +25,69 @@
 #define prev8_wd  (previous_words[7])
 #define prev9_wd  (previous_words[8])
 
+/* Return the number of stored words counting head shift */
+#define WORD_COUNT() (previous_words_count - head_shift)
+
 /*
  * Return the index in previous_words for index from the beginning. n is
  * 1-based and the result is 0-based.
  */
-#define HEAD_INDEX(n) \
-	(previous_words_count - (n))
+#define HEAD_INDEX(n) (WORD_COUNT() - (n))
+
+/* Move the position of the beginning word for matching macros.  */
+#define HEAD_SHIFT(n) (head_shift += (n))
+
+/* Set the position of the beginning word for matching macros.  */
+#define HEAD_SET(n) (head_shift = (n))
+
+/*
+ * remove n words from current shifted position. This moves entire the
+ * previous_words regardless of head_shift.
+ */
+#define COLLAPSE(s, n)							\
+	do { \
+		memmove(previous_words + HEAD_INDEX((s) + (n) - 1), \
+				previous_words + HEAD_INDEX((s) - 1), \
+				sizeof(char *) * \
+				(previous_words_count - HEAD_INDEX((s) - 1)));	\
+		previous_words_count -= (n); \
+	} while (0)
+
+/*
+ * Find the position where the specified word appears last and shift to there.
+ * The words before the position will be ignored ever after.
+ */
+#define SHIFT_TO_LAST1(p1) \
+	HEAD_SHIFT(find_last_index_of(p1, previous_words, previous_words_count))
 
 /*
  * Macros for matching the last N words before point, and after head_sift,
  * case-insensitively.
  */
 #define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches(p1, prev_wd))
 
 #define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd))
 
 #define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
+	(WORD_COUNT() >= 3 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd))
 
 #define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
+	(WORD_COUNT() >= 4 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
 	 word_matches(p4, prev4_wd))
 
 #define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
+	(WORD_COUNT() >= 5 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -67,7 +95,7 @@
 	 word_matches(p5, prev5_wd))
 
 #define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
+	(WORD_COUNT() >= 6 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -76,7 +104,7 @@
 	 word_matches(p6, prev6_wd))
 
 #define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
+	(WORD_COUNT() >= 7 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -86,7 +114,7 @@
 	 word_matches(p7, prev7_wd))
 
 #define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
+	(WORD_COUNT() >= 8 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -97,7 +125,7 @@
 	 word_matches(p8, prev8_wd))
 
 #define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
+	(WORD_COUNT() >= 9 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -113,10 +141,10 @@
 	 * head_shift, case-sensitively.
 	 */
 #define TailMatchesCS1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches_cs(p1, prev_wd))
 #define TailMatchesCS2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches_cs(p1, prev_wd) && \
 	 word_matches_cs(p2, prev2_wd))
 
@@ -125,31 +153,31 @@
 	 * case-insensitively.
 	 */
 #define Matches1(p1) \
-	(previous_words_count == 1 && \
+	(WORD_COUNT() == 1 && \
 	 TailMatches1(p1))
 #define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
+	(WORD_COUNT() == 2 && \
 	 TailMatches2(p1, p2))
 #define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
+	(WORD_COUNT() == 3 && \
 	 TailMatches3(p1, p2, p3))
 #define Matches4(p1, p2, p3, p4) \
-	(previous_words_count == 4 && \
+	(WORD_COUNT() == 4 && \
 	 TailMatches4(p1, p2, p3, p4))
 #define Matches5(p1, p2, p3, p4, p5) \
-	(previous_words_count == 5 && \
+	(WORD_COUNT() == 5 && \
 	 TailMatches5(p1, p2, p3, p4, p5))
 #define Matches6(p1, p2, p3, p4, p5, p6) \
-	(previous_words_count == 6 && \
+	(WORD_COUNT() == 6 && \
 	 TailMatches6(p1, p2, p3, p4, p5, p6))
 #define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(previous_words_count == 7 && \
+	(WORD_COUNT() == 7 && \
 	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
 #define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(previous_words_count == 8 && \
+	(WORD_COUNT() == 8 && \
 	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
 #define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(previous_words_count == 9 && \
+	(WORD_COUNT() == 9 && \
 	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
 
 /*
@@ -195,16 +223,39 @@
 	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
 	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]))
 
-#define MidMatches7(s,p1, p2, p3, p4, p5, p6, p7)	\
+#define MidMatches7(s,p1, p2, p3, p4, p5, p6, p7)			\
 	(HEAD_INDEX((s) + 6) >= 0 &&							\
-	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
-	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
-	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&			\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&		\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&			\
 	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
 	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
 	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&			\
 	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]))
 
+#define MidMatches8(s,p1, p2, p3, p4, p5, p6, p7, p8)		\
+	(HEAD_INDEX((s) + 7) >= 0 &&							\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&				\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&		\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&		\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&		\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&		\
+	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]) &&		\
+	 word_matches(p8, previous_words[HEAD_INDEX((s) + 7)]))
+
+#define MidMatches9(s,p1, p2, p3, p4, p5, p6, p7, p8, p9)		\
+	(HEAD_INDEX((s) + 8) >= 0 &&							\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&				\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&		\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&		\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&		\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&		\
+	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]) &&		\
+	 word_matches(p8, previous_words[HEAD_INDEX((s) + 7)]) &&		\
+	 word_matches(p9, previous_words[HEAD_INDEX((s) + 8)]))
+
 #define HeadMatches1(p1) \
 	MidMatches1(1, p1)
 #define HeadMatches2(p1, p2) \
@@ -219,6 +270,10 @@
 	MidMatches6(1, p1, p2, p3, p4, p5, p6)
 #define HeadMatches7(p1, p2, p3, p4, p5, p6, p7) \
 	MidMatches7(1, p1, p2, p3, p4, p5, p6, p7)
+#define HeadMatches8(p1, p2, p3, p4, p5, p6, p7, p8)	\
+	MidMatches8(1, p1, p2, p3, p4, p5, p6, p7, p8)
+#define HeadMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9)	\
+	MidMatches9(1, p1, p2, p3, p4, p5, p6, p7, p8, p9)
 
 /*
  * A few macros to ease typing. You can use these to complete the given
@@ -240,12 +295,6 @@
 
 #define COMPLETION_CHARP (completion_charp->data)
 
-#define COMPLETE_WITH_QUERY(query)				\
-do { \
-	SET_COMP_CHARP(query);	\
-	return completion_matches(text, complete_from_query);	\
-} while (0)
-
 /*
  * COMPLETE_WITH_QUERY with additional keywords. Keywords are complete
  * case-sensitively
@@ -257,11 +306,8 @@ do { \
 	return completion_matches(text, complete_from_query);	\
 } while (0)
 
-#define COMPLETE_WITH_SCHEMA_QUERY(query) \
-do { \
-	completion_squery = &(query); \
-	return completion_matches(text, complete_from_schema_query); \
-} while (0)
+#define COMPLETE_WITH_QUERY(query) COMPLETE_WITH_QUERY_KW((query), "")
+
 
 /*
  * COMPLETE_WITH_SCHEMA_QUERY with additional keywords. Keywords are complete
@@ -274,6 +320,8 @@ do { \
 	return completion_matches(text, complete_from_schema_query); \
 } while (0)
 
+#define COMPLETE_WITH_SCHEMA_QUERY(query) COMPLETE_WITH_SCHEMA_QUERY_KW((query), "")
+
 #define COMPLETE_WITH_LIST_CS(list) \
 do { \
 	completion_charpp = list; \
@@ -295,7 +343,7 @@ do { \
 	return completion_matches(text, complete_from_const);	\
 } while (0)
 
-#define COMPLETE_WITH_ATTR(relation, addon) \
+#define COMPLETE_WITH_ATTR_KW(relation, addon) \
 do { \
 	char   *_completion_schema; \
 	char   *_completion_table; \
@@ -322,6 +370,8 @@ do { \
 	return completion_matches(text, complete_from_query); \
 } while (0)
 
+#define COMPLETE_WITH_ATTR(relation) COMPLETE_WITH_ATTR_KW((relation), "")
+
 #define COMPLETE_WITH_FUNCTION_ARG(function) \
 do { \
 	char   *_completion_schema; \
@@ -446,4 +496,17 @@ do { \
 	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
 						s8, s9, s10, s11, s12, s13, s14, s15)
 
+#define COMPLETE_THING(p) \
+do { \
+	const pgsql_thing_t *ent = find_thing_entry(previous_words[-(p) - 1]);	\
+	if (ent) \
+	{ \
+		if (ent->query) \
+			COMPLETE_WITH_QUERY(ent->query); \
+		else if (ent->squery) \
+			COMPLETE_WITH_SCHEMA_QUERY(*ent->squery); \
+	} \
+	return NULL; \
+} while (0)
+
 #endif   /* TAB_COMPLETE_MACROS_H */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 3af623c..7a880d9 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -770,6 +770,7 @@ static char **complete_from_variables(const char *text,
 					const char *prefix, const char *suffix, bool need_value);
 static char *complete_from_files(const char *text, int state);
 
+static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static char *concatenate_strings(const char *s1, const char *s2);
 static char *additional_kw_query( const char *ref, int n, ...);
@@ -782,6 +783,7 @@ static char *get_guctype(const char *varname);
 
 static char **psql_completion_internal(const char *text, char **previous_words,
 										   int previous_words_count);
+static const pgsql_thing_t *find_thing_entry(char *word);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -990,6 +992,9 @@ static char **
 psql_completion_internal(const char *text, char **previous_words,
 						 int previous_words_count)
 {
+	/* The number of prefixing words to be ignored */
+	int			head_shift = 0;
+
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
@@ -1040,10 +1045,24 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
+	/*
+	 * If this is in CREATE SCHEMA, seek to the last CREATE and regard it as
+	 * current command to complete.
+	 */
+	if (HeadMatches2("CREATE", "SCHEMA"))
+		SHIFT_TO_LAST1("CREATE|GRANT");
+
 /* CREATE */
 	/* complete with something you can create */
 	if (Matches1("CREATE"))
-		return completion_matches(text, create_command_generator);
+	{
+		if (head_shift == 0)
+			return completion_matches(text, create_command_generator);
+		else
+			/* schema_element allows some kinds of object */
+			COMPLETE_WITH_LIST5("TABLE", "VIEW", "INDEX", "SEQUENCE",
+								"TRIGGER");
+	}			
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
@@ -1292,8 +1311,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
 	if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars,
-							ADDLIST1("ALL"));
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_alter_system_set_vars,
+							   ADDLIST1("ALL"));
 	/* ALTER VIEW <name> */
 	if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
@@ -1423,13 +1442,13 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER TABLE xxx ALTER */
 	if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
-		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST2("COLUMN", "CONSTRAINT"));
+		COMPLETE_WITH_ATTR_KW(prev2_wd, ADDLIST2("COLUMN", "CONSTRAINT"));
 
 	/* ALTER TABLE xxx RENAME */
 	if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
-		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST3("COLUMN", "CONSTRAINT", "TO"));
+		COMPLETE_WITH_ATTR_KW(prev2_wd, ADDLIST3("COLUMN", "CONSTRAINT", "TO"));
 	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 
 	/* ALTER TABLE xxx RENAME yyy */
 	if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
@@ -1444,7 +1463,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
@@ -1455,26 +1474,25 @@ psql_completion_internal(const char *text, char **previous_words,
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
 	}
+	/* Remove COLUMN just after ALTER */
+	if (HeadMatches5("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN"))
+		COLLAPSE(5, 1);
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
-			 Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
 		COMPLETE_WITH_LIST4("TYPE", "SET", "RESET", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
-			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
 		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
-		 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
-	Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
 		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
-			 Matches8("ALTER", "TABLE", MatchAny, "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("DEFAULT", "NOT NULL");
+
 	if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
 	if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
@@ -1596,7 +1614,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * of attributes
 	 */
 	if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
 	if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
@@ -1629,17 +1647,16 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
 									  ADDLIST1("VERBOSE"));
-	if (Matches2("CLUSTER", "VERBOSE"))
+	/* Remove VERBOSE for further completion */
+	if (HeadMatches2("CLUSTER", "VERBOSE"))
+		COLLAPSE(2, 1);
+	if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 	/* If we have CLUSTER <sth>, then add "USING" */
-	if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
-		COMPLETE_WITH_CONST("USING");
-	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	if (Matches3("CLUSTER", "VERBOSE", MatchAny))
+	if (Matches2("CLUSTER", MatchAny))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER <sth> USING, then add the index as well */
-	if (Matches3("CLUSTER", MatchAny, "USING") ||
-			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
+	if (Matches3("CLUSTER", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
@@ -1680,9 +1697,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 	if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
-		Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
-			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
+	if (HeadMatches3("COMMENT", "ON", MatchAny) &&
+		TailMatches1(MatchAnyExcept("IS")))
 		COMPLETE_WITH_CONST("IS");
 
 /* COPY */
@@ -1693,34 +1709,32 @@ psql_completion_internal(const char *text, char **previous_words,
 	 */
 	if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
-									  ADDLIST1("("));
+									  ADDLIST2("(", "BINARY"));
 	/* If we have COPY BINARY, complete with list of tables */
 	if (Matches2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
+	/* Remove BINARY of COPY for further completion */
+	if (HeadMatches2("COPY", "BINARY"))
 	/* If we have COPY (, complete it with legal commands */
 	if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
 	/* If we have COPY [BINARY] <sth>, complete it with "TO" or "FROM" */
-	if (Matches2("COPY|\\copy", MatchAny) ||
-			 Matches3("COPY", "BINARY", MatchAny))
+	if (Matches2("COPY|\\copy", MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "TO");
 	/* If we have COPY [BINARY] <sth> FROM|TO, complete with filename */
-	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
-			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
+	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO"))
 	{
 		SET_COMP_CHARP("");
 		return completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename */
-	if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
-			 Matches5("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
+	if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny))
 		COMPLETE_WITH_LIST6("BINARY", "OIDS", "DELIMITER", "NULL", "CSV",
 							"ENCODING");
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename CSV */
-	if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
-			 Matches6("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny, "CSV"))
+	if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV"))
 		COMPLETE_WITH_LIST5("HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE",
 							"FORCE NOT NULL");
 
@@ -1767,57 +1781,47 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
 
-	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	/* CREATE INDEX */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	if (TailMatches2("CREATE", "UNIQUE"))
+	if (Matches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
-	/*
-	 * If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY", and
-	 * existing indexes
-	 */
-	if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	/* Remove UNIQUE for further completion */
+	if (HeadMatches3("CREATE", "UNIQUE", "INDEX"))
+		COLLAPSE(2, 1);
+	/* Complete with index names as category suggestion and possible keywords */
+	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
 									  ADDLIST2("ON", "CONCURRENTLY"));
-	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
-			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
-
-	/*
-	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
-	 * indexes
-	 */
-	if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+	/* Remove CONCURRENTLY for further completion */
+	if (HeadMatches3("CREATE", "INDEX", "CONCURRENTLY"))
+		COLLAPSE(3, 1);
+	/* Complete with existing index names as word category suggestion */
+	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
 									  ADDLIST1("ON"));
-	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
-			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
+	/* Suggest ON just after index name */
+	if (Matches3("CREATE", "INDEX", MatchAnyExcept("ON")))
 		COMPLETE_WITH_CONST("ON");
-
-	/*
-	 * Complete INDEX <name> ON <table> with a list of table columns (which
-	 * should really be in parens)
-	 */
-	if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-		TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+	/* Specified index name does matter ever after */
+	if (HeadMatches4("CREATE", "INDEX", MatchAny, "ON"))
+		COLLAPSE(3, 1);
+	/* Complete with table names only*/
+	if (Matches3("CREATE", "INDEX", "ON"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
+	/* MatchAny is table name */
+	if (Matches4("CREATE", "INDEX", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-		TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
-	/* same if you put in USING */
-	if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
-			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
-			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
+	if (Matches5("CREATE", "INDEX", "ON", MatchAny, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
-			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
-			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
+	/*  Remove "Using xxx" for further completion*/
+	if (HeadMatches6("CREATE", "INDEX", "ON", MatchAny, "USING", MatchAny))
+		COLLAPSE(5, 2);
+	if (Matches4("CREATE", "INDEX", "ON", MatchAny))
 		COMPLETE_WITH_CONST("(");
+	if (Matches5("CREATE", "INDEX", "ON", MatchAny, "("))
+		COMPLETE_WITH_ATTR(prev2_wd);
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
@@ -1849,43 +1853,72 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
-	/* Complete "CREATE RULE <sth>" with "AS ON" */
-	if (Matches3("CREATE", "RULE", MatchAny))
-		COMPLETE_WITH_CONST("AS ON");
-	/* Complete "CREATE RULE <sth> AS" with "ON" */
-	if (Matches4("CREATE", "RULE", MatchAny, "AS"))
-		COMPLETE_WITH_CONST("ON");
-	/* Complete "CREATE RULE <sth> AS ON" with SELECT|UPDATE|INSERT|DELETE */
-	if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
-		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
-	/* Complete "AS ON SELECT|UPDATE|INSERT|DELETE" with a "TO" */
-	if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
-		COMPLETE_WITH_CONST("TO");
-	/* Complete "AS ON <sth> TO" with a table name */
-	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
+	if (HeadMatches2("CREATE", "RULE"))
+	{
+		/* Complete "CREATE RULE <sth>" with "AS ON" */
+		if (Matches3("CREATE", "RULE", MatchAny))
+			COMPLETE_WITH_CONST("AS ON");
+		/* Complete "CREATE RULE <sth> AS" with "ON" */
+		if (Matches4("CREATE", "RULE", MatchAny, "AS"))
+			COMPLETE_WITH_CONST("ON");
+		/* Complete "CREATE RULE <sth> AS ON" with SELECT|UPDATE|INSERT|DELETE */
+		if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
+			COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
+		/* Complete "AS ON SELECT|UPDATE|INSERT|DELETE" with a "TO" */
+		if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
+			COMPLETE_WITH_CONST("TO");
+		/* Complete "AS ON <sth> TO" with a table name */
+		if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
+		/* Complete "ON <sth> TO <name>" with DO INSTEAD*/
+		if (TailMatches4("ON", "SELECT|UPDATE|INSERT|DELETE", "TO", MatchAny))
+			COMPLETE_WITH_CONST("DO INSTEAD");
+		/* Complete DO INSTEAD with actions */
+		if (TailMatches2("DO", "INSTEAD"))
+			COMPLETE_WITH_LIST5("SELECT", "INSERT", "UPDATE", "DELETE", "NOTIFY");
+		/* Complete DO INSTEAD and further */
+		SHIFT_TO_LAST1("SELECT|INSERT|UPDATE|DELETE|NOTIFY");
+		if (HeadMatches1("SELECT|INSERT|UPDATE|DELETE|NOTIFY"))
+			return psql_completion_internal(text, previous_words, WORD_COUNT());
+		COMPLETE_WITH_CONST("");
+	}
+	
+	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
+	if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
+	/* Remove TEMPORARY/TEMP for further completion */
+	if (HeadMatches3("CREATE", "TEMP|TEMPORARY", "TABLE|VIEW|SEQUENCE"))
+		COLLAPSE(2, 1);
 
-/* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
-			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
-		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
-							"CYCLE", "OWNED BY", "START WITH");
-	if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
-		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
+	/* CREATE SEQUENCE */
+	if (Matches2("CREATE", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+	if (Matches3("CREATE", "SEQUENCE", MatchAny))
+		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO",
+							"CACHE", "CYCLE", "OWNED BY", "START WITH");
+	if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
 	if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
-/* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
-		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
+/* CREATE SCHEMA <name> */
+	if (Matches2("CREATE", "SCHEMA"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+
+/* CREATE TABLE  */
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	if (TailMatches2("CREATE", "UNLOGGED"))
+	if (Matches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
+	/* Remove UNLOGGED for further completion */
+	if (HeadMatches2("CREATE", "UNLOGGED"))
+		COLLAPSE(2, 1);
+	/* Complete CREATE TABLE with existing table names */
+	if (Matches2("CREATE", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
+
 /* CREATE TABLESPACE */
 	if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
@@ -1899,7 +1932,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
-/* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
 	if (TailMatches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
@@ -1967,12 +2000,12 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
-/* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE VIEW */
 	/* Complete CREATE VIEW <name> with AS */
-	if (TailMatches3("CREATE", "VIEW", MatchAny))
+	if (Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
+	if (Matches4("CREATE", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
@@ -2002,15 +2035,15 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
 		COMPLETE_WITH_LIST3("WITH HOLD", "WITHOUT HOLD", "FOR");
 
-/* DELETE --- can be inside EXPLAIN, RULE, etc */
+/* DELETE */
 	/* ... despite which, only complete DELETE with FROM at start of line */
 	if (Matches1("DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
-	if (TailMatches2("DELETE", "FROM"))
+	if (Matches2("DELETE", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete DELETE FROM <table> */
-	if (TailMatches3("DELETE", "FROM", MatchAny))
+	if (Matches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
 	/* XXX: implement tab completion for DELETE ... USING */
 
@@ -2050,14 +2083,17 @@ psql_completion_internal(const char *text, char **previous_words,
 									  ADDLIST1("CONCURRENTLY"));
 	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
+	if (HeadMatches3("DROP", "INDEX", "CONCURRENTLY"))
+		COLLAPSE(3, 1);
 	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
-		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
 	if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+
+	/* DROP VIEW is suggested as a general thing */
+
 	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 
@@ -2129,13 +2165,23 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"ANALYZE", "VERBOSE");
-	if (Matches2("EXPLAIN", "ANALYZE"))
+	if (HeadMatches2("EXPLAIN", "ANALYZE"))
+		COLLAPSE(2, 1);
+	if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"VERBOSE");
-	if (Matches2("EXPLAIN", "VERBOSE") ||
-			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
+	if (HeadMatches2("EXPLAIN", "VERBOSE"))
+		COLLAPSE(2, 1);
+	if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 
+	/* complete on individual syntaxes here after */
+	if (Matches2("EXPLAIN", "SELECT|INSERT|DELETE|UPDATE|DECLARE"))
+	{
+		COLLAPSE(1, 1);
+		return psql_completion_internal(text, previous_words, WORD_COUNT());
+	}
+
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
 	if (Matches1("FETCH|MOVE"))
@@ -2171,9 +2217,9 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatches2("FOREIGN", "SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
 
-/* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* GRANT && REVOKE */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	if (TailMatches1("GRANT|REVOKE"))
+	if (Matches1("GRANT|REVOKE"))
 		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
 			ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
 					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
@@ -2183,13 +2229,13 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	if (TailMatches2("GRANT|REVOKE", MatchAny))
+	if (Matches2("GRANT|REVOKE", MatchAny))
 	{
 		if (TailMatches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
-		if (TailMatches2("GRANT", MatchAny))
+		if (HeadMatches1("GRANT"))		/* GRANT roles */
 			COMPLETE_WITH_CONST("TO");
-		else
+		else							/* REVOKE roles */
 			COMPLETE_WITH_CONST("FROM");
 	}
 
@@ -2204,7 +2250,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
 	 */
-	if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
+	if (Matches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tsvmf,
 			   ADDLIST15("ALL FUNCTIONS IN SCHEMA",
 						 "ALL SEQUENCES IN SCHEMA",
@@ -2222,11 +2268,11 @@ psql_completion_internal(const char *text, char **previous_words,
 						 "TABLESPACE",
 						 "TYPE"));
 
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
+	if (Matches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
 							"TABLES IN SCHEMA");
 
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+	if (Matches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/*
@@ -2235,27 +2281,11 @@ psql_completion_internal(const char *text, char **previous_words,
 	 *
 	 * Complete "GRANT/REVOKE * ON *" with "TO/FROM".
 	 */
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
+	if (Matches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
-		if (TailMatches1("DATABASE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-		if (TailMatches1("DOMAIN"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains);
-		if (TailMatches1("FUNCTION"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
-		if (TailMatches1("LANGUAGE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-		if (TailMatches1("SCHEMA"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-		if (TailMatches1("SEQUENCE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
-		if (TailMatches1("TABLE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf);
-		if (TailMatches1("TABLESPACE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-		if (TailMatches1("TYPE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
-		if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
+		if (TailMatches1("DATABASE|DOMAIN|FUNCTION|LANGUAGE|SCHEMA|SEQUENCE|TABLE|TABLESPACE|TYPE"))
+			COMPLETE_THING(-1);
+		if (HeadMatches1("GRANT"))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2276,27 +2306,13 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("FROM");
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
-	{
-		if (TailMatches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
-			COMPLETE_WITH_CONST("TO");
-		else
-			COMPLETE_WITH_CONST("FROM");
-	}
-
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
-	{
-		if (TailMatches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
-			COMPLETE_WITH_CONST("TO");
-		else
-			COMPLETE_WITH_CONST("FROM");
-	}
-
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
+	if (Matches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny) ||
+		Matches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny) ||
+		Matches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
 	{
-		if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (HeadMatches1("GRANT"))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2312,29 +2328,29 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("IMPORT", "FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
-/* INSERT --- can be inside EXPLAIN, RULE, etc */
+/* INSERT */
 	/* Complete INSERT with "INTO" */
-	if (TailMatches1("INSERT"))
+	if (Matches1("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
-	if (TailMatches2("INSERT", "INTO"))
+	if (Matches2("INSERT", "INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete "INSERT INTO <table> (" with attribute names */
-	if (TailMatches4("INSERT", "INTO", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+	if (Matches4("INSERT", "INTO", MatchAny, "("))
+		COMPLETE_WITH_ATTR(prev2_wd);
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES"
 	 */
-	if (TailMatches3("INSERT", "INTO", MatchAny))
+	if (Matches3("INSERT", "INTO", MatchAny))
 		COMPLETE_WITH_LIST5("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES");
 
 	/*
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE"
 	 */
-	if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
+	if (Matches4("INSERT", "INTO", MatchAny, MatchAny) &&
 			 ends_with(prev_wd, ')'))
 		COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES");
 
@@ -2352,22 +2368,26 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* For the following, handle the case of a single table only for now */
 
+	/* Remove TABLE and ONLY from LOCK */
+	if (HeadMatches3("LOCK", "TABLE", MatchAny))
+		COLLAPSE(2, 1);
+	if (HeadMatches3("LOCK", "ONLY", MatchAny))
+		COLLAPSE(2, 1);
+
 	/* Complete LOCK [TABLE] <table> with "IN" */
-	if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
-			 Matches3("LOCK", "TABLE", MatchAny))
+	if (Matches2("LOCK", MatchAnyExcept("TABLE")))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	if (Matches3("LOCK", MatchAny, "IN") ||
-			 Matches4("LOCK", "TABLE", MatchAny, "IN"))
+	if (Matches3("LOCK", MatchAny, "IN"))
 		COMPLETE_WITH_LIST8("ACCESS SHARE MODE",
 							"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
 							"SHARE UPDATE EXCLUSIVE MODE", "SHARE MODE",
 							"SHARE ROW EXCLUSIVE MODE",
 							"EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE");
 
-/* NOTIFY --- can be inside EXPLAIN, RULE, etc */
-	if (TailMatches1("NOTIFY"))
+/* NOTIFY  */
+	if (Matches1("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
 
 /* OPTIONS */
@@ -2382,11 +2402,18 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatches3("FROM", MatchAny, "ORDER"))
 		COMPLETE_WITH_CONST("BY");
 	if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 
 /* PREPARE xx AS */
-	if (Matches3("PREPARE", MatchAny, "AS"))
-		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
+	if (HeadMatches1("PREPARE"))
+	{
+		if (Matches3("PREPARE", MatchAny, "AS"))
+			COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
+
+		/* Complete for indivisual command */
+		SHIFT_TO_LAST1("SELECT|UPDATE|INSERT|DELETE");
+		return psql_completion_internal(text, previous_words, WORD_COUNT());
+	}
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -2447,8 +2474,9 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("ON", "FOR");
 	if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	if (Matches3("SECURITY", "LABEL", "ON") ||
-			 Matches5("SECURITY", "LABEL", "FOR", MatchAny, "ON"))
+	if (HeadMatches4("SECURITY", "LABEL", "FOR", MatchAny))
+		COLLAPSE(3, 2);
+	if (Matches3("SECURITY", "LABEL", "ON"))
 	{
 		static const char *const list_SECURITY_LABEL[] =
 		{"TABLE", "COLUMN", "AGGREGATE", "DATABASE", "DOMAIN",
@@ -2467,44 +2495,51 @@ psql_completion_internal(const char *text, char **previous_words,
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
 	if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars,
-							ADDLIST6("CONSTRAINTS", "TRANSACTION", "SESSION",
-									 "ROLE", "TABLESPACE", "ALL"));
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_set_vars,
+							   ADDLIST6("CONSTRAINTS", "TRANSACTION", "SESSION",
+										"ROLE", "TABLESPACE", "ALL"));
 	if (Matches1("SHOW"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars, 
-							ADDLIST2("SESSION AUTHORIZATION", "ALL"));
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_show_vars, 
+							   ADDLIST2("SESSION AUTHORIZATION", "ALL"));
 	/* Complete "SET TRANSACTION" */
 	if (Matches2("SET", "TRANSACTION"))
 		COMPLETE_WITH_LIST5("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	if (Matches2("BEGIN|START", "TRANSACTION") ||
-		Matches2("BEGIN", "WORK") ||
-		Matches1("BEGIN") ||
-		Matches5("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
-		COMPLETE_WITH_LIST4("ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
-		Matches2("BEGIN", "NOT") ||
-		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
-		COMPLETE_WITH_CONST("DEFERRABLE");
-	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
-		Matches2("BEGIN", "ISOLATION") ||
-		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
-		COMPLETE_WITH_CONST("LEVEL");
-	if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
-		Matches3("BEGIN", "ISOLATION", "LEVEL") ||
-		Matches7("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
-		COMPLETE_WITH_LIST3("READ", "REPEATABLE READ", "SERIALIZABLE");
-	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
-		Matches4("BEGIN", "ISOLATION", "LEVEL", "READ") ||
-		Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
-		COMPLETE_WITH_LIST2("UNCOMMITTED", "COMMITTED");
-	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
-		Matches4("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
-		Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
-		COMPLETE_WITH_CONST("READ");
-	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
-		Matches2("BEGIN", "READ") ||
-		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
-		COMPLETE_WITH_LIST2("ONLY", "WRITE");
+	if (HeadMatches2("BEGIN", "WORK|TRANSACTION"))
+		COLLAPSE(2, 1);
+	{
+		int shift = 0;
+
+		if (HeadMatches2("START", "TRANSACTION"))
+			shift = 2;
+		if (HeadMatches1("BEGIN"))
+			shift = 1;
+		if (HeadMatches5("SET", "SESSION", "CHARACTERISTICS", "AS",
+						 "TRANSACTION"))
+			shift = 5;
+
+		if (shift > 0)
+		{
+			/* complete with transaction mode */
+			HEAD_SHIFT(shift);
+
+			if (WORD_COUNT() == 0)
+				COMPLETE_WITH_LIST4("ISOLATION LEVEL", "READ", "DEFERRABLE",
+									"NOT DEFERRABLE");
+			if (Matches1("NOT"))
+				COMPLETE_WITH_CONST("DEFERRABLE");
+			if (Matches1("ISOLATION"))
+				COMPLETE_WITH_CONST("LEVEL");
+			if (Matches2("ISOLATION", "LEVEL"))
+				COMPLETE_WITH_LIST3("READ", "REPEATABLE READ", "SERIALIZABLE");
+			if (Matches3("ISOLATION", "LEVEL", "REPEATABLE"))
+				COMPLETE_WITH_CONST("READ");
+			if (Matches3("ISOLATION", "LEVEL", "READ"))
+				COMPLETE_WITH_LIST2("UNCOMMITTED", "COMMITTED");
+			if (Matches1("READ"))
+				COMPLETE_WITH_LIST2("ONLY", "WRITE");
+			COMPLETE_WITH_CONST("");
+		}
+	}
 	/* SET CONSTRAINTS */
 	if (Matches2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_constraints_with_schema,
@@ -2601,18 +2636,18 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches1("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
 
-/* UPDATE --- can be inside EXPLAIN, RULE, etc */
+/* UPDATE  */
 	/* If prev. word is UPDATE suggest a list of tables */
-	if (TailMatches1("UPDATE"))
+	if (Matches1("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete UPDATE <table> with "SET" */
-	if (TailMatches2("UPDATE", MatchAny))
+	if (Matches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
 	/* Complete UPDATE <table> SET with list of attributes */
-	if (TailMatches3("UPDATE", MatchAny, "SET"))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+	if (Matches3("UPDATE", MatchAny, "SET"))
+		COMPLETE_WITH_ATTR(prev2_wd);
 	/* UPDATE <table> SET <attr> = */
-	if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
+	if (Matches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
@@ -2671,7 +2706,7 @@ psql_completion_internal(const char *text, char **previous_words,
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
 	if (TailMatches2(MatchAny, "WHERE"))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+		COMPLETE_WITH_ATTR(prev2_wd);
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
@@ -2844,18 +2879,14 @@ psql_completion_internal(const char *text, char **previous_words,
 	 */
 	else
 	{
-		int			i;
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
 
-		for (i = 0; words_after_create[i].name; i++)
+		if (ent)
 		{
-			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
-			{
-				if (words_after_create[i].query)
-					COMPLETE_WITH_QUERY(words_after_create[i].query);
-				if (words_after_create[i].squery)
-					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery);
-				break;
-			}
+			if (ent->query)
+				COMPLETE_WITH_QUERY(ent->query);
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery);
 		}
 	}
 
@@ -3366,6 +3397,18 @@ complete_from_files(const char *text, int state)
 
 /* HELPER FUNCTIONS */
 
+/*
+ * Return the index (reverse to the index of previous_words) of the tailmost
+ * (topmost in the array) appearance of w.
+ */
+static int
+find_last_index_of(char *w, char **previous_words, int len)
+{
+	int i;
+
+	for (i = 0 ; i < len && !word_matches(w, previous_words[i]) ; i++);
+	return i < len ? (len - i - 1) : 0;
+}
 
 /*
  * Make a pg_strdup copy of s and convert the case according to
@@ -3665,6 +3708,24 @@ get_guctype(const char *varname)
 	return guctype;
 }
 
+/*
+ * Finds the entry in words_after_create[] that matches the word.
+ * NULL if not found.
+ */
+static const pgsql_thing_t *
+find_thing_entry(char *word)
+{
+	int			i;
+
+	for (i = 0; words_after_create[i].name; i++)
+	{
+		if (pg_strcasecmp(word, words_after_create[i].name) == 0)
+			return words_after_create + i;
+	}
+
+	return NULL;
+}
+
 #ifdef NOT_USED
 
 /*
-- 
2.9.2

0005-Add-suggestion-for-IF-NOT-EXISTS-for-some-syntaxes.patchtext/x-patch; charset=us-asciiDownload
From 887b7c483ef7a494fb107f9a7cf7d35182f47b7b Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Thu, 15 Sep 2016 20:49:45 +0900
Subject: [PATCH 5/6] Add suggestion for IF (NOT) EXISTS for some syntaxes

Add suggestion for IF EXISTS or IF NOT EXISTS for some arbitrary
syntaxes, ALTER TABLE/ALTER FOREIGN TABLE/ALTER INDEX/
ALTER MATERIALIZED VIEW/ALTER DOMAIN DROP CONSTRAINT/ALTER SEQUENCE/
ALTER VIEW/ALTER POLICY/ALTER TABLE DROP COLUMN/
ALTER TABLE DROP CONSTRAINT/CREATE INDEX/CREATE SEQUENCE/
CREATE TABLE/CREATE VIEW/DROP INDEX/DROP (MATERIALIZED) VIEW/
DROP USER MAPPING
---
 src/bin/psql/tab-complete.c | 180 +++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 168 insertions(+), 12 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 7a880d9..3b10b85 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1074,7 +1074,9 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* ALTER TABLE */
 	if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
-									  ADDLIST1("ALL IN TABLESPACE"));
+								   ADDLIST2("ALL IN TABLESPACE", "IF EXISTS"));
+	if (HeadMatches4("ALTER", "TABLE", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
 
 	/* ALTER something */
 	if (Matches1("ALTER"))
@@ -1177,6 +1179,14 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
+	/* ALTER FOREIGN TABLE */
+	if (Matches3("ALTER", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_foreign_tables,
+									  ADDLIST1("IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (HeadMatches5("ALTER", "FOREIGN", "TABLE", "IF", "EXISTS"))
+		COLLAPSE(4, 2);
+
 	/* ALTER FOREIGN TABLE <name> */
 	if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
@@ -1191,7 +1201,11 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* ALTER INDEX */
 	if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
-									  ADDLIST1("ALL IN TABLESPACE"));
+									  ADDLIST2("ALL IN TABLESPACE", "IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (HeadMatches4("ALTER", "INDEX", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+
 	/* ALTER INDEX <name> */
 	if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
@@ -1220,7 +1234,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* ALTER MATERIALIZED VIEW */
 	if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_matviews,
-									  ADDLIST1("ALL IN TABLESPACE"));
+								   ADDLIST2("ALL IN TABLESPACE", "IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (HeadMatches5("ALTER", "MATERIALIZED", "VIEW", "IF", "EXISTS"))
+		COLLAPSE(4, 2);
 
 	/* ALTER USER,ROLE <name> */
 	if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
@@ -1276,6 +1293,19 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
+		if (word_matches("DROP", prev2_wd))
+			COMPLETE_WITH_QUERY_KW(Query_for_constraint_of_type,
+								   ADDLIST1("IF EXISTS"));
+		else
+			COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+	}
+	/* Remove optional words for further completion */
+	if (HeadMatches7("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT", "IF", "EXISTS"))
+		COLLAPSE(6, 2);
+	/*  Complete constraint name again without IF EXISTS */
+	if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
 	}
 	/* ALTER DOMAIN <sth> RENAME */
@@ -1288,6 +1318,16 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* ALTER DOMAIN <sth> SET */
 	if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
+	/* ALTER SEQUENCE */
+	if (Matches2("ALTER", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_sequences,
+									  ADDLIST1("IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (HeadMatches4("ALTER", "SEQUENCE", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches2("ALTER", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
 	/* ALTER SEQUENCE <name> */
 	if (Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
@@ -1313,6 +1353,16 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY_KW(Query_for_list_of_alter_system_set_vars,
 							   ADDLIST1("ALL"));
+	/* ALTER VIEW */
+	if (Matches2("ALTER", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_views,
+									  ADDLIST1("IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (HeadMatches4("ALTER", "VIEW", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches2("ALTER", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	/* ALTER VIEW <name> */
 	if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
@@ -1324,6 +1374,12 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER POLICY <name> */
 	if (Matches2("ALTER", "POLICY"))
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_policies, ADDLIST1("IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (Matches4("ALTER", "POLICY", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches2("ALTER", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* ALTER POLICY <name> ON */
 	if (Matches3("ALTER", "POLICY", MatchAny))
@@ -1463,8 +1519,13 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+		COMPLETE_WITH_ATTR_KW(prev3_wd, ADDLIST1("IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (HeadMatches7("ALTER", "TABLE", MatchAny, "DROP", "COLUMN", "IF", "EXISTS"))
+		COLLAPSE(6, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd);
-
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
@@ -1472,6 +1533,19 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
+		if (word_matches("DROP", prev2_wd))
+			COMPLETE_WITH_QUERY_KW(Query_for_constraint_of_table,
+								ADDLIST1("IF EXISTS"));
+		else
+			COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+	}
+	/* Remove IF EXISTS for further completion */
+	if (HeadMatches7("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT", "IF", "EXISTS"))
+		COLLAPSE(6, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
 	}
 	/* Remove COLUMN just after ALTER */
@@ -1614,7 +1688,18 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * of attributes
 	 */
 	if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
-		COMPLETE_WITH_ATTR(prev3_wd);
+	{
+		if (word_matches("DROP", prev2_wd))
+			COMPLETE_WITH_ATTR_KW(prev3_wd, ADDLIST1("IF EXISTS"));
+		else
+			COMPLETE_WITH_ATTR(prev3_wd);
+	}
+	/* Remove IF EXISTS for further completion */
+	if (HeadMatches7("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE", "IF", "EXISTS"))
+		COLLAPSE(6, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE"))
+			COMPLETE_WITH_ATTR(prev3_wd);	
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
 	if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
@@ -1762,6 +1847,13 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	if (Matches2("CREATE", "EXTENSION"))
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_available_extensions,
+							   ADDLIST1("IF NOT EXISTS"));
+	/* Remove IF NOT EXISTS for further completion*/
+	if (HeadMatches5("CREATE", "EXTENSION", "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+	/* Complete again without IF NOT EXISTS */
+	if (Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
 	if (Matches3("CREATE", "EXTENSION", MatchAny))
@@ -1792,14 +1884,20 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* Complete with index names as category suggestion and possible keywords */
 	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
-									  ADDLIST2("ON", "CONCURRENTLY"));
+									  ADDLIST3("ON", "CONCURRENTLY", "IF NOT EXISTS"));
 	/* Remove CONCURRENTLY for further completion */
 	if (HeadMatches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COLLAPSE(3, 1);
 	/* Complete with existing index names as word category suggestion */
 	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
-									  ADDLIST1("ON"));
+									  ADDLIST2("ON", "IF NOT EXISTS"));
+	/* Remove IF NOT EXISTS for further completion */
+	if (HeadMatches5("CREATE", "INDEX", "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+	/* Complete again without IF NOT EXISTS */
+	if (Matches2("CREATE", "INDEX"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	/* Suggest ON just after index name */
 	if (Matches3("CREATE", "INDEX", MatchAnyExcept("ON")))
 		COMPLETE_WITH_CONST("ON");
@@ -1892,6 +1990,13 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* CREATE SEQUENCE */
 	if (Matches2("CREATE", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_sequences,
+									  ADDLIST1("IF NOT EXISTS"));
+	/* Remove IF NOT EXISTS for further completion */
+	if (HeadMatches5("CREATE", "SEQUENCE", "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+	/* Complete again without IF NOT EXISTS */
+	if (Matches2("CREATE", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
 	if (Matches3("CREATE", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO",
@@ -1917,6 +2022,13 @@ psql_completion_internal(const char *text, char **previous_words,
 		COLLAPSE(2, 1);
 	/* Complete CREATE TABLE with existing table names */
 	if (Matches2("CREATE", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
+									  ADDLIST1("IF NOT EXISTS"));
+	/* Remove IF NOT EXIST for further completion */
+	if (HeadMatches5("CREATE", "TABLE", "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+	/* Complete again without IF NOT EXISTS */
+	if (Matches2("CREATE", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 /* CREATE TABLESPACE */
@@ -2001,6 +2113,16 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
 /* CREATE VIEW */
+	/* Complete CREATE VIEW with name */
+	if (Matches2("CREATE", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_views,
+									  ADDLIST1("IF NOT EXISTS"));
+	/* Remove IF NOT EXISTS for further completion */
+	if (HeadMatches5("CREATE", "VIEW", "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+	/* Complete again without IF NOT EXISTS */
+	if (Matches2("CREATE", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	/* Complete CREATE VIEW <name> with AS */
 	if (Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
@@ -2080,20 +2202,43 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* DROP INDEX */
 	if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
-									  ADDLIST1("CONCURRENTLY"));
-	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
+									  ADDLIST2("CONCURRENTLY", "IF EXISTS"));
+		if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
+			COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+										  ADDLIST1("IF EXISTS"));
 	if (HeadMatches3("DROP", "INDEX", "CONCURRENTLY"))
 		COLLAPSE(3, 1);
+	/* Remove IF EXISTS for further completion */
+	if (HeadMatches4("DROP", "INDEX", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches2("DROP", "INDEX"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
+	/* DROP VIEW */
+	if (Matches2("DROP", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_views,
+									  ADDLIST1("IF EXISTS"));
+	/* Remove IF EXISTS for further completion */
+	if (HeadMatches4("DROP", "VIEW", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+	/* Complet again without IF EXISTS */
+	if (Matches2("DROP", "VIEW"))
+		COMPLETE_THING(-1);
+
 	/* DROP MATERIALIZED VIEW */
 	if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 
-	/* DROP VIEW is suggested as a general thing */
-
+	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_matviews,
+									  ADDLIST1("IF EXISTS"));
+	/* Remove IF EXISTS for further completion */
+	if (HeadMatches5("DROP", "MATERIALIZED", "VIEW", "IF", "EXISTS"))
+		COLLAPSE(4, 2);
+	/* Complet again without IF EXISTS */
 	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 
@@ -2652,6 +2797,17 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* USER MAPPING */
 	if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
+	{
+		if (word_matches("DROP", prev3_wd))
+			COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR");
+		else
+			COMPLETE_WITH_CONST("FOR");
+	}
+	/* Remove IF EXISTS for further completion */
+	if (HeadMatches5("DROP", "USER", "MAPPING", "IF", "EXISTS"))
+		COLLAPSE(4, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches3("DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
 	if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
-- 
2.9.2

0006-Add-README-for-tab-completion.patchtext/x-patch; charset=us-asciiDownload
From 6d2cde0f44c880f7edc4fe3527ef02ca28248e74 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Wed, 26 Oct 2016 12:06:33 +0900
Subject: [PATCH 6/6] Add README for tab-completion.

---
 src/bin/psql/README.completion | 165 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 165 insertions(+)
 create mode 100644 src/bin/psql/README.completion

diff --git a/src/bin/psql/README.completion b/src/bin/psql/README.completion
new file mode 100644
index 0000000..65981e4
--- /dev/null
+++ b/src/bin/psql/README.completion
@@ -0,0 +1,165 @@
+Word completion of interactive psql
+===================================
+
+psql supports word completion on interactive input. The core function
+of the feature is psql_completion_internal in tab-complete.c. A bunch
+of macros are provided in order to make it easier to read and maintain
+the completion code. psql_completion is called with reference console
+input stored in char ** previous_words in reverse order but developers
+don't need to be aware of the detail. Most of the operations can be
+described using the provided macros.
+
+Basic structure of the completion code
+--------------------------------------
+
+The main part of the function is just a series of completion
+definitions, where the first match wins. Each definition basically is
+in the following shape.
+
+   if (*matching expression*)
+      *enumeration of words for completion, then return*
+
+The matching expression is examined against previous_words which
+contains the whole command line before the last space. The completion
+code enumerates the expected words. For example, for "CREATE <tab>"
+the word list to be matched is ["CREATE"] and the prefix for
+completion is nothing. For "CREATE INDEX i", the list is ["CREATE",
+"INDEX"] and the prefix for enumeration is "i".
+
+
+Matching expression macros
+--------------------------
+There are four types of matching expression macros.
+
+- MatchesN(word1, word2 .. , wordN)
+
+ true iff the word list is exactly the same as the paremeter.
+
+- HeadMatchesN(word1, word2 .., wordN)
+
+ true iff the first N words in the word list matches the parameter.
+
+- TailMatchesN(word1, word2 .., wordN)
+
+ true iff the last N words in the word list matches the parameter.
+
+- MidMatchesN(pos, word1, word2 .., wordN)
+
+ true iff N successive words starts from pos in the word list matches
+ the parameter. The position is 1-based.
+
+Special matching words
+----------------------
+
+A defined symbol MatchAny matches any word. If you want to match any
+of several words, multiple words concatenated by '|' can be
+used. "CREATE|UPDATE" matches any of "CREATE" and "UPDATE".
+
+
+Enumeration macros
+-----------------
+There are N types of word enumeration macros.
+
+- COMPLETE_WITH_QUERY(query), COMPLETE_WITH_QUERY_KW(query, addon)
+
+  Suggest completion words acquired using the given query. The details
+  of the query is seen in the comment for _complete_from_query(). Word
+  matching is case-sensitive.
+
+  The latter form takes an additional parameter, which should be a
+  fragment of query starts with " UNION " followed by a query string
+  which gives some additional words. For case-insensitive suggestion,
+  this could be as simple as a static query string but for
+  case-sensitive case, where the letter case of suggested words
+  follows input, ADDLISTN() macro can be used.
+
+- COMPLETE_WITH_SCHEMA_QUERY(squery),
+  COMPLETE_WITH_SCHEMA_QUERY_KW(squery, addon)
+
+  Suggest words based on a "schema query", which is a struct that
+  containing parameters. You will see the details in the comment for
+  _complete_from_query(). Word maching is case-sensitive.
+
+  Just same as COMPLETE_WITH_QUERY_KW, the latter form takes a
+  fragment query same to that for COMPLETE_WITH_QUERY_KW.
+
+- COMPLETE_WITH_LIST_CS(list)
+
+  Suggest completion words given as a string array. Word matching is
+  case-sensitive.
+
+- COMPLETE_WITH_LIST_CSN(s1, s2.. ,sN)
+
+  Shortcut for COMPLETE_WITH_LIST_CS.
+
+- COMPLETE_WITH_LIST(list)
+
+  Same as COMPLETE_WITH_LIST_CS except that word matching is
+  case-insensitive and the letter case of suggested words is
+  determined according to COMP_KEYWORD_CASE.
+
+- COMPLETE_WITH_LISTN(s1, s2.. ,sN)
+
+  Shortcut for COMPLETE_WITH_LIST.
+
+- COMPLETE_WITH_CONST(string)
+
+  Same as COMPLETE_WITH_LIST but with just one suggestion.
+
+- COMPLETE_WITH_ATTR(relation, addon)
+
+  Suggest attribute names for the given relation. Word matching is
+  case-sensitve.
+
+- COMPLETE_WITH_FUNCTION_ARG(function)
+
+  Suggest function name for the given SQL function. Word matching is
+  case-sensitve.
+
+- COMPLETE_THING(relpos)
+
+  Suggest any object name designated by the word at relpos from the
+  current word. COMPLETE_THING(-1) for "... TABLE " enumerates names
+  of all available tables.
+
+
+Additional keywords for COMPLETE_WITH(_SCHEMA)_QUERY
+----------------------------------------------------
+
+Some syntaxes need suggestion by mixture of object names and
+keywords. Object names are enumarated with
+COMPLETE_WITH(_SCHEMA)_QUERY but keywords should be added manually
+onto them. COMPLETE_WITH(_SCHEMA)_QUERY_KW takes a fragment query that
+gives keywords to be suggested with the object names. Since the
+framgent queriy is just appended to the main query so it is in the
+form of ' UNION <any sql>' that adds arbitrary number of tuples
+contain a text value. For example,
+
+" UNION ALL SELECT 'CURRENT_USER' UNION ALL SELECT 'PUBLIC'"
+
+If you want the case of the keywords to follow input, ADDLISTN() macro
+provides a fragment query containing such keywords.
+
+ADDLIST3("CURRENT_USER", "PUBLIC", "USER") for input 'c' returns a
+fragment query equivalent to the following,
+
+" UNION ALL SELECT 'current_user' UNION ALL SELECT 'public'"
+
+
+Removing "NOISE" words
+----------------------------------------------------
+
+Many syntaxes has "NOISE" words, that is, words that has no effect on
+the following completion. For example, the existence of the second
+word in "CREATE UNIQUE INDEX" makes no difference for further
+completion behavior. Removing such words makes further completion code
+simpler.
+
+COLLAPSE(s, l) removes l words starts from s from previous_words.
+
+if (Matches("CREATE", "UNIQUE", "INDEX"))
+   COLLAPSE(2, 1);
+
+After the above code, the previous_words is ["CREATE", "INDEX"] so the
+following completion definitions need not care about the removed words.
+
-- 
2.9.2

#52Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#51)
6 attachment(s)
Re: IF (NOT) EXISTS in psql-completion

Hello, I rebased this patch on the current master.

At Mon, 31 Oct 2016 10:15:48 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <20161031.101548.162143279.horiguchi.kyotaro@lab.ntt.co.jp>

Anyway, I fixed space issues and addressed the
COMPLETE_WITH_QUERY()'s almost unused parameter problem. And
tried to write a README files.

tab-complete.c have gotten some improvements after this time.

577f0bdd2b8904cbdfde6c98f4bda6fd93a05ffc psql: Tab completion for renaming enum values.
927d7bb6b120a2ca09a164898f887eb850b7a329 Improve tab completion for CREATE TRIGGER.
1d15d0db50a5f39ab69c1fe60f2d5dcc7e2ddb9c psql: Tab-complete LOCK [TABLE] ... IN {ACCESS|ROW|SHARE}.

The attached patchset is rebsaed on the master including these
patches.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

0001-Refactoring-tab-complete-to-make-psql_completion-cod.patchtext/x-patch; charset=us-asciiDownload
From 30222c2f13062bd69aa6fe59e901812431fd175d Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Wed, 14 Sep 2016 11:59:04 +0900
Subject: [PATCH 1/6] Refactoring tab-complete to make psql_completion code
 simpler.

psql_completion consists of a long-long sequence of "else-if"s and
succeeding finishing code. This structure enforces us to use a very
tricky way to put something other than matching-completion pairs in
the midst of the sequence. This patch separates the
matching-completion pairs into individual function
"psql_completion_internal". Returning at the points of completion let
us free from the else-if sequence and let us put anything we want into
the matching-completion sequence.

Addition to that the amount of the code seems to be a good reason to
move it out of tab-complete.c. This patch searates it out as
tab-complete-macros.h
---
 src/bin/psql/Makefile              |    2 +
 src/bin/psql/tab-complete-macros.h |  430 +++++++++++
 src/bin/psql/tab-complete.c        | 1372 ++++++++++++++----------------------
 3 files changed, 957 insertions(+), 847 deletions(-)
 create mode 100644 src/bin/psql/tab-complete-macros.h

diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index 1f6a289..51f88ba 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -47,6 +47,8 @@ ifeq ($(GCC),yes)
 psqlscanslash.o: CFLAGS += -Wno-error
 endif
 
+tab-complete.o: tab-complete-macros.h
+
 distprep: sql_help.h psqlscanslash.c
 
 install: all installdirs
diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
new file mode 100644
index 0000000..97ffcd1
--- /dev/null
+++ b/src/bin/psql/tab-complete-macros.h
@@ -0,0 +1,430 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2016, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/tab-complete-macros.h
+ */
+#ifndef TAB_COMPLETE_MACROS_H
+#define TAB_COMPLETE_MACROS_H
+
+/*
+ * For compactness, we use these macros to reference previous_words[].
+ * Caution: do not access a previous_words[] entry without having checked
+ * previous_words_count to be sure it's valid.  In most cases below, that
+ * check is implicit in a TailMatches() or similar macro, but in some places
+ * we have to check it explicitly.
+ */
+#define prev_wd   (previous_words[0])
+#define prev2_wd  (previous_words[1])
+#define prev3_wd  (previous_words[2])
+#define prev4_wd  (previous_words[3])
+#define prev5_wd  (previous_words[4])
+#define prev6_wd  (previous_words[5])
+#define prev7_wd  (previous_words[6])
+#define prev8_wd  (previous_words[7])
+#define prev9_wd  (previous_words[8])
+
+/*
+ * Return the index in previous_words for index from the beginning. n is
+ * 1-based and the result is 0-based.
+ */
+#define HEAD_INDEX(n) \
+	(previous_words_count - (n))
+
+/*
+ * Macros for matching the last N words before point, and after head_sift,
+ * case-insensitively.
+ */
+#define TailMatches1(p1) \
+	(previous_words_count >= 1 && \
+	 word_matches(p1, prev_wd))
+
+#define TailMatches2(p2, p1) \
+	(previous_words_count >= 2 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd))
+
+#define TailMatches3(p3, p2, p1) \
+	(previous_words_count >= 3 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd))
+
+#define TailMatches4(p4, p3, p2, p1) \
+	(previous_words_count >= 4 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd))
+
+#define TailMatches5(p5, p4, p3, p2, p1) \
+	(previous_words_count >= 5 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd))
+
+#define TailMatches6(p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 6 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd))
+
+#define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 7 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd))
+
+#define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 8 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd) && \
+	 word_matches(p8, prev8_wd))
+
+#define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 9 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd) && \
+	 word_matches(p8, prev8_wd) && \
+	 word_matches(p9, prev9_wd))
+
+	/*
+	 * Macros for matching the last N words before point, and after
+	 * head_shift, case-sensitively.
+	 */
+#define TailMatchesCS1(p1) \
+	(previous_words_count >= 1 && \
+	 word_matches_cs(p1, prev_wd))
+#define TailMatchesCS2(p2, p1) \
+	(previous_words_count >= 2 && \
+	 word_matches_cs(p1, prev_wd) && \
+	 word_matches_cs(p2, prev2_wd))
+
+	/*
+	 * Macros for matching N words exactly to the line, and after head_shift,
+	 * case-insensitively.
+	 */
+#define Matches1(p1) \
+	(previous_words_count == 1 && \
+	 TailMatches1(p1))
+#define Matches2(p1, p2) \
+	(previous_words_count == 2 && \
+	 TailMatches2(p1, p2))
+#define Matches3(p1, p2, p3) \
+	(previous_words_count == 3 && \
+	 TailMatches3(p1, p2, p3))
+#define Matches4(p1, p2, p3, p4) \
+	(previous_words_count == 4 && \
+	 TailMatches4(p1, p2, p3, p4))
+#define Matches5(p1, p2, p3, p4, p5) \
+	(previous_words_count == 5 && \
+	 TailMatches5(p1, p2, p3, p4, p5))
+#define Matches6(p1, p2, p3, p4, p5, p6) \
+	(previous_words_count == 6 && \
+	 TailMatches6(p1, p2, p3, p4, p5, p6))
+#define Matches7(p1, p2, p3, p4, p5, p6, p7) \
+	(previous_words_count == 7 && \
+	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
+#define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
+	(previous_words_count == 8 && \
+	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
+#define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
+	(previous_words_count == 9 && \
+	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
+
+/*
+ * Macros for matching N words after head_shift + sth in the line, regardless
+ * of what is after them, case-insensitively.
+ */
+#define MidMatches1(s, p1) \
+	(HEAD_INDEX(s) >=0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]))
+
+#define MidMatches2(s, p1, p2) \
+	(HEAD_INDEX((s) + 1) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]))
+
+#define MidMatches3(s, p1, p2, p3) \
+	(HEAD_INDEX((s) + 2) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]))
+
+#define MidMatches4(s, p1, p2, p3, p4) \
+	(HEAD_INDEX((s) + 3) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]))
+
+#define MidMatches5(s, p1, p2, p3, p4, p5) \
+	(HEAD_INDEX((s) + 4) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&	\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&		\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]))
+
+#define MidMatches6(s,p1, p2, p3, p4, p5, p6)		\
+	(HEAD_INDEX((s) + 5) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]))
+
+#define MidMatches7(s,p1, p2, p3, p4, p5, p6, p7)	\
+	(HEAD_INDEX((s) + 6) >= 0 &&							\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&			\
+	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]))
+
+#define HeadMatches1(p1) \
+	MidMatches1(1, p1)
+#define HeadMatches2(p1, p2) \
+	MidMatches2(1, p1, p2)
+#define HeadMatches3(p1, p2, p3) \
+	MidMatches3(1, p1, p2, p3)
+#define HeadMatches4(p1, p2, p3, p4) \
+	MidMatches4(1, p1, p2, p3, p4)
+#define HeadMatches5(p1, p2, p3, p4, p5) \
+	MidMatches5(1, p1, p2, p3, p4, p5)
+#define HeadMatches6(p1, p2, p3, p4, p5, p6) \
+	MidMatches6(1, p1, p2, p3, p4, p5, p6)
+#define HeadMatches7(p1, p2, p3, p4, p5, p6, p7) \
+	MidMatches7(1, p1, p2, p3, p4, p5, p6, p7)
+
+/*
+ * A few macros to ease typing. You can use these to complete the given
+ * string with
+ * 1) The results from a query you pass it. (Perhaps one of those below?)
+ * 2) The results from a schema query you pass it.
+ * 3) The items from a null-pointer-terminated list (with or without
+ *	  case-sensitive comparison; see also COMPLETE_WITH_LISTn, below).
+ * 4) A string constant.
+ * 5) The list of attributes of the given table (possibly schema-qualified).
+ * 6) The list of arguments to the given function (possibly schema-qualified).
+ */
+#define COMPLETE_WITH_QUERY(query) \
+do { \
+	completion_charp = query;	\
+	return completion_matches(text, complete_from_query);	\
+} while (0)
+
+#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
+do { \
+	completion_squery = &(query); \
+	completion_charp = addon; \
+	return completion_matches(text, complete_from_schema_query); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS(list) \
+do { \
+	completion_charpp = list; \
+	completion_case_sensitive = true; \
+	return completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST(list) \
+do { \
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	return completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_CONST(string) \
+do { \
+	completion_charp = string;	\
+	completion_case_sensitive = false; \
+	return completion_matches(text, complete_from_const);	\
+} while (0)
+
+#define COMPLETE_WITH_ATTR(relation, addon) \
+do { \
+	char   *_completion_schema; \
+	char   *_completion_table; \
+\
+	_completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \
+								 false, false, pset.encoding); \
+	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+				   false, false, pset.encoding); \
+	_completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+								false, false, pset.encoding); \
+	if (_completion_table == NULL) \
+	{ \
+		completion_charp = Query_for_list_of_attributes addon; \
+		completion_info_charp = relation; \
+	} \
+	else \
+	{ \
+		completion_charp = Query_for_list_of_attributes_with_schema addon; \
+		completion_info_charp = _completion_table; \
+		completion_info_charp2 = _completion_schema; \
+	} \
+	return completion_matches(text, complete_from_query); \
+} while (0)
+
+#define COMPLETE_WITH_ENUM_VALUE(type) \
+do { \
+	char   *_completion_schema; \
+	char   *_completion_type; \
+\
+	_completion_schema = strtokx(type, " \t\n\r", ".", "\"", 0, \
+								 false, false, pset.encoding); \
+	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+				   false, false, pset.encoding); \
+	_completion_type = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+							   false, false, pset.encoding);  \
+	if (_completion_type == NULL)\
+	{ \
+		completion_charp = Query_for_list_of_enum_values;	\
+		completion_info_charp = type; \
+	} \
+	else \
+	{ \
+		completion_charp = Query_for_list_of_enum_values_with_schema;	\
+		completion_info_charp = _completion_type; \
+		completion_info_charp2 = _completion_schema; \
+	} \
+	return completion_matches(text, complete_from_query);	\
+} while (0)
+
+#define COMPLETE_WITH_FUNCTION_ARG(function) \
+do { \
+	char   *_completion_schema; \
+	char   *_completion_function; \
+\
+	_completion_schema = strtokx(function, " \t\n\r", ".", "\"", 0, \
+								 false, false, pset.encoding); \
+	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+				   false, false, pset.encoding); \
+	_completion_function = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+								   false, false, pset.encoding); \
+	if (_completion_function == NULL) \
+	{ \
+		completion_charp = Query_for_list_of_arguments; \
+		completion_info_charp = function; \
+	} \
+	else \
+	{ \
+		completion_charp = Query_for_list_of_arguments_with_schema;	\
+		completion_info_charp = _completion_function; \
+		completion_info_charp2 = _completion_schema; \
+	} \
+	return completion_matches(text, complete_from_query); \
+} while (0)
+
+/*
+ * These macros simplify use of COMPLETE_WITH_LIST for short, fixed lists.
+ * There is no COMPLETE_WITH_LIST1; use COMPLETE_WITH_CONST for that case.
+ */
+#define COMPLETE_WITH_LIST2(s1, s2) \
+do { \
+	static const char *const list[] = { s1, s2, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST3(s1, s2, s3) \
+do { \
+	static const char *const list[] = { s1, s2, s3, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST4(s1, s2, s3, s4) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST5(s1, s2, s3, s4, s5) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST6(s1, s2, s3, s4, s5, s6) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST7(s1, s2, s3, s4, s5, s6, s7) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST8(s1, s2, s3, s4, s5, s6, s7, s8) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST9(s1, s2, s3, s4, s5, s6, s7, s8, s9) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST10(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+/*
+ * Likewise for COMPLETE_WITH_LIST_CS.
+ */
+#define COMPLETE_WITH_LIST_CS2(s1, s2) \
+do { \
+	static const char *const list[] = { s1, s2, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS3(s1, s2, s3) \
+do { \
+	static const char *const list[] = { s1, s2, s3, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS4(s1, s2, s3, s4) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS5(s1, s2, s3, s4, s5) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+
+
+#endif   /* TAB_COMPLETE_MACROS_H */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b556c00..fd3f68e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -34,6 +34,7 @@
 
 #include "postgres_fe.h"
 #include "tab-complete.h"
+#include "tab-complete-macros.h"
 #include "input.h"
 
 /* If we don't have this, we might as well forget about the whole thing: */
@@ -133,211 +134,6 @@ static const SchemaQuery *completion_squery;	/* to pass a SchemaQuery */
 static bool completion_case_sensitive;	/* completion is case sensitive */
 
 /*
- * A few macros to ease typing. You can use these to complete the given
- * string with
- * 1) The results from a query you pass it. (Perhaps one of those below?)
- * 2) The results from a schema query you pass it.
- * 3) The items from a null-pointer-terminated list (with or without
- *	  case-sensitive comparison; see also COMPLETE_WITH_LISTn, below).
- * 4) A string constant.
- * 5) The list of attributes of the given table (possibly schema-qualified).
- * 6/ The list of arguments to the given function (possibly schema-qualified).
- */
-#define COMPLETE_WITH_QUERY(query) \
-do { \
-	completion_charp = query; \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
-do { \
-	completion_squery = &(query); \
-	completion_charp = addon; \
-	matches = completion_matches(text, complete_from_schema_query); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS(list) \
-do { \
-	completion_charpp = list; \
-	completion_case_sensitive = true; \
-	matches = completion_matches(text, complete_from_list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST(list) \
-do { \
-	completion_charpp = list; \
-	completion_case_sensitive = false; \
-	matches = completion_matches(text, complete_from_list); \
-} while (0)
-
-#define COMPLETE_WITH_CONST(string) \
-do { \
-	completion_charp = string; \
-	completion_case_sensitive = false; \
-	matches = completion_matches(text, complete_from_const); \
-} while (0)
-
-#define COMPLETE_WITH_ATTR(relation, addon) \
-do { \
-	char   *_completion_schema; \
-	char   *_completion_table; \
-\
-	_completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-								false, false, pset.encoding); \
-	if (_completion_table == NULL) \
-	{ \
-		completion_charp = Query_for_list_of_attributes  addon; \
-		completion_info_charp = relation; \
-	} \
-	else \
-	{ \
-		completion_charp = Query_for_list_of_attributes_with_schema  addon; \
-		completion_info_charp = _completion_table; \
-		completion_info_charp2 = _completion_schema; \
-	} \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-#define COMPLETE_WITH_ENUM_VALUE(type) \
-do { \
-	char   *_completion_schema; \
-	char   *_completion_type; \
-\
-	_completion_schema = strtokx(type, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_type = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-							   false, false, pset.encoding);  \
-	if (_completion_type == NULL)\
-	{ \
-		completion_charp = Query_for_list_of_enum_values; \
-		completion_info_charp = type; \
-	} \
-	else \
-	{ \
-		completion_charp = Query_for_list_of_enum_values_with_schema; \
-		completion_info_charp = _completion_type; \
-		completion_info_charp2 = _completion_schema; \
-	} \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-#define COMPLETE_WITH_FUNCTION_ARG(function) \
-do { \
-	char   *_completion_schema; \
-	char   *_completion_function; \
-\
-	_completion_schema = strtokx(function, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_function = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-								   false, false, pset.encoding); \
-	if (_completion_function == NULL) \
-	{ \
-		completion_charp = Query_for_list_of_arguments; \
-		completion_info_charp = function; \
-	} \
-	else \
-	{ \
-		completion_charp = Query_for_list_of_arguments_with_schema; \
-		completion_info_charp = _completion_function; \
-		completion_info_charp2 = _completion_schema; \
-	} \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-/*
- * These macros simplify use of COMPLETE_WITH_LIST for short, fixed lists.
- * There is no COMPLETE_WITH_LIST1; use COMPLETE_WITH_CONST for that case.
- */
-#define COMPLETE_WITH_LIST2(s1, s2) \
-do { \
-	static const char *const list[] = { s1, s2, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST3(s1, s2, s3) \
-do { \
-	static const char *const list[] = { s1, s2, s3, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST4(s1, s2, s3, s4) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST5(s1, s2, s3, s4, s5) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST6(s1, s2, s3, s4, s5, s6) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST7(s1, s2, s3, s4, s5, s6, s7) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST8(s1, s2, s3, s4, s5, s6, s7, s8) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST9(s1, s2, s3, s4, s5, s6, s7, s8, s9) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST10(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-/*
- * Likewise for COMPLETE_WITH_LIST_CS.
- */
-#define COMPLETE_WITH_LIST_CS2(s1, s2) \
-do { \
-	static const char *const list[] = { s1, s2, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS3(s1, s2, s3) \
-do { \
-	static const char *const list[] = { s1, s2, s3, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS4(s1, s2, s3, s4) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS5(s1, s2, s3, s4, s5) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-/*
  * Assembly instructions for schema queries
  */
 
@@ -1007,6 +803,8 @@ static char **get_previous_words(int point, char **buffer, int *nwords);
 
 static char *get_guctype(const char *varname);
 
+static char **psql_completion_internal(const char *text, char **previous_words,
+										   int previous_words_count);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -1162,156 +960,58 @@ psql_completion(const char *text, int start, int end)
 	/* The number of words found on the input line. */
 	int			previous_words_count;
 
-	/*
-	 * For compactness, we use these macros to reference previous_words[].
-	 * Caution: do not access a previous_words[] entry without having checked
-	 * previous_words_count to be sure it's valid.  In most cases below, that
-	 * check is implicit in a TailMatches() or similar macro, but in some
-	 * places we have to check it explicitly.
-	 */
-#define prev_wd   (previous_words[0])
-#define prev2_wd  (previous_words[1])
-#define prev3_wd  (previous_words[2])
-#define prev4_wd  (previous_words[3])
-#define prev5_wd  (previous_words[4])
-#define prev6_wd  (previous_words[5])
-#define prev7_wd  (previous_words[6])
-#define prev8_wd  (previous_words[7])
-#define prev9_wd  (previous_words[8])
-
-	/* Macros for matching the last N words before point, case-insensitively. */
-#define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, prev_wd))
-
-#define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd))
-
-#define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd))
-
-#define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd))
-
-#define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd))
-
-#define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd))
-
-#define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd))
-
-#define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd) && \
-	 word_matches(p8, prev8_wd))
-
-#define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd) && \
-	 word_matches(p8, prev8_wd) && \
-	 word_matches(p9, prev9_wd))
-
-	/* Macros for matching the last N words before point, case-sensitively. */
-#define TailMatchesCS1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches_cs(p1, prev_wd))
-#define TailMatchesCS2(p2, p1) \
-	(previous_words_count >= 2 && \
-	 word_matches_cs(p1, prev_wd) && \
-	 word_matches_cs(p2, prev2_wd))
+	(void) end;					/* "end" is not used */
 
-	/*
-	 * Macros for matching N words beginning at the start of the line,
-	 * case-insensitively.
-	 */
-#define Matches1(p1) \
-	(previous_words_count == 1 && \
-	 TailMatches1(p1))
-#define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
-	 TailMatches2(p1, p2))
-#define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
-	 TailMatches3(p1, p2, p3))
-#define Matches4(p1, p2, p3, p4) \
-	(previous_words_count == 4 && \
-	 TailMatches4(p1, p2, p3, p4))
-#define Matches5(p1, p2, p3, p4, p5) \
-	(previous_words_count == 5 && \
-	 TailMatches5(p1, p2, p3, p4, p5))
-#define Matches6(p1, p2, p3, p4, p5, p6) \
-	(previous_words_count == 6 && \
-	 TailMatches6(p1, p2, p3, p4, p5, p6))
-#define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(previous_words_count == 7 && \
-	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
-#define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(previous_words_count == 8 && \
-	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
-#define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(previous_words_count == 9 && \
-	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
+#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
+	rl_completion_append_character = ' ';
+#endif
+
+	/* Clear a few things. */
+	completion_charp = NULL;
+	completion_charpp = NULL;
+	completion_info_charp = NULL;
+	completion_info_charp2 = NULL;
 
 	/*
-	 * Macros for matching N words at the start of the line, regardless of
-	 * what is after them, case-insensitively.
+	 * Scan the input line to extract the words before our current position.
+	 * According to those we'll make some smart decisions on what the user is
+	 * probably intending to type.
 	 */
-#define HeadMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]))
+	previous_words = get_previous_words(start,
+										&words_buffer,
+										&previous_words_count);
+
+	matches  = psql_completion_internal(text, previous_words,
+										previous_words_count);
 
-#define HeadMatches2(p1, p2) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]))
+	/* free storage */
+	free(previous_words);
+	free(words_buffer);
 
-#define HeadMatches3(p1, p2, p3) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]) && \
-	 word_matches(p3, previous_words[previous_words_count - 3]))
+	if (matches != NULL)
+		return matches;
+
+	/*
+	 * If we still don't have anything to match we have to fabricate some sort
+	 * of default list. If we were to just return NULL, readline automatically
+	 * attempts filename completion, and that's usually no good.
+	 */
+#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
+	rl_completion_append_character = '\0';
+#endif
+	COMPLETE_WITH_CONST("");		/* This returns matches */
+}
 
+/*
+ * The completion function.
+ *
+ * Makes completion list.
+ * Note: COMPLETE_WITH_* macros immediately return to the caller.
+ */
+static char **
+psql_completion_internal(const char *text, char **previous_words,
+						 int previous_words_count)
+{
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
@@ -1343,90 +1043,80 @@ psql_completion(const char *text, int start, int end)
 		"\\timing", "\\unset", "\\x", "\\w", "\\watch", "\\z", "\\!", NULL
 	};
 
-	(void) end;					/* "end" is not used */
-
-#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
-	rl_completion_append_character = ' ';
-#endif
-
-	/* Clear a few things. */
-	completion_charp = NULL;
-	completion_charpp = NULL;
-	completion_info_charp = NULL;
-	completion_info_charp2 = NULL;
-
-	/*
-	 * Scan the input line to extract the words before our current position.
-	 * According to those we'll make some smart decisions on what the user is
-	 * probably intending to type.
-	 */
-	previous_words = get_previous_words(start,
-										&words_buffer,
-										&previous_words_count);
-
 	/* If current word is a backslash command, offer completions for that */
 	if (text[0] == '\\')
 		COMPLETE_WITH_LIST_CS(backslash_commands);
 
 	/* If current word is a variable interpolation, handle that case */
-	else if (text[0] == ':' && text[1] != ':')
+	if (text[0] == ':' && text[1] != ':')
 	{
 		if (text[1] == '\'')
-			matches = complete_from_variables(text, ":'", "'", true);
-		else if (text[1] == '"')
-			matches = complete_from_variables(text, ":\"", "\"", true);
-		else
-			matches = complete_from_variables(text, ":", "", true);
+			return complete_from_variables(text, ":'", "'", true);
+		if (text[1] == '"')
+			return complete_from_variables(text, ":\"", "\"", true);
+
+		return complete_from_variables(text, ":", "", true);
 	}
 
 	/* If no previous word, suggest one of the basic sql commands */
-	else if (previous_words_count == 0)
+	if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
 /* CREATE */
 	/* complete with something you can create */
-	else if (TailMatches1("CREATE"))
-		matches = completion_matches(text, create_command_generator);
+	if (Matches1("CREATE"))
+		return completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
-	else if (Matches1("DROP"))
-		matches = completion_matches(text, drop_command_generator);
+	if (Matches1("DROP"))
+		return completion_matches(text, drop_command_generator);
 
 /* ALTER */
 
 	/* ALTER TABLE */
-	else if (Matches2("ALTER", "TABLE"))
+	if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
 	/* ALTER something */
-	else if (Matches1("ALTER"))
+	if (Matches1("ALTER"))
 	{
 		static const char *const list_ALTER[] =
-		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
-			"EVENT TRIGGER", "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
-			"GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW", "OPERATOR",
-			"POLICY", "ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM", "TABLE",
-			"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
-		"USER", "USER MAPPING FOR", "VIEW", NULL};
+		{"AGGREGATE",
+		 "COLLATION", "CONVERSION",
+		 "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
+		 "EVENT TRIGGER", "EXTENSION",
+		 "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
+		 "GROUP",
+		 "INDEX",
+		 "LANGUAGE", "LARGE OBJECT",
+		 "MATERIALIZED VIEW", 
+		 "OPERATOR",
+		 "POLICY",
+		 "ROLE", "RULE",
+		 "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM",
+		 "TABLE", "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
+		 "USER", "USER MAPPING FOR",
+		 "VIEW",
+		 NULL};
 
 		COMPLETE_WITH_LIST(list_ALTER);
 	}
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx */
-	else if (TailMatches4("ALL", "IN", "TABLESPACE", MatchAny))
+	if (TailMatches4("ALL", "IN", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("SET TABLESPACE", "OWNED BY");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY */
-	else if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
+	if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY xxx */
-	else if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
+	if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("SET TABLESPACE");
 	/* ALTER AGGREGATE,FUNCTION <name> */
-	else if (Matches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
+	if (Matches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION <name> (...) */
-	else if (Matches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
+	if (Matches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
 	{
 		if (ends_with(prev_wd, ')'))
 			COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
@@ -1435,63 +1125,63 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER SCHEMA <name> */
-	else if (Matches3("ALTER", "SCHEMA", MatchAny))
+	if (Matches3("ALTER", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER TO", "RENAME TO");
 
 	/* ALTER COLLATION <name> */
-	else if (Matches3("ALTER", "COLLATION", MatchAny))
+	if (Matches3("ALTER", "COLLATION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER CONVERSION <name> */
-	else if (Matches3("ALTER", "CONVERSION", MatchAny))
+	if (Matches3("ALTER", "CONVERSION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER DATABASE <name> */
-	else if (Matches3("ALTER", "DATABASE", MatchAny))
+	if (Matches3("ALTER", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST7("RESET", "SET", "OWNER TO", "RENAME TO",
 							"IS_TEMPLATE", "ALLOW_CONNECTIONS",
 							"CONNECTION LIMIT");
 
 	/* ALTER EVENT TRIGGER */
-	else if (Matches3("ALTER", "EVENT", "TRIGGER"))
+	if (Matches3("ALTER", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
+	if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST4("DISABLE", "ENABLE", "OWNER TO", "RENAME TO");
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
+	if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST2("REPLICA", "ALWAYS");
 
 	/* ALTER EXTENSION <name> */
-	else if (Matches3("ALTER", "EXTENSION", MatchAny))
+	if (Matches3("ALTER", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST4("ADD", "DROP", "UPDATE", "SET SCHEMA");
 
 	/* ALTER EXTENSION <name> UPDATE */
-	else if (Matches4("ALTER", "EXTENSION", MatchAny, "UPDATE"))
+	if (Matches4("ALTER", "EXTENSION", MatchAny, "UPDATE"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions_with_TO);
 	}
 
 	/* ALTER EXTENSION <name> UPDATE TO */
-	else if (Matches5("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
+	if (Matches5("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
 	}
 
 	/* ALTER FOREIGN */
-	else if (Matches2("ALTER", "FOREIGN"))
+	if (Matches2("ALTER", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
 	/* ALTER FOREIGN TABLE <name> */
-	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
+	if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
 		{"ADD", "ALTER", "DISABLE TRIGGER", "DROP", "ENABLE", "INHERIT",
@@ -1502,42 +1192,42 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER INDEX */
-	else if (Matches2("ALTER", "INDEX"))
+	if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 	/* ALTER INDEX <name> */
-	else if (Matches3("ALTER", "INDEX", MatchAny))
+	if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
 	/* ALTER INDEX <name> SET */
-	else if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
+	if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
 		COMPLETE_WITH_LIST2("(", "TABLESPACE");
 	/* ALTER INDEX <name> RESET */
-	else if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
+	if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER INDEX <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
+	if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
 		COMPLETE_WITH_LIST3("fillfactor", "fastupdate",
 							"gin_pending_list_limit");
-	else if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
+	if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST3("fillfactor =", "fastupdate =",
 							"gin_pending_list_limit =");
 
 	/* ALTER LANGUAGE <name> */
-	else if (Matches3("ALTER", "LANGUAGE", MatchAny))
+	if (Matches3("ALTER", "LANGUAGE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER_TO", "RENAME TO");
 
 	/* ALTER LARGE OBJECT <oid> */
-	else if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
+	if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
 		COMPLETE_WITH_CONST("OWNER TO");
 
 	/* ALTER MATERIALIZED VIEW */
-	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
+	if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
 	/* ALTER USER,ROLE <name> */
-	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
-			 !TailMatches2("USER", "MAPPING"))
+	if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
+		!TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_ALTERUSER[] =
 		{"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -1551,7 +1241,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER USER,ROLE <name> WITH */
-	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
+	if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_ALTERUSER_WITH[] =
@@ -1566,43 +1256,43 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
-	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	if (Matches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* ALTER DEFAULT PRIVILEGES */
-	else if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
+	if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
 		COMPLETE_WITH_LIST3("FOR ROLE", "FOR USER", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR */
-	else if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
+	if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
 		COMPLETE_WITH_LIST2("ROLE", "USER");
 	/* ALTER DEFAULT PRIVILEGES { FOR ROLE ... | IN SCHEMA ... } */
-	else if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER", MatchAny) ||
+	if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER", MatchAny) ||
 		Matches6("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("GRANT", "REVOKE");
 	/* ALTER DOMAIN <name> */
-	else if (Matches3("ALTER", "DOMAIN", MatchAny))
+	if (Matches3("ALTER", "DOMAIN", MatchAny))
 		COMPLETE_WITH_LIST6("ADD", "DROP", "OWNER TO", "RENAME", "SET",
 							"VALIDATE CONSTRAINT");
 	/* ALTER DOMAIN <sth> DROP */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
 	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
 	}
 	/* ALTER DOMAIN <sth> RENAME */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("CONSTRAINT", "TO");
 	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
-	else if (Matches6("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
+	if (Matches6("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER DOMAIN <sth> SET */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	/* ALTER SEQUENCE <name> */
-	else if (Matches3("ALTER", "SEQUENCE", MatchAny))
+	if (Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1611,74 +1301,74 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
 	/* ALTER SEQUENCE <name> NO */
-	else if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
+	if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 	/* ALTER SERVER <name> */
-	else if (Matches3("ALTER", "SERVER", MatchAny))
+	if (Matches3("ALTER", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST4("VERSION", "OPTIONS", "OWNER TO", "RENAME TO");
 	/* ALTER SERVER <name> VERSION <version> */
-	else if (Matches5("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
+	if (Matches5("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
 		COMPLETE_WITH_CONST("OPTIONS");
 	/* ALTER SYSTEM SET, RESET, RESET ALL */
-	else if (Matches2("ALTER", "SYSTEM"))
+	if (Matches2("ALTER", "SYSTEM"))
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
-	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
+	if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
 	/* ALTER VIEW <name> */
-	else if (Matches3("ALTER", "VIEW", MatchAny))
+	if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
-	else if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
 	/* ALTER POLICY <name> */
-	else if (Matches2("ALTER", "POLICY"))
+	if (Matches2("ALTER", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* ALTER POLICY <name> ON */
-	else if (Matches3("ALTER", "POLICY", MatchAny))
+	if (Matches3("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
-	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
+	if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 	/* ALTER POLICY <name> ON <table> - show options */
-	else if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING (", "WITH CHECK (");
 	/* ALTER POLICY <name> ON <table> TO <role> */
-	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* ALTER POLICY <name> ON <table> USING ( */
-	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
-	else if (Matches7("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
+	if (Matches7("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
 		COMPLETE_WITH_CONST("(");
 
 	/* ALTER RULE <name>, add ON */
-	else if (Matches3("ALTER", "RULE", MatchAny))
+	if (Matches3("ALTER", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
-	else if (Matches4("ALTER", "RULE", MatchAny, "ON"))
+	if (Matches4("ALTER", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
 
 	/* ALTER RULE <name> ON <name> */
-	else if (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/* ALTER TRIGGER <name>, add ON */
-	else if (Matches3("ALTER", "TRIGGER", MatchAny))
+	if (Matches3("ALTER", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
-	else if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
+	if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
@@ -1687,17 +1377,17 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
-	else if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
+	if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER TRIGGER <name> ON <name> */
-	else if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/*
 	 * If we detect ALTER TABLE <name>, suggest sub commands
 	 */
-	else if (Matches3("ALTER", "TABLE", MatchAny))
+	if (Matches3("ALTER", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER2[] =
 		{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
@@ -1707,114 +1397,114 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
 	/* ALTER TABLE xxx ENABLE */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST5("ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE",
 							"TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
 		COMPLETE_WITH_LIST2("RULE", "TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
 	/* ALTER TABLE xxx INHERIT */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx NO INHERIT */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx DISABLE */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
 		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
 
 	/* ALTER TABLE xxx ALTER */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
 
 	/* ALTER TABLE xxx RENAME */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/* ALTER TABLE xxx RENAME yyy */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
+	if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
-	else if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
+	if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* If we have ALTER TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
+	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
 			 Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
 		COMPLETE_WITH_LIST4("TYPE", "SET", "RESET", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
 			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
 		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
+	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
 		 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
+	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
 	Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
 		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
 			 Matches8("ALTER", "TABLE", MatchAny, "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("DEFAULT", "NOT NULL");
-	else if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
 	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
 		COMPLETE_WITH_LIST7("(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED",
 							"WITH", "WITHOUT");
 
@@ -1822,19 +1512,19 @@ psql_completion(const char *text, int start, int end)
 	 * If we have ALTER TABLE <sth> SET TABLESPACE provide a list of
 	 * tablespaces
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	/* If we have ALTER TABLE <sth> SET WITH provide OIDS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
 		COMPLETE_WITH_CONST("OIDS");
 	/* If we have ALTER TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
 		COMPLETE_WITH_LIST2("CLUSTER", "OIDS");
 	/* ALTER TABLE <foo> RESET */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLE <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
 	{
 		static const char *const list_TABLEOPTIONS[] =
 		{
@@ -1872,72 +1562,72 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
-	else if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
+	if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
 		COMPLETE_WITH_LIST4("FULL", "NOTHING", "DEFAULT", "USING");
-	else if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
 		COMPLETE_WITH_CONST("IDENTITY");
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
-	else if (Matches3("ALTER", "TABLESPACE", MatchAny))
+	if (Matches3("ALTER", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "OWNER TO", "SET", "RESET");
 	/* ALTER TABLESPACE <foo> SET|RESET */
-	else if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
+	if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
+	if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
 		COMPLETE_WITH_LIST3("seq_page_cost", "random_page_cost",
 							"effective_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
-	else if (Matches3("ALTER", "TEXT", "SEARCH"))
+	if (Matches3("ALTER", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
 		COMPLETE_WITH_LIST2("RENAME TO", "SET SCHEMA");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING",
 							"DROP MAPPING FOR",
 							"OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* complete ALTER TYPE <foo> with actions */
-	else if (Matches3("ALTER", "TYPE", MatchAny))
+	if (Matches3("ALTER", "TYPE", MatchAny))
 		COMPLETE_WITH_LIST7("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE",
 							"DROP ATTRIBUTE",
 							"OWNER TO", "RENAME", "SET SCHEMA");
 	/* complete ALTER TYPE <foo> ADD with actions */
-	else if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
+	if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
 	/* ALTER TYPE <foo> RENAME	*/
-	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST3("ATTRIBUTE", "TO", "VALUE");
 	/* ALTER TYPE xxx RENAME (ATTRIBUTE|VALUE) yyy */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE|VALUE", MatchAny))
+	if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE|VALUE", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 	/*
 	 * If we have ALTER TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list
 	 * of attributes
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
+	if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
+	if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* complete ALTER GROUP <foo> */
-	else if (Matches3("ALTER", "GROUP", MatchAny))
+	if (Matches3("ALTER", "GROUP", MatchAny))
 		COMPLETE_WITH_LIST3("ADD USER", "DROP USER", "RENAME TO");
 	/* complete ALTER GROUP <foo> ADD|DROP with USER */
-	else if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
+	if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
 		COMPLETE_WITH_CONST("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
-	else if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
+	if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 	/*
@@ -1947,33 +1637,33 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_ENUM_VALUE(prev3_wd);
 
 /* BEGIN */
-	else if (Matches1("BEGIN"))
+	if (Matches1("BEGIN"))
 		COMPLETE_WITH_LIST6("WORK", "TRANSACTION", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
 /* END, ABORT */
-	else if (Matches1("END|ABORT"))
+	if (Matches1("END|ABORT"))
 		COMPLETE_WITH_LIST2("WORK", "TRANSACTION");
 /* COMMIT */
-	else if (Matches1("COMMIT"))
+	if (Matches1("COMMIT"))
 		COMPLETE_WITH_LIST3("WORK", "TRANSACTION", "PREPARED");
 /* RELEASE SAVEPOINT */
-	else if (Matches1("RELEASE"))
+	if (Matches1("RELEASE"))
 		COMPLETE_WITH_CONST("SAVEPOINT");
 /* ROLLBACK */
-	else if (Matches1("ROLLBACK"))
+	if (Matches1("ROLLBACK"))
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
-	else if (Matches1("CLUSTER"))
+	if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
-	else if (Matches2("CLUSTER", "VERBOSE"))
+	if (Matches2("CLUSTER", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 	/* If we have CLUSTER <sth>, then add "USING" */
-	else if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
+	if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	else if (Matches3("CLUSTER", "VERBOSE", MatchAny))
+	if (Matches3("CLUSTER", "VERBOSE", MatchAny))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER <sth> USING, then add the index as well */
-	else if (Matches3("CLUSTER", MatchAny, "USING") ||
+	if (Matches3("CLUSTER", MatchAny, "USING") ||
 			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
@@ -1981,9 +1671,9 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* COMMENT */
-	else if (Matches1("COMMENT"))
+	if (Matches1("COMMENT"))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches2("COMMENT", "ON"))
+	if (Matches2("COMMENT", "ON"))
 	{
 		static const char *const list_COMMENT[] =
 		{"ACCESS METHOD", "CAST", "COLLATION", "CONVERSION", "DATABASE",
@@ -1996,26 +1686,26 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
-	else if (Matches4("COMMENT", "ON", "ACCESS", "METHOD"))
+	if (Matches4("COMMENT", "ON", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (Matches3("COMMENT", "ON", "FOREIGN"))
+	if (Matches3("COMMENT", "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
-	else if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
+	if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches3("COMMENT", "ON", "CONSTRAINT"))
+	if (Matches3("COMMENT", "ON", "CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
-	else if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
+	if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
+	if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
 	}
-	else if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
+	if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
+	if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	else if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
+	if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
 		Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
 			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
 		COMPLETE_WITH_CONST("IS");
@@ -2026,97 +1716,97 @@ psql_completion(const char *text, int start, int end)
 	 * If we have COPY, offer list of tables or "(" (Also cover the analogous
 	 * backslash command).
 	 */
-	else if (Matches1("COPY|\\copy"))
+	if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION ALL SELECT '('");
 	/* If we have COPY BINARY, complete with list of tables */
-	else if (Matches2("COPY", "BINARY"))
+	if (Matches2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* If we have COPY (, complete it with legal commands */
-	else if (Matches2("COPY|\\copy", "("))
+	if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
 	/* If we have COPY [BINARY] <sth>, complete it with "TO" or "FROM" */
-	else if (Matches2("COPY|\\copy", MatchAny) ||
+	if (Matches2("COPY|\\copy", MatchAny) ||
 			 Matches3("COPY", "BINARY", MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "TO");
 	/* If we have COPY [BINARY] <sth> FROM|TO, complete with filename */
-	else if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
+	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
 			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
 	{
 		completion_charp = "";
-		matches = completion_matches(text, complete_from_files);
+		return completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename */
-	else if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
+	if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
 			 Matches5("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
 		COMPLETE_WITH_LIST6("BINARY", "OIDS", "DELIMITER", "NULL", "CSV",
 							"ENCODING");
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename CSV */
-	else if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
+	if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
 			 Matches6("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny, "CSV"))
 		COMPLETE_WITH_LIST5("HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE",
 							"FORCE NOT NULL");
 
 	/* CREATE ACCESS METHOD */
 	/* Complete "CREATE ACCESS METHOD <name>" */
-	else if (Matches4("CREATE", "ACCESS", "METHOD", MatchAny))
+	if (Matches4("CREATE", "ACCESS", "METHOD", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE" */
-	else if (Matches5("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
+	if (Matches5("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
 		COMPLETE_WITH_CONST("INDEX");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE <type>" */
-	else if (Matches6("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
+	if (Matches6("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
 		COMPLETE_WITH_CONST("HANDLER");
 
 	/* CREATE DATABASE */
-	else if (Matches3("CREATE", "DATABASE", MatchAny))
+	if (Matches3("CREATE", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST9("OWNER", "TEMPLATE", "ENCODING", "TABLESPACE",
 							"IS_TEMPLATE",
 							"ALLOW_CONNECTIONS", "CONNECTION LIMIT",
 							"LC_COLLATE", "LC_CTYPE");
 
-	else if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
+	if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
-	else if (Matches2("CREATE", "EXTENSION"))
+	if (Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
-	else if (Matches3("CREATE", "EXTENSION", MatchAny))
+	if (Matches3("CREATE", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
 	/* CREATE EXTENSION <name> VERSION */
-	else if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
+	if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
 	}
 
 	/* CREATE FOREIGN */
-	else if (Matches2("CREATE", "FOREIGN"))
+	if (Matches2("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* CREATE FOREIGN DATA WRAPPER */
-	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	else if (TailMatches2("CREATE", "UNIQUE"))
+	if (TailMatches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
 	/*
 	 * If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY", and
 	 * existing indexes
 	 */
-	else if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	if (TailMatches2("CREATE|UNIQUE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
 								   " UNION SELECT 'CONCURRENTLY'");
 	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	else if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
+	if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
 			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
@@ -2124,11 +1814,11 @@ psql_completion(const char *text, int start, int end)
 	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
 	 * indexes
 	 */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+	if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'");
 	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
+	if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
 			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
@@ -2136,117 +1826,117 @@ psql_completion(const char *text, int start, int end)
 	 * Complete INDEX <name> ON <table> with a list of table columns (which
 	 * should really be in parens)
 	 */
-	else if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-			 TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+	if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
+		TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-			 TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
+	if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
+		TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* same if you put in USING */
-	else if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
+	if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	else if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
+	if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
+	if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
 			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
 			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-	else if (Matches3("CREATE", "POLICY", MatchAny))
+	if (Matches3("CREATE", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
-	else if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
+	if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
-	else if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
+	if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING (", "WITH CHECK (");
 	/* CREATE POLICY <name> ON <table> FOR ALL|SELECT|INSERT|UPDATE|DELETE */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
 		COMPLETE_WITH_LIST5("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
 	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
 		COMPLETE_WITH_LIST2("TO", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> FOR SELECT|DELETE TO|USING" */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
 		COMPLETE_WITH_LIST2("TO", "USING (");
 	/* CREATE POLICY <name> ON <table> FOR ALL|UPDATE TO|USING|WITH CHECK */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
 		COMPLETE_WITH_LIST3("TO", "USING (", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS ON" */
-	else if (Matches3("CREATE", "RULE", MatchAny))
+	if (Matches3("CREATE", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("AS ON");
 	/* Complete "CREATE RULE <sth> AS" with "ON" */
-	else if (Matches4("CREATE", "RULE", MatchAny, "AS"))
+	if (Matches4("CREATE", "RULE", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE RULE <sth> AS ON" with SELECT|UPDATE|INSERT|DELETE */
-	else if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
+	if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
 	/* Complete "AS ON SELECT|UPDATE|INSERT|DELETE" with a "TO" */
-	else if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
+	if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
-	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
+	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	else if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
+	if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
 			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	else if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
+	if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
 		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
-	else if (Matches3("CREATE", "SERVER", MatchAny))
+	if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
 /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	else if (TailMatches2("CREATE", "UNLOGGED"))
+	if (TailMatches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
 /* CREATE TABLESPACE */
-	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
+	if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
 	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
-	else if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
+	if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
 		COMPLETE_WITH_CONST("LOCATION");
 
 /* CREATE TEXT SEARCH */
-	else if (Matches3("CREATE", "TEXT", "SEARCH"))
+	if (Matches3("CREATE", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	else if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	if (TailMatches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+	if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
 	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
@@ -2254,10 +1944,10 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
 	 * tables
 	 */
-	else if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
+	if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
-	else if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
+	if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("ON", MatchAny))
 		COMPLETE_WITH_LIST7("NOT DEFERRABLE", "DEFERRABLE", "INITIALLY",
@@ -2292,13 +1982,13 @@ psql_completion(const char *text, int start, int end)
 			  TailMatches2("FOR", "ROW|STATEMENT")))
 		COMPLETE_WITH_LIST2("WHEN (", "EXECUTE PROCEDURE");
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
 	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("EXECUTE", "PROCEDURE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 
 /* CREATE ROLE,USER,GROUP <name> */
-	else if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
+	if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_CREATEROLE[] =
@@ -2313,7 +2003,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE ROLE,USER,GROUP <name> WITH */
-	else if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
+	if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_CREATEROLE_WITH[] =
@@ -2331,70 +2021,70 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
 	 * PASSWORD
 	 */
-	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
-	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
+	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE VIEW <name> with AS */
-	else if (TailMatches3("CREATE", "VIEW", MatchAny))
+	if (TailMatches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
+	if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches2("CREATE", "MATERIALIZED"))
+	if (Matches2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE EVENT TRIGGER */
-	else if (Matches2("CREATE", "EVENT"))
+	if (Matches2("CREATE", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	/* Complete CREATE EVENT TRIGGER <name> with ON */
-	else if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
+	if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
-	else if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
+	if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_LIST3("ddl_command_start", "ddl_command_end", "sql_drop");
 
 /* DECLARE */
-	else if (Matches2("DECLARE", MatchAny))
+	if (Matches2("DECLARE", MatchAny))
 		COMPLETE_WITH_LIST5("BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL",
 							"CURSOR");
-	else if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
+	if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
 		COMPLETE_WITH_LIST3("WITH HOLD", "WITHOUT HOLD", "FOR");
 
 /* DELETE --- can be inside EXPLAIN, RULE, etc */
 	/* ... despite which, only complete DELETE with FROM at start of line */
-	else if (Matches1("DELETE"))
+	if (Matches1("DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
-	else if (TailMatches2("DELETE", "FROM"))
+	if (TailMatches2("DELETE", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete DELETE FROM <table> */
-	else if (TailMatches3("DELETE", "FROM", MatchAny))
+	if (TailMatches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
 	/* XXX: implement tab completion for DELETE ... USING */
 
 /* DISCARD */
-	else if (Matches1("DISCARD"))
+	if (Matches1("DISCARD"))
 		COMPLETE_WITH_LIST4("ALL", "PLANS", "SEQUENCES", "TEMP");
 
 /* DO */
-	else if (Matches1("DO"))
+	if (Matches1("DO"))
 		COMPLETE_WITH_CONST("LANGUAGE");
 
 /* DROP */
 	/* Complete DROP object with CASCADE / RESTRICT */
-	else if (Matches3("DROP",
+	if (Matches3("DROP",
 					  "COLLATION|CONVERSION|DOMAIN|EXTENSION|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW",
 					  MatchAny) ||
 			 Matches4("DROP", "ACCESS", "METHOD", MatchAny) ||
@@ -2407,88 +2097,88 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
+	if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
-	else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
+	if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
-	else if (Matches2("DROP", "FOREIGN"))
+	if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* DROP INDEX */
-	else if (Matches2("DROP", "INDEX"))
+	if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
+	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches3("DROP", "INDEX", MatchAny))
+	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	else if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
+	if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
-	else if (Matches2("DROP", "MATERIALIZED"))
+	if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
 	/* DROP OWNED BY */
-	else if (Matches2("DROP", "OWNED"))
+	if (Matches2("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (Matches3("DROP", "OWNED", "BY"))
+	if (Matches3("DROP", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
-	else if (Matches3("DROP", "TEXT", "SEARCH"))
+	if (Matches3("DROP", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (Matches3("DROP", "TRIGGER", MatchAny))
+	if (Matches3("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
+	if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
 	}
-	else if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
+	if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP ACCESS METHOD */
-	else if (Matches2("DROP", "ACCESS"))
+	if (Matches2("DROP", "ACCESS"))
 		COMPLETE_WITH_CONST("METHOD");
-	else if (Matches3("DROP", "ACCESS", "METHOD"))
+	if (Matches3("DROP", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 
 	/* DROP EVENT TRIGGER */
-	else if (Matches2("DROP", "EVENT"))
+	if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
-	else if (Matches3("DROP", "EVENT", "TRIGGER"))
+	if (Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* DROP POLICY <name>  */
-	else if (Matches2("DROP", "POLICY"))
+	if (Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* DROP POLICY <name> ON */
-	else if (Matches3("DROP", "POLICY", MatchAny))
+	if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* DROP POLICY <name> ON <table> */
-	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
+	if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 
 	/* DROP RULE */
-	else if (Matches3("DROP", "RULE", MatchAny))
+	if (Matches3("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
+	if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
-	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
+	if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 /* EXECUTE */
-	else if (Matches1("EXECUTE"))
+	if (Matches1("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
 
 /* EXPLAIN */
@@ -2496,22 +2186,22 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
-	else if (Matches1("EXPLAIN"))
+	if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"ANALYZE", "VERBOSE");
-	else if (Matches2("EXPLAIN", "ANALYZE"))
+	if (Matches2("EXPLAIN", "ANALYZE"))
 		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"VERBOSE");
-	else if (Matches2("EXPLAIN", "VERBOSE") ||
+	if (Matches2("EXPLAIN", "VERBOSE") ||
 			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
 		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-	else if (Matches1("FETCH|MOVE"))
+	if (Matches1("FETCH|MOVE"))
 		COMPLETE_WITH_LIST4("ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE");
 	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
-	else if (Matches2("FETCH|MOVE", MatchAny))
+	if (Matches2("FETCH|MOVE", MatchAny))
 		COMPLETE_WITH_LIST3("ALL", "NEXT", "PRIOR");
 
 	/*
@@ -2519,31 +2209,31 @@ psql_completion(const char *text, int start, int end)
 	 * but we may as well tab-complete both: perhaps some users prefer one
 	 * variant or the other.
 	 */
-	else if (Matches3("FETCH|MOVE", MatchAny, MatchAny))
+	if (Matches3("FETCH|MOVE", MatchAny, MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "IN");
 
 /* FOREIGN DATA WRAPPER */
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
-	else if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
+	if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
 			 !TailMatches4("CREATE", MatchAny, MatchAny, MatchAny))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 	/* applies in CREATE SERVER */
-	else if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
+	if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
 			 HeadMatches2("CREATE", "SERVER"))
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /* FOREIGN TABLE */
-	else if (TailMatches2("FOREIGN", "TABLE") &&
+	if (TailMatches2("FOREIGN", "TABLE") &&
 			 !TailMatches3("CREATE", MatchAny, MatchAny))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* FOREIGN SERVER */
-	else if (TailMatches2("FOREIGN", "SERVER"))
+	if (TailMatches2("FOREIGN", "SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
 
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	else if (TailMatches1("GRANT|REVOKE"))
+	if (TailMatches1("GRANT|REVOKE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'SELECT'"
 							" UNION SELECT 'INSERT'"
@@ -2563,11 +2253,11 @@ psql_completion(const char *text, int start, int end)
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	else if (TailMatches2("GRANT|REVOKE", MatchAny))
+	if (TailMatches2("GRANT|REVOKE", MatchAny))
 	{
 		if (TailMatches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
-		else if (TailMatches2("GRANT", MatchAny))
+		if (TailMatches2("GRANT", MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2584,7 +2274,7 @@ psql_completion(const char *text, int start, int end)
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
 	 */
-	else if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
+	if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
 								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
 								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
@@ -2602,11 +2292,11 @@ psql_completion(const char *text, int start, int end)
 								   " UNION SELECT 'TABLESPACE'"
 								   " UNION SELECT 'TYPE'");
 
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
 							"TABLES IN SCHEMA");
 
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/*
@@ -2615,27 +2305,27 @@ psql_completion(const char *text, int start, int end)
 	 *
 	 * Complete "GRANT/REVOKE * ON *" with "TO/FROM".
 	 */
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
 		if (TailMatches1("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-		else if (TailMatches1("DOMAIN"))
+		if (TailMatches1("DOMAIN"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-		else if (TailMatches1("FUNCTION"))
+		if (TailMatches1("FUNCTION"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-		else if (TailMatches1("LANGUAGE"))
+		if (TailMatches1("LANGUAGE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-		else if (TailMatches1("SCHEMA"))
+		if (TailMatches1("SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-		else if (TailMatches1("SEQUENCE"))
+		if (TailMatches1("SEQUENCE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-		else if (TailMatches1("TABLE"))
+		if (TailMatches1("TABLE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-		else if (TailMatches1("TABLESPACE"))
+		if (TailMatches1("TABLESPACE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-		else if (TailMatches1("TYPE"))
+		if (TailMatches1("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-		else if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
+		if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2645,18 +2335,18 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "GRANT/REVOKE ... TO/FROM" with username, PUBLIC,
 	 * CURRENT_USER, or SESSION_USER.
 	 */
-	else if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
+	if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
 			 (HeadMatches1("REVOKE") && TailMatches1("FROM")))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 
 	/* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */
-	else if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
+	if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (HeadMatches1("REVOKE") && TailMatches3("ON", MatchAny, MatchAny))
+	if (HeadMatches1("REVOKE") && TailMatches3("ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("FROM");
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	else if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
+	if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
 	{
 		if (TailMatches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2665,7 +2355,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	else if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 	{
 		if (TailMatches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2674,7 +2364,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	else if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
+	if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
 	{
 		if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2683,62 +2373,62 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* GROUP BY */
-	else if (TailMatches3("FROM", MatchAny, "GROUP"))
+	if (TailMatches3("FROM", MatchAny, "GROUP"))
 		COMPLETE_WITH_CONST("BY");
 
 /* IMPORT FOREIGN SCHEMA */
-	else if (Matches1("IMPORT"))
+	if (Matches1("IMPORT"))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
-	else if (Matches2("IMPORT", "FOREIGN"))
+	if (Matches2("IMPORT", "FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT --- can be inside EXPLAIN, RULE, etc */
 	/* Complete INSERT with "INTO" */
-	else if (TailMatches1("INSERT"))
+	if (TailMatches1("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
-	else if (TailMatches2("INSERT", "INTO"))
+	if (TailMatches2("INSERT", "INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete "INSERT INTO <table> (" with attribute names */
-	else if (TailMatches4("INSERT", "INTO", MatchAny, "("))
+	if (TailMatches4("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES"
 	 */
-	else if (TailMatches3("INSERT", "INTO", MatchAny))
+	if (TailMatches3("INSERT", "INTO", MatchAny))
 		COMPLETE_WITH_LIST5("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES");
 
 	/*
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE"
 	 */
-	else if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
+	if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
 			 ends_with(prev_wd, ')'))
 		COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES");
 
 	/* Insert an open parenthesis after "VALUES" */
-	else if (TailMatches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
+	if (TailMatches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
 		COMPLETE_WITH_CONST("(");
 
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
-	else if (Matches1("LOCK"))
+	if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION SELECT 'TABLE'");
-	else if (Matches2("LOCK", "TABLE"))
+	if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* For the following, handle the case of a single table only for now */
 
 	/* Complete LOCK [TABLE] <table> with "IN" */
-	else if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
+	if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
 			 Matches3("LOCK", "TABLE", MatchAny))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	else if (Matches3("LOCK", MatchAny, "IN") ||
+	if (Matches3("LOCK", MatchAny, "IN") ||
 			 Matches4("LOCK", "TABLE", MatchAny, "IN"))
 		COMPLETE_WITH_LIST8("ACCESS SHARE MODE",
 							"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
@@ -2758,25 +2448,25 @@ psql_completion(const char *text, int start, int end)
 							"UPDATE EXCLUSIVE MODE");
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
-	else if (TailMatches1("NOTIFY"))
+	if (TailMatches1("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
 
 /* OPTIONS */
-	else if (TailMatches1("OPTIONS"))
+	if (TailMatches1("OPTIONS"))
 		COMPLETE_WITH_CONST("(");
 
 /* OWNER TO  - complete with available roles */
-	else if (TailMatches2("OWNER", "TO"))
+	if (TailMatches2("OWNER", "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* ORDER BY */
-	else if (TailMatches3("FROM", MatchAny, "ORDER"))
+	if (TailMatches3("FROM", MatchAny, "ORDER"))
 		COMPLETE_WITH_CONST("BY");
-	else if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
+	if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 /* PREPARE xx AS */
-	else if (Matches3("PREPARE", MatchAny, "AS"))
+	if (Matches3("PREPARE", MatchAny, "AS"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
 
 /*
@@ -2785,60 +2475,60 @@ psql_completion(const char *text, int start, int end)
  */
 
 /* REASSIGN OWNED BY xxx TO yyy */
-	else if (Matches1("REASSIGN"))
+	if (Matches1("REASSIGN"))
 		COMPLETE_WITH_CONST("OWNED BY");
-	else if (Matches2("REASSIGN", "OWNED"))
+	if (Matches2("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (Matches3("REASSIGN", "OWNED", "BY"))
+	if (Matches3("REASSIGN", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
+	if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
+	if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* REFRESH MATERIALIZED VIEW */
-	else if (Matches1("REFRESH"))
+	if (Matches1("REFRESH"))
 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
-	else if (Matches2("REFRESH", "MATERIALIZED"))
+	if (Matches2("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
+	if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
+	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
+	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
+	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
-	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
+	if (Matches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
-	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
+	if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
-	else if (Matches7("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
+	if (Matches7("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
 
 /* REINDEX */
-	else if (Matches1("REINDEX"))
+	if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
-	else if (Matches2("REINDEX", "TABLE"))
+	if (Matches2("REINDEX", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-	else if (Matches2("REINDEX", "INDEX"))
+	if (Matches2("REINDEX", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches2("REINDEX", "SCHEMA"))
+	if (Matches2("REINDEX", "SCHEMA"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-	else if (Matches2("REINDEX", "SYSTEM|DATABASE"))
+	if (Matches2("REINDEX", "SYSTEM|DATABASE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 
 /* SECURITY LABEL */
-	else if (Matches1("SECURITY"))
+	if (Matches1("SECURITY"))
 		COMPLETE_WITH_CONST("LABEL");
-	else if (Matches2("SECURITY", "LABEL"))
+	if (Matches2("SECURITY", "LABEL"))
 		COMPLETE_WITH_LIST2("ON", "FOR");
-	else if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
+	if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches3("SECURITY", "LABEL", "ON") ||
+	if (Matches3("SECURITY", "LABEL", "ON") ||
 			 Matches5("SECURITY", "LABEL", "FOR", MatchAny, "ON"))
 	{
 		static const char *const list_SECURITY_LABEL[] =
@@ -2849,7 +2539,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_SECURITY_LABEL);
 	}
-	else if (Matches5("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
+	if (Matches5("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("IS");
 
 /* SELECT */
@@ -2857,69 +2547,69 @@ psql_completion(const char *text, int start, int end)
 
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
-	else if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
+	if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
-	else if (Matches1("SHOW"))
+	if (Matches1("SHOW"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
 	/* Complete "SET TRANSACTION" */
-	else if (Matches2("SET", "TRANSACTION"))
+	if (Matches2("SET", "TRANSACTION"))
 		COMPLETE_WITH_LIST5("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	else if (Matches2("BEGIN|START", "TRANSACTION") ||
-			 Matches2("BEGIN", "WORK") ||
-			 Matches1("BEGIN") ||
-		  Matches5("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
+	if (Matches2("BEGIN|START", "TRANSACTION") ||
+		Matches2("BEGIN", "WORK") ||
+		Matches1("BEGIN") ||
+		Matches5("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
 		COMPLETE_WITH_LIST4("ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
-			 Matches2("BEGIN", "NOT") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
+		Matches2("BEGIN", "NOT") ||
+		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
 		COMPLETE_WITH_CONST("DEFERRABLE");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
-			 Matches2("BEGIN", "ISOLATION") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
+		Matches2("BEGIN", "ISOLATION") ||
+		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
 		COMPLETE_WITH_CONST("LEVEL");
-	else if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
-			 Matches3("BEGIN", "ISOLATION", "LEVEL") ||
-			 Matches7("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
+	if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
+		Matches3("BEGIN", "ISOLATION", "LEVEL") ||
+		Matches7("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
 		COMPLETE_WITH_LIST3("READ", "REPEATABLE READ", "SERIALIZABLE");
-	else if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
-			 Matches4("BEGIN", "ISOLATION", "LEVEL", "READ") ||
-			 Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
+	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
+		Matches4("BEGIN", "ISOLATION", "LEVEL", "READ") ||
+		Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
 		COMPLETE_WITH_LIST2("UNCOMMITTED", "COMMITTED");
-	else if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
-			 Matches4("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
-			 Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
+	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
+		Matches4("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
+		Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
 		COMPLETE_WITH_CONST("READ");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
-			 Matches2("BEGIN", "READ") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
+		Matches2("BEGIN", "READ") ||
+		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
-	else if (Matches2("SET", "CONSTRAINTS"))
+	if (Matches2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
-	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
+	if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
-	else if (Matches2("SET", "ROLE"))
+	if (Matches2("SET", "ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
-	else if (Matches2("SET", "SESSION"))
+	if (Matches2("SET", "SESSION"))
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
-	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
+	if (Matches3("SET", "SESSION", "AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
 	/* Complete RESET SESSION with AUTHORIZATION */
-	else if (Matches2("RESET", "SESSION"))
+	if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
 	/* Complete SET <var> with "TO" */
-	else if (Matches2("SET", MatchAny))
+	if (Matches2("SET", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete ALTER DATABASE|FUNCTION|ROLE|USER ... SET <name> */
-	else if (HeadMatches2("ALTER", "DATABASE|FUNCTION|ROLE|USER") &&
+	if (HeadMatches2("ALTER", "DATABASE|FUNCTION|ROLE|USER") &&
 			 TailMatches2("SET", MatchAny))
 		COMPLETE_WITH_LIST2("FROM CURRENT", "TO");
 	/* Suggest possible variable values */
-	else if (TailMatches3("SET", MatchAny, "TO|="))
+	if (TailMatches3("SET", MatchAny, "TO|="))
 	{
 		/* special cased code for individual GUCs */
 		if (TailMatches2("DateStyle", "TO|="))
@@ -2932,112 +2622,117 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST(my_list);
 		}
-		else if (TailMatches2("search_path", "TO|="))
+		if (TailMatches2("search_path", "TO|="))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
 								" AND nspname not like 'pg\\_toast%%' "
 								" AND nspname not like 'pg\\_temp%%' "
 								" UNION SELECT 'DEFAULT' ");
 		else
 		{
-			/* generic, type based, GUC support */
+			/* generic, type based, GUC support, guctype is malloc'ed */
 			char	   *guctype = get_guctype(prev2_wd);
 
 			if (guctype && strcmp(guctype, "enum") == 0)
 			{
 				char		querybuf[1024];
 
+				free(guctype);
 				snprintf(querybuf, sizeof(querybuf), Query_for_enum, prev2_wd);
 				COMPLETE_WITH_QUERY(querybuf);
 			}
-			else if (guctype && strcmp(guctype, "bool") == 0)
+
+			if (guctype && strcmp(guctype, "bool") == 0)
+			{
+				free(guctype);
 				COMPLETE_WITH_LIST9("on", "off", "true", "false", "yes", "no",
 									"1", "0", "DEFAULT");
-			else
-				COMPLETE_WITH_CONST("DEFAULT");
+			}
 
 			if (guctype)
 				free(guctype);
+
+			COMPLETE_WITH_CONST("DEFAULT");
 		}
 	}
 
 /* START TRANSACTION */
-	else if (Matches1("START"))
+	if (Matches1("START"))
 		COMPLETE_WITH_CONST("TRANSACTION");
 
 /* TABLE, but not TABLE embedded in other commands */
-	else if (Matches1("TABLE"))
+	if (Matches1("TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
 /* TABLESAMPLE */
-	else if (TailMatches1("TABLESAMPLE"))
+	if (TailMatches1("TABLESAMPLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods);
-	else if (TailMatches2("TABLESAMPLE", MatchAny))
+	if (TailMatches2("TABLESAMPLE", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
-	else if (Matches1("TRUNCATE"))
+	if (Matches1("TRUNCATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* UNLISTEN */
-	else if (Matches1("UNLISTEN"))
+	if (Matches1("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
 
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
-	else if (TailMatches1("UPDATE"))
+	if (TailMatches1("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete UPDATE <table> with "SET" */
-	else if (TailMatches2("UPDATE", MatchAny))
+	if (TailMatches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
 	/* Complete UPDATE <table> SET with list of attributes */
-	else if (TailMatches3("UPDATE", MatchAny, "SET"))
+	if (TailMatches3("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* UPDATE <table> SET <attr> = */
-	else if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
+	if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
+	if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
-	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
+	if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
 							" UNION SELECT 'PUBLIC'"
 							" UNION SELECT 'USER'");
-	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
+	if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
+	if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("SERVER");
-	else if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
+	if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /*
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
-	else if (Matches1("VACUUM"))
+	if (Matches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'FULL'"
 								   " UNION SELECT 'FREEZE'"
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches2("VACUUM", "FULL|FREEZE"))
+	if (Matches2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
+	if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
+	if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (Matches2("VACUUM", "VERBOSE"))
+	if (Matches2("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (Matches2("VACUUM", "ANALYZE"))
+	if (Matches2("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (HeadMatches1("VACUUM"))
+	if (HeadMatches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 /* WITH [RECURSIVE] */
@@ -3046,114 +2741,114 @@ psql_completion(const char *text, int start, int end)
 	 * Only match when WITH is the first word, as WITH may appear in many
 	 * other contexts.
 	 */
-	else if (Matches1("WITH"))
+	if (Matches1("WITH"))
 		COMPLETE_WITH_CONST("RECURSIVE");
 
 /* ANALYZE */
 	/* Complete with list of tables */
-	else if (Matches1("ANALYZE"))
+	if (Matches1("ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
-	else if (TailMatches2(MatchAny, "WHERE"))
+	if (TailMatches2(MatchAny, "WHERE"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
-	else if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
+	if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* ... JOIN ... */
-	else if (TailMatches1("JOIN"))
+	if (TailMatches1("JOIN"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
-	else if (TailMatchesCS1("\\?"))
+	if (TailMatchesCS1("\\?"))
 		COMPLETE_WITH_LIST_CS3("commands", "options", "variables");
-	else if (TailMatchesCS1("\\connect|\\c"))
+	if (TailMatchesCS1("\\connect|\\c"))
 	{
 		if (!recognized_connection_string(text))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 	}
-	else if (TailMatchesCS2("\\connect|\\c", MatchAny))
+	if (TailMatchesCS2("\\connect|\\c", MatchAny))
 	{
 		if (!recognized_connection_string(prev_wd))
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	}
-	else if (TailMatchesCS1("\\da*"))
+	if (TailMatchesCS1("\\da*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
-	else if (TailMatchesCS1("\\dA*"))
+	if (TailMatchesCS1("\\dA*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (TailMatchesCS1("\\db*"))
+	if (TailMatchesCS1("\\db*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-	else if (TailMatchesCS1("\\dD*"))
+	if (TailMatchesCS1("\\dD*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-	else if (TailMatchesCS1("\\des*"))
+	if (TailMatchesCS1("\\des*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
-	else if (TailMatchesCS1("\\deu*"))
+	if (TailMatchesCS1("\\deu*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (TailMatchesCS1("\\dew*"))
+	if (TailMatchesCS1("\\dew*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
-	else if (TailMatchesCS1("\\df*"))
+	if (TailMatchesCS1("\\df*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 
-	else if (TailMatchesCS1("\\dFd*"))
+	if (TailMatchesCS1("\\dFd*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
-	else if (TailMatchesCS1("\\dFp*"))
+	if (TailMatchesCS1("\\dFp*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
-	else if (TailMatchesCS1("\\dFt*"))
+	if (TailMatchesCS1("\\dFt*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
 	/* must be at end of \dF alternatives: */
-	else if (TailMatchesCS1("\\dF*"))
+	if (TailMatchesCS1("\\dF*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
 
-	else if (TailMatchesCS1("\\di*"))
+	if (TailMatchesCS1("\\di*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (TailMatchesCS1("\\dL*"))
+	if (TailMatchesCS1("\\dL*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-	else if (TailMatchesCS1("\\dn*"))
+	if (TailMatchesCS1("\\dn*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-	else if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
+	if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-	else if (TailMatchesCS1("\\ds*"))
+	if (TailMatchesCS1("\\ds*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-	else if (TailMatchesCS1("\\dt*"))
+	if (TailMatchesCS1("\\dt*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-	else if (TailMatchesCS1("\\dT*"))
+	if (TailMatchesCS1("\\dT*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-	else if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
+	if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (TailMatchesCS1("\\dv*"))
+	if (TailMatchesCS1("\\dv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (TailMatchesCS1("\\dx*"))
+	if (TailMatchesCS1("\\dx*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
-	else if (TailMatchesCS1("\\dm*"))
+	if (TailMatchesCS1("\\dm*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (TailMatchesCS1("\\dE*"))
+	if (TailMatchesCS1("\\dE*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
-	else if (TailMatchesCS1("\\dy*"))
+	if (TailMatchesCS1("\\dy*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* must be at end of \d alternatives: */
-	else if (TailMatchesCS1("\\d*"))
+	if (TailMatchesCS1("\\d*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
-	else if (TailMatchesCS1("\\ef"))
+	if (TailMatchesCS1("\\ef"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (TailMatchesCS1("\\ev"))
+	if (TailMatchesCS1("\\ev"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
-	else if (TailMatchesCS1("\\encoding"))
+	if (TailMatchesCS1("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
-	else if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
+	if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
 		COMPLETE_WITH_LIST(sql_commands);
-	else if (TailMatchesCS1("\\l*") && !TailMatchesCS1("\\lo*"))
+	if (TailMatchesCS1("\\l*") && !TailMatchesCS1("\\lo*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-	else if (TailMatchesCS1("\\password"))
+	if (TailMatchesCS1("\\password"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (TailMatchesCS1("\\pset"))
+	if (TailMatchesCS1("\\pset"))
 	{
 		static const char *const my_list[] =
 		{"border", "columns", "expanded", "fieldsep", "fieldsep_zero",
@@ -3164,7 +2859,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (TailMatchesCS2("\\pset", MatchAny))
+	if (TailMatchesCS2("\\pset", MatchAny))
 	{
 		if (TailMatchesCS1("format"))
 		{
@@ -3174,53 +2869,53 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (TailMatchesCS1("linestyle"))
+		if (TailMatchesCS1("linestyle"))
 			COMPLETE_WITH_LIST_CS3("ascii", "old-ascii", "unicode");
-		else if (TailMatchesCS1("unicode_border_linestyle|"
+		if (TailMatchesCS1("unicode_border_linestyle|"
 								"unicode_column_linestyle|"
 								"unicode_header_linestyle"))
 			COMPLETE_WITH_LIST_CS2("single", "double");
 	}
-	else if (TailMatchesCS1("\\unset"))
+	if (TailMatchesCS1("\\unset"))
 	{
-		matches = complete_from_variables(text, "", "", true);
+		return complete_from_variables(text, "", "", true);
 	}
-	else if (TailMatchesCS1("\\set"))
+	if (TailMatchesCS1("\\set"))
 	{
-		matches = complete_from_variables(text, "", "", false);
+		return complete_from_variables(text, "", "", false);
 	}
-	else if (TailMatchesCS2("\\set", MatchAny))
+	if (TailMatchesCS2("\\set", MatchAny))
 	{
 		if (TailMatchesCS1("AUTOCOMMIT|ON_ERROR_STOP|QUIET|"
 						   "SINGLELINE|SINGLESTEP"))
 			COMPLETE_WITH_LIST_CS2("on", "off");
-		else if (TailMatchesCS1("COMP_KEYWORD_CASE"))
+		if (TailMatchesCS1("COMP_KEYWORD_CASE"))
 			COMPLETE_WITH_LIST_CS4("lower", "upper",
 								   "preserve-lower", "preserve-upper");
-		else if (TailMatchesCS1("ECHO"))
+		if (TailMatchesCS1("ECHO"))
 			COMPLETE_WITH_LIST_CS4("errors", "queries", "all", "none");
-		else if (TailMatchesCS1("ECHO_HIDDEN"))
+		if (TailMatchesCS1("ECHO_HIDDEN"))
 			COMPLETE_WITH_LIST_CS3("noexec", "off", "on");
-		else if (TailMatchesCS1("HISTCONTROL"))
+		if (TailMatchesCS1("HISTCONTROL"))
 			COMPLETE_WITH_LIST_CS4("ignorespace", "ignoredups",
 								   "ignoreboth", "none");
-		else if (TailMatchesCS1("ON_ERROR_ROLLBACK"))
+		if (TailMatchesCS1("ON_ERROR_ROLLBACK"))
 			COMPLETE_WITH_LIST_CS3("on", "off", "interactive");
-		else if (TailMatchesCS1("SHOW_CONTEXT"))
+		if (TailMatchesCS1("SHOW_CONTEXT"))
 			COMPLETE_WITH_LIST_CS3("never", "errors", "always");
-		else if (TailMatchesCS1("VERBOSITY"))
+		if (TailMatchesCS1("VERBOSITY"))
 			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
 	}
-	else if (TailMatchesCS1("\\sf*"))
+	if (TailMatchesCS1("\\sf*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (TailMatchesCS1("\\sv*"))
+	if (TailMatchesCS1("\\sv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
+	if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
 							"\\ir|\\include_relative|\\o|\\out|"
 							"\\s|\\w|\\write|\\lo_import"))
 	{
 		completion_charp = "\\";
-		matches = completion_matches(text, complete_from_files);
+		return completion_matches(text, complete_from_files);
 	}
 
 	/*
@@ -3238,7 +2933,7 @@ psql_completion(const char *text, int start, int end)
 			{
 				if (words_after_create[i].query)
 					COMPLETE_WITH_QUERY(words_after_create[i].query);
-				else if (words_after_create[i].squery)
+				if (words_after_create[i].squery)
 					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
 											   NULL);
 				break;
@@ -3246,25 +2941,8 @@ psql_completion(const char *text, int start, int end)
 		}
 	}
 
-	/*
-	 * If we still don't have anything to match we have to fabricate some sort
-	 * of default list. If we were to just return NULL, readline automatically
-	 * attempts filename completion, and that's usually no good.
-	 */
-	if (matches == NULL)
-	{
-		COMPLETE_WITH_CONST("");
-#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
-		rl_completion_append_character = '\0';
-#endif
-	}
-
-	/* free storage */
-	free(previous_words);
-	free(words_buffer);
-
-	/* Return our Grand List O' Matches */
-	return matches;
+	/* We found no match */
+	return NULL;
 }
 
 
-- 
2.9.2

0002-Make-keywords-case-follow-to-input.patchtext/x-patch; charset=us-asciiDownload
From dc8316716a1d55baa1a44240d44dc1a1bf39090d Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Wed, 14 Sep 2016 12:48:16 +0900
Subject: [PATCH 2/6] Make keywords' case follow to input

Currently some keywords suggested along with database objects are
always in upper case. This patch changes the behavior so that the case
of the additional keywords follow the setting of COMP_KEYWORD_CASE.

COMPLETE_WITH_ATTR needs completion_charp to be appendable, so this
patch changes it to a PQExpBuffer and adjust COMPLET_WITH_* macros to
that change.

This leaves keywords contained in Query_for_list_of_grant_roles and
Query_for_enum, but it is another problem.
---
 src/bin/psql/tab-complete-macros.h |  66 +++++--
 src/bin/psql/tab-complete.c        | 340 ++++++++++++++++++++-----------------
 2 files changed, 234 insertions(+), 172 deletions(-)

diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
index 97ffcd1..bd78f5d 100644
--- a/src/bin/psql/tab-complete-macros.h
+++ b/src/bin/psql/tab-complete-macros.h
@@ -231,16 +231,46 @@
  * 5) The list of attributes of the given table (possibly schema-qualified).
  * 6) The list of arguments to the given function (possibly schema-qualified).
  */
-#define COMPLETE_WITH_QUERY(query) \
+#define APPEND_COMP_CHARP(charp) \
+	appendPQExpBufferStr(completion_charp, charp);
+
+#define SET_COMP_CHARP(charp) \
+	resetPQExpBuffer(completion_charp);	\
+	APPEND_COMP_CHARP(charp);
+
+#define COMPLETION_CHARP (completion_charp->data)
+
+#define COMPLETE_WITH_QUERY(query)				\
+do { \
+	SET_COMP_CHARP(query);	\
+	return completion_matches(text, complete_from_query);	\
+} while (0)
+
+/*
+ * COMPLETE_WITH_QUERY with additional keywords. Keywords are complete
+ * case-sensitively
+ */
+#define COMPLETE_WITH_QUERY_KW(query, addon)				\
 do { \
-	completion_charp = query;	\
+	SET_COMP_CHARP(query);	\
+	APPEND_COMP_CHARP(addon); \
 	return completion_matches(text, complete_from_query);	\
 } while (0)
 
-#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
+#define COMPLETE_WITH_SCHEMA_QUERY(query) \
 do { \
 	completion_squery = &(query); \
-	completion_charp = addon; \
+	return completion_matches(text, complete_from_schema_query); \
+} while (0)
+
+/*
+ * COMPLETE_WITH_SCHEMA_QUERY with additional keywords. Keywords are complete
+ * case-sensitively
+ */
+#define COMPLETE_WITH_SCHEMA_QUERY_KW(query, addon)	\
+do { \
+	completion_squery = &(query); \
+	SET_COMP_CHARP(addon); \
 	return completion_matches(text, complete_from_schema_query); \
 } while (0)
 
@@ -260,7 +290,7 @@ do { \
 
 #define COMPLETE_WITH_CONST(string) \
 do { \
-	completion_charp = string;	\
+	SET_COMP_CHARP(string);	\
 	completion_case_sensitive = false; \
 	return completion_matches(text, complete_from_const);	\
 } while (0)
@@ -278,12 +308,14 @@ do { \
 								false, false, pset.encoding); \
 	if (_completion_table == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_attributes addon; \
+		SET_COMP_CHARP(Query_for_list_of_attributes); \
+		APPEND_COMP_CHARP(addon);					  \
 		completion_info_charp = relation; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_attributes_with_schema addon; \
+		SET_COMP_CHARP(Query_for_list_of_attributes_with_schema); \
+		APPEND_COMP_CHARP(addon); \
 		completion_info_charp = _completion_table; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -303,12 +335,12 @@ do { \
 							   false, false, pset.encoding);  \
 	if (_completion_type == NULL)\
 	{ \
-		completion_charp = Query_for_list_of_enum_values;	\
+		SET_COMP_CHARP(Query_for_list_of_enum_values);	\
 		completion_info_charp = type; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_enum_values_with_schema;	\
+		SET_COMP_CHARP(Query_for_list_of_enum_values_with_schema);	\
 		completion_info_charp = _completion_type; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -328,12 +360,12 @@ do { \
 								   false, false, pset.encoding); \
 	if (_completion_function == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_arguments; \
+		SET_COMP_CHARP(Query_for_list_of_arguments); \
 		completion_info_charp = function; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_arguments_with_schema;	\
+		SET_COMP_CHARP(Query_for_list_of_arguments_with_schema); \
 		completion_info_charp = _completion_function; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -425,6 +457,16 @@ do { \
 	COMPLETE_WITH_LIST_CS(list); \
 } while (0)
 
-
+#define ADDLIST1(s1) additional_kw_query(text, 1, s1)
+#define ADDLIST2(s1, s2) additional_kw_query(text, 2, s1, s2)
+#define ADDLIST3(s1, s2, s3) additional_kw_query(text, 3, s1, s2, s3)
+#define ADDLIST4(s1, s2, s3, s4) \
+	additional_kw_query(text, 4, s1, s2, s3, s4)
+#define ADDLIST13(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
+	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13)
+#define ADDLIST15(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
+	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13, s14, s15)
 
 #endif   /* TAB_COMPLETE_MACROS_H */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index fd3f68e..9860292 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -126,7 +126,7 @@ static int	completion_max_records;
  * Communication variables set by COMPLETE_WITH_FOO macros and then used by
  * the completion callback functions.  Ugly but there is no better way.
  */
-static const char *completion_charp;	/* to pass a string */
+static PQExpBuffer completion_charp = NULL;		/* to pass a string */
 static const char *const * completion_charpp;	/* to pass a list of strings */
 static const char *completion_info_charp;		/* to pass a second string */
 static const char *completion_info_charp2;		/* to pass a third string */
@@ -796,6 +796,7 @@ static char **complete_from_variables(const char *text,
 static char *complete_from_files(const char *text, int state);
 
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
+static char *additional_kw_query( const char *ref, int n, ...);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
 
@@ -967,7 +968,8 @@ psql_completion(const char *text, int start, int end)
 #endif
 
 	/* Clear a few things. */
-	completion_charp = NULL;
+	if (completion_charp == NULL)
+		completion_charp = createPQExpBuffer();
 	completion_charpp = NULL;
 	completion_info_charp = NULL;
 	completion_info_charp2 = NULL;
@@ -1076,8 +1078,8 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER TABLE */
 	if (Matches2("ALTER", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
+									  ADDLIST1("ALL IN TABLESPACE"));
 
 	/* ALTER something */
 	if (Matches1("ALTER"))
@@ -1193,8 +1195,8 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER INDEX */
 	if (Matches2("ALTER", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+									  ADDLIST1("ALL IN TABLESPACE"));
 	/* ALTER INDEX <name> */
 	if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
@@ -1222,8 +1224,8 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER MATERIALIZED VIEW */
 	if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_matviews,
+									  ADDLIST1("ALL IN TABLESPACE"));
 
 	/* ALTER USER,ROLE <name> */
 	if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
@@ -1378,7 +1380,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
 	if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 	/* ALTER TRIGGER <name> ON <name> */
 	if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
@@ -1424,10 +1426,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	}
 	/* ALTER TABLE xxx INHERIT */
 	if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* ALTER TABLE xxx NO INHERIT */
 	if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* ALTER TABLE xxx DISABLE */
 	if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
 		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
@@ -1444,11 +1446,11 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER TABLE xxx ALTER */
 	if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
+		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST2("COLUMN", "CONSTRAINT"));
 
 	/* ALTER TABLE xxx RENAME */
 	if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
+		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST3("COLUMN", "CONSTRAINT", "TO"));
 	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
@@ -1653,9 +1655,10 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
 	if (Matches1("CLUSTER"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("VERBOSE"));
 	if (Matches2("CLUSTER", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 	/* If we have CLUSTER <sth>, then add "USING" */
 	if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
 		COMPLETE_WITH_CONST("USING");
@@ -1702,7 +1705,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
 	}
 	if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 	if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 	if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
@@ -1717,11 +1720,11 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * backslash command).
 	 */
 	if (Matches1("COPY|\\copy"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION ALL SELECT '('");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
+									  ADDLIST1("("));
 	/* If we have COPY BINARY, complete with list of tables */
 	if (Matches2("COPY", "BINARY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* If we have COPY (, complete it with legal commands */
 	if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
@@ -1733,7 +1736,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
 			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
 	{
-		completion_charp = "";
+		SET_COMP_CHARP("");
 		return completion_matches(text, complete_from_files);
 	}
 
@@ -1802,21 +1805,20 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * existing indexes
 	 */
 	if (TailMatches2("CREATE|UNIQUE", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'ON'"
-								   " UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+									  ADDLIST2("ON", "CONCURRENTLY"));
 	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
 	if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
 			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 
 	/*
 	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
 	 * indexes
 	 */
 	if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'ON'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+									  ADDLIST1("ON"));
 	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
 	if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
 			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
@@ -1851,7 +1853,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
 	if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
 	if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING (", "WITH CHECK (");
@@ -1889,7 +1891,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
 	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 /* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
@@ -1945,47 +1947,47 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * tables
 	 */
 	if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
 	if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("ON", MatchAny))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("ON", MatchAny))
 		COMPLETE_WITH_LIST7("NOT DEFERRABLE", "DEFERRABLE", "INITIALLY",
 							"REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
+	if (HeadMatches2("CREATE", "TRIGGER") &&
 			 (TailMatches1("DEFERRABLE") || TailMatches2("INITIALLY", "IMMEDIATE|DEFERRED")))
 		COMPLETE_WITH_LIST4("REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("REFERENCING"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("REFERENCING"))
 		COMPLETE_WITH_LIST2("OLD TABLE", "NEW TABLE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("OLD|NEW", "TABLE"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("OLD|NEW", "TABLE"))
 		COMPLETE_WITH_CONST("AS");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches5("REFERENCING", "OLD", "TABLE", "AS", MatchAny) ||
-			  TailMatches4("REFERENCING", "OLD", "TABLE", MatchAny)))
+	if (HeadMatches2("CREATE", "TRIGGER") &&
+		(TailMatches5("REFERENCING", "OLD", "TABLE", "AS", MatchAny) ||
+		 TailMatches4("REFERENCING", "OLD", "TABLE", MatchAny)))
 		COMPLETE_WITH_LIST4("NEW TABLE", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches5("REFERENCING", "NEW", "TABLE", "AS", MatchAny) ||
-			  TailMatches4("REFERENCING", "NEW", "TABLE", MatchAny)))
+	if (HeadMatches2("CREATE", "TRIGGER") &&
+		(TailMatches5("REFERENCING", "NEW", "TABLE", "AS", MatchAny) ||
+		 TailMatches4("REFERENCING", "NEW", "TABLE", MatchAny)))
 		COMPLETE_WITH_LIST4("OLD TABLE", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches9("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
-			  TailMatches8("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
-			  TailMatches8("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", MatchAny) ||
-			  TailMatches7("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", MatchAny)))
+	if (HeadMatches2("CREATE", "TRIGGER") &&
+		(TailMatches9("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
+		 TailMatches8("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
+		 TailMatches8("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", MatchAny) ||
+		 TailMatches7("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", MatchAny)))
 		COMPLETE_WITH_LIST3("FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("FOR"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("FOR"))
 		COMPLETE_WITH_LIST3("EACH", "ROW", "STATEMENT");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("FOR", "EACH"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("FOR", "EACH"))
 		COMPLETE_WITH_LIST2("ROW", "STATEMENT");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
+	if (HeadMatches2("CREATE", "TRIGGER") &&
 			 (TailMatches3("FOR", "EACH", "ROW|STATEMENT") ||
 			  TailMatches2("FOR", "ROW|STATEMENT")))
 		COMPLETE_WITH_LIST2("WHEN (", "EXECUTE PROCEDURE");
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
 	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("EXECUTE", "PROCEDURE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("EXECUTE", "PROCEDURE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 
 /* CREATE ROLE,USER,GROUP <name> */
 	if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
@@ -2068,7 +2070,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
 	if (TailMatches2("DELETE", "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete DELETE FROM <table> */
 	if (TailMatches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
@@ -2106,10 +2108,10 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* DROP INDEX */
 	if (Matches2("DROP", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+									  ADDLIST1("CONCURRENTLY"));
 	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 	if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
@@ -2119,7 +2121,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 
 	/* DROP OWNED BY */
 	if (Matches2("DROP", "OWNED"))
@@ -2225,7 +2227,7 @@ psql_completion_internal(const char *text, char **previous_words,
 /* FOREIGN TABLE */
 	if (TailMatches2("FOREIGN", "TABLE") &&
 			 !TailMatches3("CREATE", MatchAny, MatchAny))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables);
 
 /* FOREIGN SERVER */
 	if (TailMatches2("FOREIGN", "SERVER"))
@@ -2234,20 +2236,10 @@ psql_completion_internal(const char *text, char **previous_words,
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
 	if (TailMatches1("GRANT|REVOKE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'SELECT'"
-							" UNION SELECT 'INSERT'"
-							" UNION SELECT 'UPDATE'"
-							" UNION SELECT 'DELETE'"
-							" UNION SELECT 'TRUNCATE'"
-							" UNION SELECT 'REFERENCES'"
-							" UNION SELECT 'TRIGGER'"
-							" UNION SELECT 'CREATE'"
-							" UNION SELECT 'CONNECT'"
-							" UNION SELECT 'TEMPORARY'"
-							" UNION SELECT 'EXECUTE'"
-							" UNION SELECT 'USAGE'"
-							" UNION SELECT 'ALL'");
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+			ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
+					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
+					  "EXECUTE", "USAGE", "ALL"));
 
 	/*
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
@@ -2275,22 +2267,22 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * privilege.
 	 */
 	if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
-								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
-								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
-								   " UNION SELECT 'ALL TABLES IN SCHEMA'"
-								   " UNION SELECT 'DATABASE'"
-								   " UNION SELECT 'DOMAIN'"
-								   " UNION SELECT 'FOREIGN DATA WRAPPER'"
-								   " UNION SELECT 'FOREIGN SERVER'"
-								   " UNION SELECT 'FUNCTION'"
-								   " UNION SELECT 'LANGUAGE'"
-								   " UNION SELECT 'LARGE OBJECT'"
-								   " UNION SELECT 'SCHEMA'"
-								   " UNION SELECT 'SEQUENCE'"
-								   " UNION SELECT 'TABLE'"
-								   " UNION SELECT 'TABLESPACE'"
-								   " UNION SELECT 'TYPE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tsvmf,
+			   ADDLIST15("ALL FUNCTIONS IN SCHEMA",
+						 "ALL SEQUENCES IN SCHEMA",
+						 "ALL TABLES IN SCHEMA",
+						 "DATABASE",
+						 "DOMAIN",
+						 "FOREIGN DATA WRAPPER",
+						 "FOREIGN SERVER",
+						 "FUNCTION",
+						 "LANGUAGE",
+						 "LARGE OBJECT",
+						 "SCHEMA",
+						 "SEQUENCE",
+						 "TABLE",
+						 "TABLESPACE",
+						 "TYPE"));
 
 	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
@@ -2310,21 +2302,21 @@ psql_completion_internal(const char *text, char **previous_words,
 		if (TailMatches1("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 		if (TailMatches1("DOMAIN"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains);
 		if (TailMatches1("FUNCTION"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 		if (TailMatches1("LANGUAGE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
 		if (TailMatches1("SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
 		if (TailMatches1("SEQUENCE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
 		if (TailMatches1("TABLE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf);
 		if (TailMatches1("TABLESPACE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 		if (TailMatches1("TYPE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 		if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
@@ -2388,7 +2380,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
 	if (TailMatches2("INSERT", "INTO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete "INSERT INTO <table> (" with attribute names */
 	if (TailMatches4("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
@@ -2415,10 +2407,10 @@ psql_completion_internal(const char *text, char **previous_words,
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
 	if (Matches1("LOCK"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION SELECT 'TABLE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
+									  ADDLIST1("TABLE"));
 	if (Matches2("LOCK", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 	/* For the following, handle the case of a single table only for now */
 
@@ -2492,10 +2484,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_matviews,
+									  ADDLIST1("CONCURRENTLY"));
 	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
 	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
@@ -2513,9 +2505,9 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
 	if (Matches2("REINDEX", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 	if (Matches2("REINDEX", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	if (Matches2("REINDEX", "SCHEMA"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
 	if (Matches2("REINDEX", "SYSTEM|DATABASE"))
@@ -2585,7 +2577,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
 	if (Matches2("SET", "CONSTRAINTS"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_constraints_with_schema,
+									  ADDLIST1("ALL"));
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
 	if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
@@ -2597,7 +2590,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
 	if (Matches3("SET", "SESSION", "AUTHORIZATION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+							   ADDLIST1("DEFAULT"));
 	/* Complete RESET SESSION with AUTHORIZATION */
 	if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
@@ -2623,10 +2617,10 @@ psql_completion_internal(const char *text, char **previous_words,
 			COMPLETE_WITH_LIST(my_list);
 		}
 		if (TailMatches2("search_path", "TO|="))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
-								" AND nspname not like 'pg\\_toast%%' "
-								" AND nspname not like 'pg\\_temp%%' "
-								" UNION SELECT 'DEFAULT' ");
+			COMPLETE_WITH_QUERY_KW(Query_for_list_of_schemas
+								   " AND nspname not like 'pg\\_toast%%' "
+								   " AND nspname not like 'pg\\_temp%%' ",
+								   ADDLIST1("DEFAULT"));
 		else
 		{
 			/* generic, type based, GUC support, guctype is malloc'ed */
@@ -2661,7 +2655,7 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* TABLE, but not TABLE embedded in other commands */
 	if (Matches1("TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations);
 
 /* TABLESAMPLE */
 	if (TailMatches1("TABLESAMPLE"))
@@ -2671,7 +2665,7 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* TRUNCATE */
 	if (Matches1("TRUNCATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 /* UNLISTEN */
 	if (Matches1("UNLISTEN"))
@@ -2680,7 +2674,7 @@ psql_completion_internal(const char *text, char **previous_words,
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
 	if (TailMatches1("UPDATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete UPDATE <table> with "SET" */
 	if (TailMatches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
@@ -2695,10 +2689,8 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
 	if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'CURRENT_USER'"
-							" UNION SELECT 'PUBLIC'"
-							" UNION SELECT 'USER'");
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+			ADDLIST3("CURRENT_USER", "PUBLIC", "USER"));
 	if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
 	if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
@@ -2711,29 +2703,26 @@ psql_completion_internal(const char *text, char **previous_words,
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
 	if (Matches1("VACUUM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'FULL'"
-								   " UNION SELECT 'FREEZE'"
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST4("FULL", "FREEZE",
+											   "ANALYZE", "VERBOSE"));
 	if (Matches2("VACUUM", "FULL|FREEZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST2("ANALYZE", "VERBOSE"));
 	if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("VERBOSE"));
 	if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("ANALYZE"));
 	if (Matches2("VACUUM", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("ANALYZE"));
 	if (Matches2("VACUUM", "ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("VERBOSE"));
 	if (HeadMatches1("VACUUM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 
 /* WITH [RECURSIVE] */
 
@@ -2747,7 +2736,7 @@ psql_completion_internal(const char *text, char **previous_words,
 /* ANALYZE */
 	/* Complete with list of tables */
 	if (Matches1("ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf);
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
@@ -2757,11 +2746,11 @@ psql_completion_internal(const char *text, char **previous_words,
 /* ... FROM ... */
 /* TODO: also include SRF ? */
 	if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf);
 
 /* ... JOIN ... */
 	if (TailMatches1("JOIN"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf);
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
@@ -2778,13 +2767,13 @@ psql_completion_internal(const char *text, char **previous_words,
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	}
 	if (TailMatchesCS1("\\da*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates);
 	if (TailMatchesCS1("\\dA*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 	if (TailMatchesCS1("\\db*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	if (TailMatchesCS1("\\dD*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains);
 	if (TailMatchesCS1("\\des*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
 	if (TailMatchesCS1("\\deu*"))
@@ -2792,7 +2781,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatchesCS1("\\dew*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 	if (TailMatchesCS1("\\df*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 
 	if (TailMatchesCS1("\\dFd*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
@@ -2805,40 +2794,40 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
 
 	if (TailMatchesCS1("\\di*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	if (TailMatchesCS1("\\dL*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
 	if (TailMatchesCS1("\\dn*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
 	if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf);
 	if (TailMatchesCS1("\\ds*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
 	if (TailMatchesCS1("\\dt*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	if (TailMatchesCS1("\\dT*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 	if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	if (TailMatchesCS1("\\dv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	if (TailMatchesCS1("\\dx*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
 	if (TailMatchesCS1("\\dm*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 	if (TailMatchesCS1("\\dE*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables);
 	if (TailMatchesCS1("\\dy*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* must be at end of \d alternatives: */
 	if (TailMatchesCS1("\\d*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations);
 
 	if (TailMatchesCS1("\\ef"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 	if (TailMatchesCS1("\\ev"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 
 	if (TailMatchesCS1("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
@@ -2907,14 +2896,14 @@ psql_completion_internal(const char *text, char **previous_words,
 			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
 	}
 	if (TailMatchesCS1("\\sf*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 	if (TailMatchesCS1("\\sv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
 							"\\ir|\\include_relative|\\o|\\out|"
 							"\\s|\\w|\\write|\\lo_import"))
 	{
-		completion_charp = "\\";
+		SET_COMP_CHARP("\\");
 		return completion_matches(text, complete_from_files);
 	}
 
@@ -2934,8 +2923,7 @@ psql_completion_internal(const char *text, char **previous_words,
 				if (words_after_create[i].query)
 					COMPLETE_WITH_QUERY(words_after_create[i].query);
 				if (words_after_create[i].squery)
-					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
-											   NULL);
+					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery);
 				break;
 			}
 		}
@@ -3182,13 +3170,13 @@ _complete_from_query(int is_schema_query, const char *text, int state)
 							  char_length, e_text);
 
 			/* If an addon query was provided, use it */
-			if (completion_charp)
-				appendPQExpBuffer(&query_buffer, "\n%s", completion_charp);
+			if (COMPLETION_CHARP[0])
+				appendPQExpBuffer(&query_buffer, "\n%s", COMPLETION_CHARP);
 		}
 		else
 		{
 			/* completion_charp is an sprintf-style format string */
-			appendPQExpBuffer(&query_buffer, completion_charp,
+			appendPQExpBuffer(&query_buffer, COMPLETION_CHARP,
 							  char_length, e_text,
 							  e_info_charp, e_info_charp,
 							  e_info_charp2, e_info_charp2);
@@ -3303,18 +3291,17 @@ complete_from_list(const char *text, int state)
 static char *
 complete_from_const(const char *text, int state)
 {
-	Assert(completion_charp != NULL);
 	if (state == 0)
 	{
 		if (completion_case_sensitive)
-			return pg_strdup(completion_charp);
+			return pg_strdup(COMPLETION_CHARP);
 		else
 
 			/*
 			 * If case insensitive matching was requested initially, adjust
 			 * the case according to setting.
 			 */
-			return pg_strdup_keyword_case(completion_charp, text);
+			return pg_strdup_keyword_case(COMPLETION_CHARP, text);
 	}
 	else
 		return NULL;
@@ -3415,7 +3402,7 @@ complete_from_files(const char *text, int state)
 	if (state == 0)
 	{
 		/* Initialization: stash the unquoted input. */
-		unquoted_text = strtokx(text, "", NULL, "'", *completion_charp,
+		unquoted_text = strtokx(text, "", NULL, "'", COMPLETION_CHARP[0],
 								false, true, pset.encoding);
 		/* expect a NULL return for the empty string only */
 		if (!unquoted_text)
@@ -3436,7 +3423,7 @@ complete_from_files(const char *text, int state)
 		 * bother providing a macro to simplify this.
 		 */
 		ret = quote_if_needed(unquoted_match, " \t\r\n\"`",
-							  '\'', *completion_charp, pset.encoding);
+							  '\'', COMPLETION_CHARP[0], pset.encoding);
 		if (ret)
 			free(unquoted_match);
 		else
@@ -3480,6 +3467,39 @@ pg_strdup_keyword_case(const char *s, const char *ref)
 	return ret;
 }
 
+/* Construct codelet to append given keywords  */
+static char *
+additional_kw_query(const char *ref, int n, ...)
+{
+	va_list ap;
+	static PQExpBuffer qbuf = NULL;
+	int i;
+
+	if (qbuf == NULL)
+		qbuf = createPQExpBuffer();
+	else
+		resetPQExpBuffer(qbuf);
+
+	/* Construct an additional queriy to append keywords */
+	appendPQExpBufferStr(qbuf, " UNION ALL SELECT * FROM (VALUES ");
+
+	va_start(ap, n);
+	for (i = 0 ; i < n ; i++)
+	{
+		char *item = pg_strdup_keyword_case(va_arg(ap, char *), ref);
+		if (i > 0) appendPQExpBufferChar(qbuf, ',');
+		appendPQExpBufferStr(qbuf, "('");
+		appendPQExpBufferStr(qbuf, item);
+		appendPQExpBufferStr(qbuf, "')");
+		pg_free(item);
+	}
+	va_end(ap);
+
+	appendPQExpBufferStr(qbuf, ") as x");
+
+	return qbuf->data;
+}
+
 
 /*
  * escape_string - Escape argument for use as string literal.
-- 
2.9.2

0003-Fix-suggested-keywords-to-follow-input-in-tab-comple.patchtext/x-patch; charset=us-asciiDownload
From b2bace6bca7b7f5d80297ba064fba28cf0b07c62 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 16 Sep 2016 11:36:13 +0900
Subject: [PATCH 3/6] Fix suggested keywords to follow input in tab-completion
 session 2

The prevous fix left some completions unfixed. This commit moves
keywords of Query_for_list_of_set_vars/alter_system_set_vars/show_vars
out of the constant and process the keywords as the same as other
syntaxes.

Role name is a bother. PUBLIC, CURRENT_USER, SESSION_USER are
certainly roles and the last two are used in many places. In this
patch I renamed Query_for_list_of_roles to
Query_for_list_of_true_roles and added Query_for_list_of_roles that
having CURRENT_USER and SESSION_USER. But this might be too much.
---
 src/bin/psql/tab-complete-macros.h |   2 +
 src/bin/psql/tab-complete.c        | 100 ++++++++++++++++++++++---------------
 2 files changed, 62 insertions(+), 40 deletions(-)

diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
index bd78f5d..d7e2e3f 100644
--- a/src/bin/psql/tab-complete-macros.h
+++ b/src/bin/psql/tab-complete-macros.h
@@ -462,6 +462,8 @@ do { \
 #define ADDLIST3(s1, s2, s3) additional_kw_query(text, 3, s1, s2, s3)
 #define ADDLIST4(s1, s2, s3, s4) \
 	additional_kw_query(text, 4, s1, s2, s3, s4)
+#define ADDLIST6(s1, s2, s3, s4, s5, s6)				\
+	additional_kw_query(text, 6, s1, s2, s3, s4, s5, s6)
 #define ADDLIST13(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
 	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
 						s8, s9, s10, s11, s12, s13)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 9860292..c14f9f3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -469,43 +469,38 @@ static const SchemaQuery Query_for_list_of_matviews = {
 " WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s'"
 
 #define Query_for_list_of_alter_system_set_vars \
-"SELECT name FROM "\
-" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
-"  WHERE context != 'internal') ss "\
-" WHERE substring(name,1,%d)='%s'"\
-" UNION ALL SELECT 'all' ss"
-
-#define Query_for_list_of_set_vars \
-"SELECT name FROM "\
-" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
-"  WHERE context IN ('user', 'superuser') "\
-"  UNION ALL SELECT 'constraints' "\
-"  UNION ALL SELECT 'transaction' "\
-"  UNION ALL SELECT 'session' "\
-"  UNION ALL SELECT 'role' "\
-"  UNION ALL SELECT 'tablespace' "\
-"  UNION ALL SELECT 'all') ss "\
+"SELECT name FROM "													\
+" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings " \
+"  WHERE context != 'internal') ss "								\
 " WHERE substring(name,1,%d)='%s'"
 
+#define Query_for_list_of_set_vars									 \
+"SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings " \
+"  WHERE context IN ('user', 'superuser') "							\
+"   AND substring(name,1,%d)='%s'"
+
 #define Query_for_list_of_show_vars \
-"SELECT name FROM "\
-" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
-"  UNION ALL SELECT 'session authorization' "\
-"  UNION ALL SELECT 'all') ss "\
+"SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings " \
 " WHERE substring(name,1,%d)='%s'"
 
-#define Query_for_list_of_roles \
+#define Query_for_list_of_true_roles \
 " SELECT pg_catalog.quote_ident(rolname) "\
 "   FROM pg_catalog.pg_roles "\
 "  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"
 
+#define Query_for_list_of_roles \
+	concatenate_strings(												\
+		" SELECT pg_catalog.quote_ident(rolname) "						\
+		 "   FROM pg_catalog.pg_roles "									\
+		 "  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'",	\
+		ADDLIST2("CURRENT_USER", "SESSION_USER"))
+
 #define Query_for_list_of_grant_roles \
-" SELECT pg_catalog.quote_ident(rolname) "\
-"   FROM pg_catalog.pg_roles "\
-"  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"\
-" UNION ALL SELECT 'PUBLIC'"\
-" UNION ALL SELECT 'CURRENT_USER'"\
-" UNION ALL SELECT 'SESSION_USER'"
+	concatenate_strings(												\
+		" SELECT pg_catalog.quote_ident(rolname) "						\
+		 "   FROM pg_catalog.pg_roles "									\
+		 "  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'",	\
+		ADDLIST3("PUBLIC", "CURRENT_USER", "SESSION_USER"))
 
 /* the silly-looking length condition is just to eat up the current word */
 #define Query_for_table_owning_index \
@@ -701,13 +696,13 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "         WHERE pg_catalog.quote_ident(polname)='%s')"
 
 #define Query_for_enum \
-" SELECT name FROM ( "\
-"   SELECT pg_catalog.quote_ident(pg_catalog.unnest(enumvals)) AS name "\
-"     FROM pg_catalog.pg_settings "\
-"    WHERE pg_catalog.lower(name)=pg_catalog.lower('%s') "\
-"    UNION ALL " \
-"   SELECT 'DEFAULT' ) ss "\
-"  WHERE pg_catalog.substring(name,1,%%d)='%%s'"
+	concatenate_strings( \
+		" SELECT name FROM ( "											\
+	"   SELECT pg_catalog.quote_ident(pg_catalog.unnest(enumvals)) AS name " \
+	"     FROM pg_catalog.pg_settings "									\
+	"    WHERE pg_catalog.lower(name)=pg_catalog.lower('%s')) ss "		\
+	"  WHERE pg_catalog.substring(name,1,%%d)='%%s'", \
+		ADDLIST1("DEFAULT"))
 
 /*
  * This is a list of all "things" in Pgsql, which can show up after CREATE or
@@ -747,7 +742,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"FOREIGN DATA WRAPPER", NULL, NULL},
 	{"FOREIGN TABLE", NULL, NULL},
 	{"FUNCTION", NULL, &Query_for_list_of_functions},
-	{"GROUP", Query_for_list_of_roles},
+	{"GROUP", Query_for_list_of_true_roles},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"INDEX", NULL, &Query_for_list_of_indexes},
 	{"MATERIALIZED VIEW", NULL, &Query_for_list_of_matviews},
@@ -756,7 +751,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"OWNED", NULL, NULL, THING_NO_CREATE},		/* for DROP OWNED BY ... */
 	{"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
 	{"POLICY", NULL, NULL},
-	{"ROLE", Query_for_list_of_roles},
+	{"ROLE", Query_for_list_of_true_roles},
 	{"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"},
 	{"SCHEMA", Query_for_list_of_schemas},
 	{"SEQUENCE", NULL, &Query_for_list_of_sequences},
@@ -771,7 +766,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"UNIQUE", NULL, NULL, THING_NO_DROP},		/* for CREATE UNIQUE INDEX ... */
 	{"UNLOGGED", NULL, NULL, THING_NO_DROP},	/* for CREATE UNLOGGED TABLE
 												 * ... */
-	{"USER", Query_for_list_of_roles},
+	{"USER", Query_for_list_of_true_roles},
 	{"USER MAPPING FOR", NULL, NULL},
 	{"VIEW", NULL, &Query_for_list_of_views},
 	{NULL}						/* end of list */
@@ -796,6 +791,7 @@ static char **complete_from_variables(const char *text,
 static char *complete_from_files(const char *text, int state);
 
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
+static char *concatenate_strings(const char *s1, const char *s2);
 static char *additional_kw_query( const char *ref, int n, ...);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
@@ -1316,7 +1312,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
 	if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_alter_system_set_vars,
+							   ADDLIST1("ALL"));
 	/* ALTER VIEW <name> */
 	if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
@@ -2540,9 +2537,12 @@ psql_completion_internal(const char *text, char **previous_words,
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
 	if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_set_vars,
+							   ADDLIST6("CONSTRAINTS", "TRANSACTION", "SESSION",
+										"ROLE", "TABLESPACE", "ALL"));
 	if (Matches1("SHOW"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_show_vars,
+							   ADDLIST2("SESSION AUTHORIZATION", "ALL"));
 	/* Complete "SET TRANSACTION" */
 	if (Matches2("SET", "TRANSACTION"))
 		COMPLETE_WITH_LIST5("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
@@ -3467,6 +3467,26 @@ pg_strdup_keyword_case(const char *s, const char *ref)
 	return ret;
 }
 
+/*
+ * Returns concatenated string.
+ * Note: the internal buffer is static so this cannot be called recursively.
+ */
+static char *
+concatenate_strings(const char *s1, const char *s2)
+{
+	static PQExpBuffer qbuf = NULL;
+	
+	if (qbuf == NULL)
+		qbuf = createPQExpBuffer();
+	else
+		resetPQExpBuffer(qbuf);
+
+	appendPQExpBufferStr(qbuf, s1);
+	appendPQExpBufferStr(qbuf, s2);
+
+	return qbuf->data;
+}
+
 /* Construct codelet to append given keywords  */
 static char *
 additional_kw_query(const char *ref, int n, ...)
-- 
2.9.2

0004-Introduce-word-shift-and-removal-feature-to-psql-com.patchtext/x-patch; charset=us-asciiDownload
From a8d4699ede347e37615874bca4787ac4b0d1d620 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Thu, 15 Sep 2016 14:44:55 +0900
Subject: [PATCH 4/6] Introduce word shift and removal feature to
 psql-completion

Currently completion of psql is vulnerable for noise words such like
temp/temporary, unlogged or concurrent. Addition to that, schema
elemsnts in CREATE SCHEMA syntax or some recursive syntaxes are
processed in somewhat bogus way.  To accommodate completion mechanism
for the cases, this patch introduces mainly two features.

1. Add a feature to ignore leading words to process.  New macros
  HEAD_SHIFT, HEAD_SET to shift or set the position of the first word
  to match using other macros. All *MatchesN macros follow
  this. SHIFT_TO_LAST1 is a macro to shift the head to the position
  where the specified word is found last.

2. Add a feature to remove intermediate words from previous_words
  list.  COLLAPSE(s, n) macro removes n words from the s'th position
  (1-based). Removing "noise" words let the succeeding operations
  simple.

Using this features, this patch implements the following things.

1. Properly treat schema elements by shifting head.

 Now we can treat schema elements as the same as normal syntax by
 isolating from leading words. This allow more complex completion for
 each create xxxs.

2. Simplify some "||"-connected matches required to cover noise words
  by removing noise wors.

 CREATE INDEX or some other syntaxes have rather many optional
 elements so appropriate to demonstrate how COLLAPSE can be used.
 ALTER TABLE/COMMENT/COPY/GRANT/REVOKE/BEGINs.

3. Process recursive of CREATE RULE/EXPLAIN/PREPARE using
  Matches/HeadMatches, not with TailMtaches.

 The recursive syntaxes are previously completed using TailMatches
 instead of Match/HeadMatch but that replacement increases the
 restriction in completing the inner commands.  psql_complete_internal
 can be called recursively and it can be used to resolve this.

The changes in this patch also allows us to encapsulate completion
code for common subsyntaxes in functions but this doesn't that.
---
 src/bin/psql/tab-complete-macros.h | 139 ++++++--
 src/bin/psql/tab-complete.c        | 696 ++++++++++++++++++++++---------------
 2 files changed, 514 insertions(+), 321 deletions(-)

diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
index d7e2e3f..bb3cf68 100644
--- a/src/bin/psql/tab-complete-macros.h
+++ b/src/bin/psql/tab-complete-macros.h
@@ -25,41 +25,69 @@
 #define prev8_wd  (previous_words[7])
 #define prev9_wd  (previous_words[8])
 
+/* Return the number of stored words counting head shift */
+#define WORD_COUNT() (previous_words_count - head_shift)
+
 /*
  * Return the index in previous_words for index from the beginning. n is
  * 1-based and the result is 0-based.
  */
-#define HEAD_INDEX(n) \
-	(previous_words_count - (n))
+#define HEAD_INDEX(n) (WORD_COUNT() - (n))
+
+/* Move the position of the beginning word for matching macros.  */
+#define HEAD_SHIFT(n) (head_shift += (n))
+
+/* Set the position of the beginning word for matching macros.  */
+#define HEAD_SET(n) (head_shift = (n))
+
+/*
+ * remove n words from current shifted position. This moves entire the
+ * previous_words regardless of head_shift.
+ */
+#define COLLAPSE(s, n)							\
+	do { \
+		memmove(previous_words + HEAD_INDEX((s) + (n) - 1), \
+				previous_words + HEAD_INDEX((s) - 1), \
+				sizeof(char *) * \
+				(previous_words_count - HEAD_INDEX((s) - 1)));	\
+		previous_words_count -= (n); \
+	} while (0)
+
+/*
+ * Find the position where the specified word appears last and shift to there.
+ * The words before the position will be ignored ever after.
+ */
+#define SHIFT_TO_LAST1(p1) \
+	HEAD_SHIFT(find_last_index_of(p1, previous_words, previous_words_count))
 
 /*
  * Macros for matching the last N words before point, and after head_sift,
  * case-insensitively.
  */
 #define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches(p1, prev_wd))
 
 #define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd))
 
 #define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
+	(WORD_COUNT() >= 3 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd))
 
 #define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
+	(WORD_COUNT() >= 4 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
 	 word_matches(p4, prev4_wd))
 
 #define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
+	(WORD_COUNT() >= 5 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -67,7 +95,7 @@
 	 word_matches(p5, prev5_wd))
 
 #define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
+	(WORD_COUNT() >= 6 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -76,7 +104,7 @@
 	 word_matches(p6, prev6_wd))
 
 #define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
+	(WORD_COUNT() >= 7 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -86,7 +114,7 @@
 	 word_matches(p7, prev7_wd))
 
 #define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
+	(WORD_COUNT() >= 8 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -97,7 +125,7 @@
 	 word_matches(p8, prev8_wd))
 
 #define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
+	(WORD_COUNT() >= 9 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -113,10 +141,10 @@
 	 * head_shift, case-sensitively.
 	 */
 #define TailMatchesCS1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches_cs(p1, prev_wd))
 #define TailMatchesCS2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches_cs(p1, prev_wd) && \
 	 word_matches_cs(p2, prev2_wd))
 
@@ -125,31 +153,31 @@
 	 * case-insensitively.
 	 */
 #define Matches1(p1) \
-	(previous_words_count == 1 && \
+	(WORD_COUNT() == 1 && \
 	 TailMatches1(p1))
 #define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
+	(WORD_COUNT() == 2 && \
 	 TailMatches2(p1, p2))
 #define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
+	(WORD_COUNT() == 3 && \
 	 TailMatches3(p1, p2, p3))
 #define Matches4(p1, p2, p3, p4) \
-	(previous_words_count == 4 && \
+	(WORD_COUNT() == 4 && \
 	 TailMatches4(p1, p2, p3, p4))
 #define Matches5(p1, p2, p3, p4, p5) \
-	(previous_words_count == 5 && \
+	(WORD_COUNT() == 5 && \
 	 TailMatches5(p1, p2, p3, p4, p5))
 #define Matches6(p1, p2, p3, p4, p5, p6) \
-	(previous_words_count == 6 && \
+	(WORD_COUNT() == 6 && \
 	 TailMatches6(p1, p2, p3, p4, p5, p6))
 #define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(previous_words_count == 7 && \
+	(WORD_COUNT() == 7 && \
 	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
 #define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(previous_words_count == 8 && \
+	(WORD_COUNT() == 8 && \
 	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
 #define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(previous_words_count == 9 && \
+	(WORD_COUNT() == 9 && \
 	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
 
 /*
@@ -195,16 +223,39 @@
 	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
 	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]))
 
-#define MidMatches7(s,p1, p2, p3, p4, p5, p6, p7)	\
+#define MidMatches7(s,p1, p2, p3, p4, p5, p6, p7)			\
 	(HEAD_INDEX((s) + 6) >= 0 &&							\
-	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
-	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
-	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&			\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&		\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&			\
 	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
 	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
 	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&			\
 	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]))
 
+#define MidMatches8(s,p1, p2, p3, p4, p5, p6, p7, p8)		\
+	(HEAD_INDEX((s) + 7) >= 0 &&							\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&				\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&		\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&		\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&		\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&		\
+	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]) &&		\
+	 word_matches(p8, previous_words[HEAD_INDEX((s) + 7)]))
+
+#define MidMatches9(s,p1, p2, p3, p4, p5, p6, p7, p8, p9)		\
+	(HEAD_INDEX((s) + 8) >= 0 &&							\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&				\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&		\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&		\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&		\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&		\
+	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]) &&		\
+	 word_matches(p8, previous_words[HEAD_INDEX((s) + 7)]) &&		\
+	 word_matches(p9, previous_words[HEAD_INDEX((s) + 8)]))
+
 #define HeadMatches1(p1) \
 	MidMatches1(1, p1)
 #define HeadMatches2(p1, p2) \
@@ -219,6 +270,10 @@
 	MidMatches6(1, p1, p2, p3, p4, p5, p6)
 #define HeadMatches7(p1, p2, p3, p4, p5, p6, p7) \
 	MidMatches7(1, p1, p2, p3, p4, p5, p6, p7)
+#define HeadMatches8(p1, p2, p3, p4, p5, p6, p7, p8)	\
+	MidMatches8(1, p1, p2, p3, p4, p5, p6, p7, p8)
+#define HeadMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9)	\
+	MidMatches9(1, p1, p2, p3, p4, p5, p6, p7, p8, p9)
 
 /*
  * A few macros to ease typing. You can use these to complete the given
@@ -240,12 +295,6 @@
 
 #define COMPLETION_CHARP (completion_charp->data)
 
-#define COMPLETE_WITH_QUERY(query)				\
-do { \
-	SET_COMP_CHARP(query);	\
-	return completion_matches(text, complete_from_query);	\
-} while (0)
-
 /*
  * COMPLETE_WITH_QUERY with additional keywords. Keywords are complete
  * case-sensitively
@@ -257,11 +306,8 @@ do { \
 	return completion_matches(text, complete_from_query);	\
 } while (0)
 
-#define COMPLETE_WITH_SCHEMA_QUERY(query) \
-do { \
-	completion_squery = &(query); \
-	return completion_matches(text, complete_from_schema_query); \
-} while (0)
+#define COMPLETE_WITH_QUERY(query) COMPLETE_WITH_QUERY_KW((query), "")
+
 
 /*
  * COMPLETE_WITH_SCHEMA_QUERY with additional keywords. Keywords are complete
@@ -274,6 +320,8 @@ do { \
 	return completion_matches(text, complete_from_schema_query); \
 } while (0)
 
+#define COMPLETE_WITH_SCHEMA_QUERY(query) COMPLETE_WITH_SCHEMA_QUERY_KW((query), "")
+
 #define COMPLETE_WITH_LIST_CS(list) \
 do { \
 	completion_charpp = list; \
@@ -295,7 +343,7 @@ do { \
 	return completion_matches(text, complete_from_const);	\
 } while (0)
 
-#define COMPLETE_WITH_ATTR(relation, addon) \
+#define COMPLETE_WITH_ATTR_KW(relation, addon) \
 do { \
 	char   *_completion_schema; \
 	char   *_completion_table; \
@@ -322,6 +370,8 @@ do { \
 	return completion_matches(text, complete_from_query); \
 } while (0)
 
+#define COMPLETE_WITH_ATTR(query) COMPLETE_WITH_ATTR_KW((query), "")
+
 #define COMPLETE_WITH_ENUM_VALUE(type) \
 do { \
 	char   *_completion_schema; \
@@ -471,4 +521,17 @@ do { \
 	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
 						s8, s9, s10, s11, s12, s13, s14, s15)
 
+#define COMPLETE_THING(p) \
+do { \
+	const pgsql_thing_t *ent = find_thing_entry(previous_words[-(p) - 1]);	\
+	if (ent) \
+	{ \
+		if (ent->query) \
+			COMPLETE_WITH_QUERY(ent->query); \
+		else if (ent->squery) \
+			COMPLETE_WITH_SCHEMA_QUERY(*ent->squery); \
+	} \
+	return NULL; \
+} while (0)
+
 #endif   /* TAB_COMPLETE_MACROS_H */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index c14f9f3..ec5a700 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -790,6 +790,7 @@ static char **complete_from_variables(const char *text,
 					const char *prefix, const char *suffix, bool need_value);
 static char *complete_from_files(const char *text, int state);
 
+static int find_last_index_of(char *w, char **previous_words, int len);
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static char *concatenate_strings(const char *s1, const char *s2);
 static char *additional_kw_query( const char *ref, int n, ...);
@@ -802,6 +803,7 @@ static char *get_guctype(const char *varname);
 
 static char **psql_completion_internal(const char *text, char **previous_words,
 										   int previous_words_count);
+static const pgsql_thing_t *find_thing_entry(char *word);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -1010,6 +1012,9 @@ static char **
 psql_completion_internal(const char *text, char **previous_words,
 						 int previous_words_count)
 {
+	/* The number of prefixing words to be ignored */
+	int			head_shift = 0;
+
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
@@ -1060,10 +1065,24 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
+	/*
+	 * If this is in CREATE SCHEMA, seek to the last CREATE and regard it as
+	 * current command to complete.
+	 */
+	if (HeadMatches2("CREATE", "SCHEMA"))
+		SHIFT_TO_LAST1("CREATE|GRANT");
+
 /* CREATE */
 	/* complete with something you can create */
 	if (Matches1("CREATE"))
-		return completion_matches(text, create_command_generator);
+	{
+		if (head_shift == 0)
+			return completion_matches(text, create_command_generator);
+		else
+			/* schema_element allows some kinds of object */
+			COMPLETE_WITH_LIST5("TABLE", "VIEW", "INDEX", "SEQUENCE",
+								"TRIGGER");
+	}			
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
@@ -1443,13 +1462,13 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER TABLE xxx ALTER */
 	if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
-		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST2("COLUMN", "CONSTRAINT"));
+		COMPLETE_WITH_ATTR_KW(prev2_wd, ADDLIST2("COLUMN", "CONSTRAINT"));
 
 	/* ALTER TABLE xxx RENAME */
 	if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
-		COMPLETE_WITH_ATTR(prev2_wd, ADDLIST3("COLUMN", "CONSTRAINT", "TO"));
+		COMPLETE_WITH_ATTR_KW(prev2_wd, ADDLIST3("COLUMN", "CONSTRAINT", "TO"));
 	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 
 	/* ALTER TABLE xxx RENAME yyy */
 	if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
@@ -1464,7 +1483,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
@@ -1475,26 +1494,25 @@ psql_completion_internal(const char *text, char **previous_words,
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
 	}
+	/* Remove COLUMN just after ALTER */
+	if (HeadMatches5("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN"))
+		COLLAPSE(5, 1);
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
-			 Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
 		COMPLETE_WITH_LIST4("TYPE", "SET", "RESET", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
-			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
 		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
-		 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
-	Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
 		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
-			 Matches8("ALTER", "TABLE", MatchAny, "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("DEFAULT", "NOT NULL");
+
 	if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
 	if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
@@ -1615,7 +1633,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * of attributes
 	 */
 	if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
 	if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
@@ -1632,7 +1650,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	/*
 	 * If we have ALTER TYPE <sth> RENAME VALUE, provide list of enum values
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "RENAME", "VALUE"))
+	if (Matches5("ALTER", "TYPE", MatchAny, "RENAME", "VALUE"))
 		COMPLETE_WITH_ENUM_VALUE(prev3_wd);
 
 /* BEGIN */
@@ -1654,17 +1672,16 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
 									  ADDLIST1("VERBOSE"));
-	if (Matches2("CLUSTER", "VERBOSE"))
+	/* Remove VERBOSE for further completion */
+	if (HeadMatches2("CLUSTER", "VERBOSE"))
+		COLLAPSE(2, 1);
+	if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 	/* If we have CLUSTER <sth>, then add "USING" */
-	if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
-		COMPLETE_WITH_CONST("USING");
-	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	if (Matches3("CLUSTER", "VERBOSE", MatchAny))
+	if (Matches2("CLUSTER", MatchAny))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER <sth> USING, then add the index as well */
-	if (Matches3("CLUSTER", MatchAny, "USING") ||
-			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
+	if (Matches3("CLUSTER", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
@@ -1705,9 +1722,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 	if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
-		Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
-			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
+	if (HeadMatches3("COMMENT", "ON", MatchAny) &&
+		TailMatches1(MatchAnyExcept("IS")))
 		COMPLETE_WITH_CONST("IS");
 
 /* COPY */
@@ -1718,34 +1734,32 @@ psql_completion_internal(const char *text, char **previous_words,
 	 */
 	if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
-									  ADDLIST1("("));
+									  ADDLIST2("(", "BINARY"));
 	/* If we have COPY BINARY, complete with list of tables */
 	if (Matches2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
+	/* Remove BINARY of COPY for further completion */
+	if (HeadMatches2("COPY", "BINARY"))
 	/* If we have COPY (, complete it with legal commands */
 	if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
 	/* If we have COPY [BINARY] <sth>, complete it with "TO" or "FROM" */
-	if (Matches2("COPY|\\copy", MatchAny) ||
-			 Matches3("COPY", "BINARY", MatchAny))
+	if (Matches2("COPY|\\copy", MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "TO");
 	/* If we have COPY [BINARY] <sth> FROM|TO, complete with filename */
-	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
-			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
+	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO"))
 	{
 		SET_COMP_CHARP("");
 		return completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename */
-	if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
-			 Matches5("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
+	if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny))
 		COMPLETE_WITH_LIST6("BINARY", "OIDS", "DELIMITER", "NULL", "CSV",
 							"ENCODING");
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename CSV */
-	if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
-			 Matches6("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny, "CSV"))
+	if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV"))
 		COMPLETE_WITH_LIST5("HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE",
 							"FORCE NOT NULL");
 
@@ -1792,57 +1806,47 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
 
-	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	/* CREATE INDEX */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	if (TailMatches2("CREATE", "UNIQUE"))
+	if (Matches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
-	/*
-	 * If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY", and
-	 * existing indexes
-	 */
-	if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	/* Remove UNIQUE for further completion */
+	if (HeadMatches3("CREATE", "UNIQUE", "INDEX"))
+		COLLAPSE(2, 1);
+	/* Complete with index names as category suggestion and possible keywords */
+	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
 									  ADDLIST2("ON", "CONCURRENTLY"));
-	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
-			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
-
-	/*
-	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
-	 * indexes
-	 */
-	if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+	/* Remove CONCURRENTLY for further completion */
+	if (HeadMatches3("CREATE", "INDEX", "CONCURRENTLY"))
+		COLLAPSE(3, 1);
+	/* Complete with existing index names as word category suggestion */
+	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
 									  ADDLIST1("ON"));
-	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
-			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
+	/* Suggest ON just after index name */
+	if (Matches3("CREATE", "INDEX", MatchAnyExcept("ON")))
 		COMPLETE_WITH_CONST("ON");
-
-	/*
-	 * Complete INDEX <name> ON <table> with a list of table columns (which
-	 * should really be in parens)
-	 */
-	if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-		TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+	/* Specified index name does not matter ever after */
+	if (HeadMatches4("CREATE", "INDEX", MatchAny, "ON"))
+		COLLAPSE(3, 1);
+	/* Complete with table names only*/
+	if (Matches3("CREATE", "INDEX", "ON"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
+	/* MatchAny is table name */
+	if (Matches4("CREATE", "INDEX", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-		TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
-	/* same if you put in USING */
-	if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
-			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
-			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
+	if (Matches5("CREATE", "INDEX", "ON", MatchAny, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
-			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
-			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
+	/*  Remove "Using xxx" for further completion*/
+	if (HeadMatches6("CREATE", "INDEX", "ON", MatchAny, "USING", MatchAny))
+		COLLAPSE(5, 2);
+	if (Matches4("CREATE", "INDEX", "ON", MatchAny))
 		COMPLETE_WITH_CONST("(");
+	if (Matches5("CREATE", "INDEX", "ON", MatchAny, "("))
+		COMPLETE_WITH_ATTR(prev2_wd);
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
@@ -1874,43 +1878,72 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
-	/* Complete "CREATE RULE <sth>" with "AS ON" */
-	if (Matches3("CREATE", "RULE", MatchAny))
-		COMPLETE_WITH_CONST("AS ON");
-	/* Complete "CREATE RULE <sth> AS" with "ON" */
-	if (Matches4("CREATE", "RULE", MatchAny, "AS"))
-		COMPLETE_WITH_CONST("ON");
-	/* Complete "CREATE RULE <sth> AS ON" with SELECT|UPDATE|INSERT|DELETE */
-	if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
-		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
-	/* Complete "AS ON SELECT|UPDATE|INSERT|DELETE" with a "TO" */
-	if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
-		COMPLETE_WITH_CONST("TO");
-	/* Complete "AS ON <sth> TO" with a table name */
-	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
+	if (HeadMatches2("CREATE", "RULE"))
+	{
+		/* Complete "CREATE RULE <sth>" with "AS ON" */
+		if (Matches3("CREATE", "RULE", MatchAny))
+			COMPLETE_WITH_CONST("AS ON");
+		/* Complete "CREATE RULE <sth> AS" with "ON" */
+		if (Matches4("CREATE", "RULE", MatchAny, "AS"))
+			COMPLETE_WITH_CONST("ON");
+		/* Complete "CREATE RULE <sth> AS ON" with SELECT|UPDATE|INSERT|DELETE */
+		if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
+			COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
+		/* Complete "AS ON SELECT|UPDATE|INSERT|DELETE" with a "TO" */
+		if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
+			COMPLETE_WITH_CONST("TO");
+		/* Complete "AS ON <sth> TO" with a table name */
+		if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
+		/* Complete "ON <sth> TO <name>" with DO INSTEAD*/
+		if (TailMatches4("ON", "SELECT|UPDATE|INSERT|DELETE", "TO", MatchAny))
+			COMPLETE_WITH_CONST("DO INSTEAD");
+		/* Complete DO INSTEAD with actions */
+		if (TailMatches2("DO", "INSTEAD"))
+			COMPLETE_WITH_LIST5("SELECT", "INSERT", "UPDATE", "DELETE", "NOTIFY");
+		/* Complete DO INSTEAD and further */
+		SHIFT_TO_LAST1("SELECT|INSERT|UPDATE|DELETE|NOTIFY");
+		if (HeadMatches1("SELECT|INSERT|UPDATE|DELETE|NOTIFY"))
+			return psql_completion_internal(text, previous_words, WORD_COUNT());
+		COMPLETE_WITH_CONST("");
+	}
+	
+	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
+	if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
+	/* Remove TEMPORARY/TEMP for further completion */
+	if (HeadMatches3("CREATE", "TEMP|TEMPORARY", "TABLE|VIEW|SEQUENCE"))
+		COLLAPSE(2, 1);
 
-/* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
-			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
-		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
-							"CYCLE", "OWNED BY", "START WITH");
-	if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
-		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
+	/* CREATE SEQUENCE */
+	if (Matches2("CREATE", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
+	if (Matches3("CREATE", "SEQUENCE", MatchAny))
+		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO",
+							"CACHE", "CYCLE", "OWNED BY", "START WITH");
+	if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
 	if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
-/* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
-		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
+/* CREATE SCHEMA <name> */
+	if (Matches2("CREATE", "SCHEMA"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+
+/* CREATE TABLE  */
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	if (TailMatches2("CREATE", "UNLOGGED"))
+	if (Matches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
+	/* Remove UNLOGGED for further completion */
+	if (HeadMatches2("CREATE", "UNLOGGED"))
+		COLLAPSE(2, 1);
+	/* Complete CREATE TABLE with existing table names */
+	if (Matches2("CREATE", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
+
 /* CREATE TABLESPACE */
 	if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
@@ -1924,62 +1957,136 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
-/* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE TRIGGER */
+	if (HeadMatches3("CREATE", "CONSTRAINT", "TRIGGER"))
+		COLLAPSE(2, 1);
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	if (Matches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
-	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
-		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
-	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
-		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
-	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
-	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
-		COMPLETE_WITH_LIST2("ON", "OR");
+	{
+		bool instead_of = false;
+
+		/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
+		if (Matches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+			COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
+		/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
+		if (Matches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+			COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
+
+		/* Repeatedly remove OR <event> */
+		while (HeadMatches7("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER",
+							MatchAny, "OR", "INSERT|DELETE|UPDATE|TRUNCATE"))
+			COLLAPSE(6, 2);
+		while (HeadMatches8("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF",
+							MatchAny, "OR", "INSERT|DELETE|UPDATE|TRUNCATE"))
+			COLLAPSE(7, 2);
+
+		/* Remove BEFORE|AFTER|INSTEAD OF <event> */
+		if (HeadMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER",
+						 MatchAny))
+			COLLAPSE(4, 2);
+		if (HeadMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF",
+						 MatchAny))
+		{
+			COLLAPSE(4, 3);
+			instead_of = true;
+		}
+		/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
+		if (Matches3("CREATE", "TRIGGER", MatchAny))
+			COMPLETE_WITH_LIST2("ON", "OR");
+		if (Matches4("CREATE", "TRIGGER", MatchAny, "OR"))
+		{
+			if (instead_of)
+				COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
+			else
+				COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
+		}
 
-	/*
-	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
-	 * tables
-	 */
-	if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
-	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
-	if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
-	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("ON", MatchAny))
-		COMPLETE_WITH_LIST7("NOT DEFERRABLE", "DEFERRABLE", "INITIALLY",
-							"REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches1("DEFERRABLE") || TailMatches2("INITIALLY", "IMMEDIATE|DEFERRED")))
-		COMPLETE_WITH_LIST4("REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("REFERENCING"))
-		COMPLETE_WITH_LIST2("OLD TABLE", "NEW TABLE");
-	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("OLD|NEW", "TABLE"))
-		COMPLETE_WITH_CONST("AS");
-	if (HeadMatches2("CREATE", "TRIGGER") &&
-		(TailMatches5("REFERENCING", "OLD", "TABLE", "AS", MatchAny) ||
-		 TailMatches4("REFERENCING", "OLD", "TABLE", MatchAny)))
-		COMPLETE_WITH_LIST4("NEW TABLE", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	if (HeadMatches2("CREATE", "TRIGGER") &&
-		(TailMatches5("REFERENCING", "NEW", "TABLE", "AS", MatchAny) ||
-		 TailMatches4("REFERENCING", "NEW", "TABLE", MatchAny)))
-		COMPLETE_WITH_LIST4("OLD TABLE", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	if (HeadMatches2("CREATE", "TRIGGER") &&
-		(TailMatches9("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
-		 TailMatches8("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
-		 TailMatches8("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", MatchAny) ||
-		 TailMatches7("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", MatchAny)))
-		COMPLETE_WITH_LIST3("FOR", "WHEN (", "EXECUTE PROCEDURE");
-	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("FOR"))
-		COMPLETE_WITH_LIST3("EACH", "ROW", "STATEMENT");
-	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("FOR", "EACH"))
-		COMPLETE_WITH_LIST2("ROW", "STATEMENT");
-	if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches3("FOR", "EACH", "ROW|STATEMENT") ||
-			  TailMatches2("FOR", "ROW|STATEMENT")))
-		COMPLETE_WITH_LIST2("WHEN (", "EXECUTE PROCEDURE");
+		/*
+		 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
+		 * tables
+		 * complete CREATE TRIGGER ... INSTEAD OF event ON with a list of
+		 * views
+		 */
+		if (Matches4("CREATE", "TRIGGER", MatchAny, "ON"))
+		{
+			if (instead_of)
+				COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
+			else
+				COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
+		}
+
+		/*
+		 * word_list became too long for matching macors, shift to just
+		 * after CREATE TRIGGER name ... ON table_name and go on.
+		 */
+		if (HeadMatches5("CREATE", "TRIGGER", MatchAny, "ON", MatchAny))
+		{
+			HEAD_SHIFT(5);
+
+			if (WORD_COUNT() == 0)
+				COMPLETE_WITH_LIST7("NOT DEFERRABLE", "DEFERRABLE", "INITIALLY",
+									"REFERENCING", "FOR", "WHEN (",
+									"EXECUTE PROCEDURE");
+			if (Matches1("INITIALLY"))
+				COMPLETE_WITH_LIST2("IMMEDIATE", "DEFERRED");
+
+			/* Remove last keyword that doesn't matter in further completion */
+			if (HeadMatches1("DEFERRABLE"))
+				COLLAPSE(1, 1);
+			if (HeadMatches2("INITIALLY", "IMMEDIATE|DEFERRED") ||
+				HeadMatches2("NOT", "DEFERRABLE"))
+				COLLAPSE(1, 2);
+
+			if (WORD_COUNT() == 0)
+				COMPLETE_WITH_LIST4("REFERENCING", "FOR", "WHEN (",
+									"EXECUTE PROCEDURE");
+			if (Matches1("REFERENCING"))
+				COMPLETE_WITH_LIST2("OLD TABLE", "NEW TABLE");
+			if (Matches3("REFERENCING", "OLD|NEW", "TABLE"))
+				COMPLETE_WITH_CONST("AS");
+
+			/*  Remove AS if exists*/
+			if (HeadMatches4("REFERENCING", "OLD|NEW", "TABLE", "AS"))
+				COLLAPSE(4, 1);
+			if (Matches4("REFERENCING", "OLD", "TABLE", MatchAny))
+				COMPLETE_WITH_LIST4("NEW TABLE", "FOR", "WHEN (",
+									"EXECUTE PROCEDURE");
+			if (Matches4("REFERENCING", "NEW", "TABLE", MatchAny))
+				COMPLETE_WITH_LIST4("OLD TABLE", "FOR", "WHEN (",
+									"EXECUTE PROCEDURE");
+			if (Matches6("REFERENCING", "OLD|NEW", "TABLE", MatchAny,
+						 "OLD|NEW", "TABLE"))
+				COMPLETE_WITH_CONST("AS");
+			if (HeadMatches8("REFERENCING", "OLD|NEW", "TABLE", MatchAny,
+							 "OLD|NEW", "TABLE", "AS", MatchAny))
+				COLLAPSE(1, 8);
+			if (HeadMatches7("REFERENCING", "OLD|NEW", "TABLE", MatchAny,
+							 "OLD|NEW", "TABLE", MatchAny))
+				COLLAPSE(1, 7);
+			if (HeadMatches4("REFERENCING", "OLD|NEW", "TABLE", MatchAny))
+				COLLAPSE(1, 4);
+			/* REFERENCING close has vanished here */
+
+			if (WORD_COUNT() == 0)
+				COMPLETE_WITH_LIST3("FOR", "WHEN (", "EXECUTE PROCEDURE");
+			if (Matches1("FOR"))
+				COMPLETE_WITH_LIST3("EACH", "ROW", "STATEMENT");
+			/* Remove EACH */
+			if (HeadMatches2("FOR", "EACH"))
+				COLLAPSE(2, 1);
+			if (Matches1("FOR"))
+				COMPLETE_WITH_LIST2("ROW", "STATEMENT");
+			if (HeadMatches2("FOR", "ROW|STATEMENT"))
+				COLLAPSE(1, 2);
+			/* FOR close has vanished here */
+			if (WORD_COUNT() == 0)
+				COMPLETE_WITH_LIST2("WHEN (", "EXECUTE PROCEDURE");
+
+			/* Continue matching on the whole command line */
+			HEAD_SHIFT(-5);
+		}
+	}
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
 	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
@@ -2026,12 +2133,12 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
-/* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE VIEW */
 	/* Complete CREATE VIEW <name> with AS */
-	if (TailMatches3("CREATE", "VIEW", MatchAny))
+	if (Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
+	if (Matches4("CREATE", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
@@ -2061,15 +2168,15 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
 		COMPLETE_WITH_LIST3("WITH HOLD", "WITHOUT HOLD", "FOR");
 
-/* DELETE --- can be inside EXPLAIN, RULE, etc */
+/* DELETE */
 	/* ... despite which, only complete DELETE with FROM at start of line */
 	if (Matches1("DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
-	if (TailMatches2("DELETE", "FROM"))
+	if (Matches2("DELETE", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete DELETE FROM <table> */
-	if (TailMatches3("DELETE", "FROM", MatchAny))
+	if (Matches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
 	/* XXX: implement tab completion for DELETE ... USING */
 
@@ -2107,16 +2214,17 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
 									  ADDLIST1("CONCURRENTLY"));
-	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
+	if (HeadMatches3("DROP", "INDEX", "CONCURRENTLY"))
+		COLLAPSE(3, 1);
 	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
-		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
 	if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+
+	/* DROP VIEW is suggested as a general thing */
+
 	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 
@@ -2188,13 +2296,23 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"ANALYZE", "VERBOSE");
-	if (Matches2("EXPLAIN", "ANALYZE"))
+	if (HeadMatches2("EXPLAIN", "ANALYZE"))
+		COLLAPSE(2, 1);
+	if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"VERBOSE");
-	if (Matches2("EXPLAIN", "VERBOSE") ||
-			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
+	if (HeadMatches2("EXPLAIN", "VERBOSE"))
+		COLLAPSE(2, 1);
+	if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 
+	/* complete on individual syntaxes here after */
+	if (Matches2("EXPLAIN", "SELECT|INSERT|DELETE|UPDATE|DECLARE"))
+	{
+		COLLAPSE(1, 1);
+		return psql_completion_internal(text, previous_words, WORD_COUNT());
+	}
+
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
 	if (Matches1("FETCH|MOVE"))
@@ -2230,9 +2348,9 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatches2("FOREIGN", "SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
 
-/* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* GRANT && REVOKE */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	if (TailMatches1("GRANT|REVOKE"))
+	if (Matches1("GRANT|REVOKE"))
 		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
 			ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
 					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
@@ -2242,13 +2360,13 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	if (TailMatches2("GRANT|REVOKE", MatchAny))
+	if (Matches2("GRANT|REVOKE", MatchAny))
 	{
 		if (TailMatches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
-		if (TailMatches2("GRANT", MatchAny))
+		if (HeadMatches1("GRANT"))		/* GRANT roles */
 			COMPLETE_WITH_CONST("TO");
-		else
+		else							/* REVOKE roles */
 			COMPLETE_WITH_CONST("FROM");
 	}
 
@@ -2263,7 +2381,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
 	 */
-	if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
+	if (Matches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tsvmf,
 			   ADDLIST15("ALL FUNCTIONS IN SCHEMA",
 						 "ALL SEQUENCES IN SCHEMA",
@@ -2281,11 +2399,11 @@ psql_completion_internal(const char *text, char **previous_words,
 						 "TABLESPACE",
 						 "TYPE"));
 
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
+	if (Matches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
 							"TABLES IN SCHEMA");
 
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+	if (Matches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/*
@@ -2294,27 +2412,11 @@ psql_completion_internal(const char *text, char **previous_words,
 	 *
 	 * Complete "GRANT/REVOKE * ON *" with "TO/FROM".
 	 */
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
+	if (Matches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
-		if (TailMatches1("DATABASE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-		if (TailMatches1("DOMAIN"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains);
-		if (TailMatches1("FUNCTION"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
-		if (TailMatches1("LANGUAGE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-		if (TailMatches1("SCHEMA"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-		if (TailMatches1("SEQUENCE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
-		if (TailMatches1("TABLE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf);
-		if (TailMatches1("TABLESPACE"))
-			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-		if (TailMatches1("TYPE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
-		if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
+		if (TailMatches1("DATABASE|DOMAIN|FUNCTION|LANGUAGE|SCHEMA|SEQUENCE|TABLE|TABLESPACE|TYPE"))
+			COMPLETE_THING(-1);
+		if (HeadMatches1("GRANT"))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2335,27 +2437,13 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("FROM");
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
-	{
-		if (TailMatches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
-			COMPLETE_WITH_CONST("TO");
-		else
-			COMPLETE_WITH_CONST("FROM");
-	}
-
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
-	{
-		if (TailMatches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
-			COMPLETE_WITH_CONST("TO");
-		else
-			COMPLETE_WITH_CONST("FROM");
-	}
-
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
+	if (Matches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny) ||
+		Matches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny) ||
+		Matches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
 	{
-		if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (HeadMatches1("GRANT"))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2371,29 +2459,29 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("IMPORT", "FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
-/* INSERT --- can be inside EXPLAIN, RULE, etc */
+/* INSERT */
 	/* Complete INSERT with "INTO" */
-	if (TailMatches1("INSERT"))
+	if (Matches1("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
-	if (TailMatches2("INSERT", "INTO"))
+	if (Matches2("INSERT", "INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete "INSERT INTO <table> (" with attribute names */
-	if (TailMatches4("INSERT", "INTO", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+	if (Matches4("INSERT", "INTO", MatchAny, "("))
+		COMPLETE_WITH_ATTR(prev2_wd);
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES"
 	 */
-	if (TailMatches3("INSERT", "INTO", MatchAny))
+	if (Matches3("INSERT", "INTO", MatchAny))
 		COMPLETE_WITH_LIST5("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES");
 
 	/*
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE"
 	 */
-	if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
+	if (Matches4("INSERT", "INTO", MatchAny, MatchAny) &&
 			 ends_with(prev_wd, ')'))
 		COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES");
 
@@ -2411,14 +2499,18 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* For the following, handle the case of a single table only for now */
 
+	/* Remove TABLE and ONLY from LOCK */
+	if (HeadMatches3("LOCK", "TABLE", MatchAny))
+		COLLAPSE(2, 1);
+	if (HeadMatches3("LOCK", "ONLY", MatchAny))
+		COLLAPSE(2, 1);
+
 	/* Complete LOCK [TABLE] <table> with "IN" */
-	if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
-			 Matches3("LOCK", "TABLE", MatchAny))
+	if (Matches2("LOCK", MatchAnyExcept("TABLE")))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	if (Matches3("LOCK", MatchAny, "IN") ||
-			 Matches4("LOCK", "TABLE", MatchAny, "IN"))
+	if (Matches3("LOCK", MatchAny, "IN"))
 		COMPLETE_WITH_LIST8("ACCESS SHARE MODE",
 							"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
 							"SHARE UPDATE EXCLUSIVE MODE", "SHARE MODE",
@@ -2426,18 +2518,15 @@ psql_completion_internal(const char *text, char **previous_words,
 							"EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE");
 
 	/* Complete LOCK [TABLE] <table> IN ACCESS|ROW with rest of lock mode */
-	else if (Matches4("LOCK", MatchAny, "IN", "ACCESS|ROW") ||
-			 Matches5("LOCK", "TABLE", MatchAny, "IN", "ACCESS|ROW"))
+	if (Matches4("LOCK", MatchAny, "IN", "ACCESS|ROW"))
 		COMPLETE_WITH_LIST2("EXCLUSIVE MODE", "SHARE MODE");
-
+	
 	/* Complete LOCK [TABLE] <table> IN SHARE with rest of lock mode */
-	else if (Matches4("LOCK", MatchAny, "IN", "SHARE") ||
-			 Matches5("LOCK", "TABLE", MatchAny, "IN", "SHARE"))
+	if (Matches4("LOCK", MatchAny, "IN", "SHARE"))
 		COMPLETE_WITH_LIST3("MODE", "ROW EXCLUSIVE MODE",
 							"UPDATE EXCLUSIVE MODE");
 
-/* NOTIFY --- can be inside EXPLAIN, RULE, etc */
-	if (TailMatches1("NOTIFY"))
+	if (Matches1("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
 
 /* OPTIONS */
@@ -2452,11 +2541,18 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatches3("FROM", MatchAny, "ORDER"))
 		COMPLETE_WITH_CONST("BY");
 	if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 
 /* PREPARE xx AS */
-	if (Matches3("PREPARE", MatchAny, "AS"))
-		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
+	if (HeadMatches1("PREPARE"))
+	{
+		if (Matches3("PREPARE", MatchAny, "AS"))
+			COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
+
+		/* Complete for indivisual command */
+		SHIFT_TO_LAST1("SELECT|UPDATE|INSERT|DELETE");
+		return psql_completion_internal(text, previous_words, WORD_COUNT());
+	}
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -2517,8 +2613,9 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("ON", "FOR");
 	if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	if (Matches3("SECURITY", "LABEL", "ON") ||
-			 Matches5("SECURITY", "LABEL", "FOR", MatchAny, "ON"))
+	if (HeadMatches4("SECURITY", "LABEL", "FOR", MatchAny))
+		COLLAPSE(3, 2);
+	if (Matches3("SECURITY", "LABEL", "ON"))
 	{
 		static const char *const list_SECURITY_LABEL[] =
 		{"TABLE", "COLUMN", "AGGREGATE", "DATABASE", "DOMAIN",
@@ -2546,35 +2643,42 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* Complete "SET TRANSACTION" */
 	if (Matches2("SET", "TRANSACTION"))
 		COMPLETE_WITH_LIST5("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	if (Matches2("BEGIN|START", "TRANSACTION") ||
-		Matches2("BEGIN", "WORK") ||
-		Matches1("BEGIN") ||
-		Matches5("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
-		COMPLETE_WITH_LIST4("ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
-		Matches2("BEGIN", "NOT") ||
-		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
-		COMPLETE_WITH_CONST("DEFERRABLE");
-	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
-		Matches2("BEGIN", "ISOLATION") ||
-		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
-		COMPLETE_WITH_CONST("LEVEL");
-	if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
-		Matches3("BEGIN", "ISOLATION", "LEVEL") ||
-		Matches7("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
-		COMPLETE_WITH_LIST3("READ", "REPEATABLE READ", "SERIALIZABLE");
-	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
-		Matches4("BEGIN", "ISOLATION", "LEVEL", "READ") ||
-		Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
-		COMPLETE_WITH_LIST2("UNCOMMITTED", "COMMITTED");
-	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
-		Matches4("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
-		Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
-		COMPLETE_WITH_CONST("READ");
-	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
-		Matches2("BEGIN", "READ") ||
-		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
-		COMPLETE_WITH_LIST2("ONLY", "WRITE");
+	if (HeadMatches2("BEGIN", "WORK|TRANSACTION"))
+		COLLAPSE(2, 1);
+	{
+		int shift = 0;
+
+		if (HeadMatches2("START", "TRANSACTION"))
+			shift = 2;
+		if (HeadMatches1("BEGIN"))
+			shift = 1;
+		if (HeadMatches5("SET", "SESSION", "CHARACTERISTICS", "AS",
+						 "TRANSACTION"))
+			shift = 5;
+
+		if (shift > 0)
+		{
+			/* complete with transaction mode */
+			HEAD_SHIFT(shift);
+
+			if (WORD_COUNT() == 0)
+				COMPLETE_WITH_LIST4("ISOLATION LEVEL", "READ", "DEFERRABLE",
+									"NOT DEFERRABLE");
+			if (Matches1("NOT"))
+				COMPLETE_WITH_CONST("DEFERRABLE");
+			if (Matches1("ISOLATION"))
+				COMPLETE_WITH_CONST("LEVEL");
+			if (Matches2("ISOLATION", "LEVEL"))
+				COMPLETE_WITH_LIST3("READ", "REPEATABLE READ", "SERIALIZABLE");
+			if (Matches3("ISOLATION", "LEVEL", "REPEATABLE"))
+				COMPLETE_WITH_CONST("READ");
+			if (Matches3("ISOLATION", "LEVEL", "READ"))
+				COMPLETE_WITH_LIST2("UNCOMMITTED", "COMMITTED");
+			if (Matches1("READ"))
+				COMPLETE_WITH_LIST2("ONLY", "WRITE");
+			COMPLETE_WITH_CONST("");
+		}
+	}
 	/* SET CONSTRAINTS */
 	if (Matches2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_constraints_with_schema,
@@ -2671,18 +2775,18 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches1("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
 
-/* UPDATE --- can be inside EXPLAIN, RULE, etc */
+/* UPDATE  */
 	/* If prev. word is UPDATE suggest a list of tables */
-	if (TailMatches1("UPDATE"))
+	if (Matches1("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete UPDATE <table> with "SET" */
-	if (TailMatches2("UPDATE", MatchAny))
+	if (Matches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
 	/* Complete UPDATE <table> SET with list of attributes */
-	if (TailMatches3("UPDATE", MatchAny, "SET"))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+	if (Matches3("UPDATE", MatchAny, "SET"))
+		COMPLETE_WITH_ATTR(prev2_wd);
 	/* UPDATE <table> SET <attr> = */
-	if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
+	if (Matches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
@@ -2741,7 +2845,7 @@ psql_completion_internal(const char *text, char **previous_words,
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
 	if (TailMatches2(MatchAny, "WHERE"))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+		COMPLETE_WITH_ATTR(prev2_wd);
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
@@ -2914,18 +3018,14 @@ psql_completion_internal(const char *text, char **previous_words,
 	 */
 	else
 	{
-		int			i;
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
 
-		for (i = 0; words_after_create[i].name; i++)
+		if (ent)
 		{
-			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
-			{
-				if (words_after_create[i].query)
-					COMPLETE_WITH_QUERY(words_after_create[i].query);
-				if (words_after_create[i].squery)
-					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery);
-				break;
-			}
+			if (ent->query)
+				COMPLETE_WITH_QUERY(ent->query);
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery);
 		}
 	}
 
@@ -3436,6 +3536,18 @@ complete_from_files(const char *text, int state)
 
 /* HELPER FUNCTIONS */
 
+/*
+ * Return the index (reverse to the index of previous_words) of the tailmost
+ * (topmost in the array) appearance of w.
+ */
+static int
+find_last_index_of(char *w, char **previous_words, int len)
+{
+	int i;
+
+	for (i = 0 ; i < len && !word_matches(w, previous_words[i]) ; i++);
+	return i < len ? (len - i - 1) : 0;
+}
 
 /*
  * Make a pg_strdup copy of s and convert the case according to
@@ -3735,6 +3847,24 @@ get_guctype(const char *varname)
 	return guctype;
 }
 
+/*
+ * Finds the entry in words_after_create[] that matches the word.
+ * NULL if not found.
+ */
+static const pgsql_thing_t *
+find_thing_entry(char *word)
+{
+	int			i;
+
+	for (i = 0; words_after_create[i].name; i++)
+	{
+		if (pg_strcasecmp(word, words_after_create[i].name) == 0)
+			return words_after_create + i;
+	}
+
+	return NULL;
+}
+
 #ifdef NOT_USED
 
 /*
-- 
2.9.2

0005-Add-suggestion-for-IF-NOT-EXISTS-for-some-syntaxes.patchtext/x-patch; charset=us-asciiDownload
From ff79622be2d6aa205945e7efb7d835792bbadd5b Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Thu, 15 Sep 2016 20:49:45 +0900
Subject: [PATCH 5/6] Add suggestion for IF (NOT) EXISTS for some syntaxes

Add suggestion for IF EXISTS or IF NOT EXISTS for some arbitrary
syntaxes, ALTER TABLE/ALTER FOREIGN TABLE/ALTER INDEX/
ALTER MATERIALIZED VIEW/ALTER DOMAIN DROP CONSTRAINT/ALTER SEQUENCE/
ALTER VIEW/ALTER POLICY/ALTER TABLE DROP COLUMN/
ALTER TABLE DROP CONSTRAINT/CREATE INDEX/CREATE SEQUENCE/
CREATE TABLE/CREATE VIEW/DROP INDEX/DROP (MATERIALIZED) VIEW/
DROP USER MAPPING
---
 src/bin/psql/tab-complete.c | 178 +++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 168 insertions(+), 10 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ec5a700..cbac5d3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1094,7 +1094,9 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* ALTER TABLE */
 	if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
-									  ADDLIST1("ALL IN TABLESPACE"));
+								   ADDLIST2("ALL IN TABLESPACE", "IF EXISTS"));
+	if (HeadMatches4("ALTER", "TABLE", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
 
 	/* ALTER something */
 	if (Matches1("ALTER"))
@@ -1197,6 +1199,14 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
+	/* ALTER FOREIGN TABLE */
+	if (Matches3("ALTER", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_foreign_tables,
+									  ADDLIST1("IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (HeadMatches5("ALTER", "FOREIGN", "TABLE", "IF", "EXISTS"))
+		COLLAPSE(4, 2);
+
 	/* ALTER FOREIGN TABLE <name> */
 	if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
@@ -1211,7 +1221,11 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* ALTER INDEX */
 	if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
-									  ADDLIST1("ALL IN TABLESPACE"));
+									  ADDLIST2("ALL IN TABLESPACE", "IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (HeadMatches4("ALTER", "INDEX", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+
 	/* ALTER INDEX <name> */
 	if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
@@ -1240,7 +1254,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* ALTER MATERIALIZED VIEW */
 	if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_matviews,
-									  ADDLIST1("ALL IN TABLESPACE"));
+								   ADDLIST2("ALL IN TABLESPACE", "IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (HeadMatches5("ALTER", "MATERIALIZED", "VIEW", "IF", "EXISTS"))
+		COLLAPSE(4, 2);
 
 	/* ALTER USER,ROLE <name> */
 	if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
@@ -1296,6 +1313,19 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
+		if (word_matches("DROP", prev2_wd))
+			COMPLETE_WITH_QUERY_KW(Query_for_constraint_of_type,
+								   ADDLIST1("IF EXISTS"));
+		else
+			COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+	}
+	/* Remove optional words for further completion */
+	if (HeadMatches7("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT", "IF", "EXISTS"))
+		COLLAPSE(6, 2);
+	/*  Complete constraint name again without IF EXISTS */
+	if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
 	}
 	/* ALTER DOMAIN <sth> RENAME */
@@ -1308,6 +1338,16 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* ALTER DOMAIN <sth> SET */
 	if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
+	/* ALTER SEQUENCE */
+	if (Matches2("ALTER", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_sequences,
+									  ADDLIST1("IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (HeadMatches4("ALTER", "SEQUENCE", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches2("ALTER", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
 	/* ALTER SEQUENCE <name> */
 	if (Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
@@ -1333,6 +1373,16 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY_KW(Query_for_list_of_alter_system_set_vars,
 							   ADDLIST1("ALL"));
+	/* ALTER VIEW */
+	if (Matches2("ALTER", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_views,
+									  ADDLIST1("IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (HeadMatches4("ALTER", "VIEW", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches2("ALTER", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	/* ALTER VIEW <name> */
 	if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
@@ -1344,6 +1394,12 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER POLICY <name> */
 	if (Matches2("ALTER", "POLICY"))
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_policies, ADDLIST1("IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (Matches4("ALTER", "POLICY", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches2("ALTER", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* ALTER POLICY <name> ON */
 	if (Matches3("ALTER", "POLICY", MatchAny))
@@ -1483,8 +1539,13 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+		COMPLETE_WITH_ATTR_KW(prev3_wd, ADDLIST1("IF EXISTS"));
+	/* Remove optional words for further completion */
+	if (HeadMatches7("ALTER", "TABLE", MatchAny, "DROP", "COLUMN", "IF", "EXISTS"))
+		COLLAPSE(6, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd);
-
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
@@ -1492,6 +1553,19 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
+		if (word_matches("DROP", prev2_wd))
+			COMPLETE_WITH_QUERY_KW(Query_for_constraint_of_table,
+								ADDLIST1("IF EXISTS"));
+		else
+			COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+	}
+	/* Remove IF EXISTS for further completion */
+	if (HeadMatches7("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT", "IF", "EXISTS"))
+		COLLAPSE(6, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT"))
+	{
+		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
 	}
 	/* Remove COLUMN just after ALTER */
@@ -1633,7 +1707,18 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * of attributes
 	 */
 	if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
-		COMPLETE_WITH_ATTR(prev3_wd);
+	{
+		if (word_matches("DROP", prev2_wd))
+			COMPLETE_WITH_ATTR_KW(prev3_wd, ADDLIST1("IF EXISTS"));
+		else
+			COMPLETE_WITH_ATTR(prev3_wd);
+	}
+	/* Remove IF EXISTS for further completion */
+	if (HeadMatches7("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE", "IF", "EXISTS"))
+		COLLAPSE(6, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE"))
+			COMPLETE_WITH_ATTR(prev3_wd);	
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
 	if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
@@ -1787,6 +1872,13 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	if (Matches2("CREATE", "EXTENSION"))
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_available_extensions,
+							   ADDLIST1("IF NOT EXISTS"));
+	/* Remove IF NOT EXISTS for further completion*/
+	if (HeadMatches5("CREATE", "EXTENSION", "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+	/* Complete again without IF NOT EXISTS */
+	if (Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
 	if (Matches3("CREATE", "EXTENSION", MatchAny))
@@ -1817,14 +1909,20 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* Complete with index names as category suggestion and possible keywords */
 	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
-									  ADDLIST2("ON", "CONCURRENTLY"));
+									  ADDLIST3("ON", "CONCURRENTLY", "IF NOT EXISTS"));
 	/* Remove CONCURRENTLY for further completion */
 	if (HeadMatches3("CREATE", "INDEX", "CONCURRENTLY"))
 		COLLAPSE(3, 1);
 	/* Complete with existing index names as word category suggestion */
 	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
-									  ADDLIST1("ON"));
+									  ADDLIST2("ON", "IF NOT EXISTS"));
+	/* Remove IF NOT EXISTS for further completion */
+	if (HeadMatches5("CREATE", "INDEX", "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+	/* Complete again without IF NOT EXISTS */
+	if (Matches2("CREATE", "INDEX"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	/* Suggest ON just after index name */
 	if (Matches3("CREATE", "INDEX", MatchAnyExcept("ON")))
 		COMPLETE_WITH_CONST("ON");
@@ -1917,6 +2015,13 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* CREATE SEQUENCE */
 	if (Matches2("CREATE", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_sequences,
+									  ADDLIST1("IF NOT EXISTS"));
+	/* Remove IF NOT EXISTS for further completion */
+	if (HeadMatches5("CREATE", "SEQUENCE", "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+	/* Complete again without IF NOT EXISTS */
+	if (Matches2("CREATE", "SEQUENCE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
 	if (Matches3("CREATE", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO",
@@ -1942,6 +2047,13 @@ psql_completion_internal(const char *text, char **previous_words,
 		COLLAPSE(2, 1);
 	/* Complete CREATE TABLE with existing table names */
 	if (Matches2("CREATE", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
+									  ADDLIST1("IF NOT EXISTS"));
+	/* Remove IF NOT EXIST for further completion */
+	if (HeadMatches5("CREATE", "TABLE", "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+	/* Complete again without IF NOT EXISTS */
+	if (Matches2("CREATE", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 /* CREATE TABLESPACE */
@@ -2134,6 +2246,16 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
 /* CREATE VIEW */
+	/* Complete CREATE VIEW with name */
+	if (Matches2("CREATE", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_views,
+									  ADDLIST1("IF NOT EXISTS"));
+	/* Remove IF NOT EXISTS for further completion */
+	if (HeadMatches5("CREATE", "VIEW", "IF", "NOT", "EXISTS"))
+		COLLAPSE(3, 3);
+	/* Complete again without IF NOT EXISTS */
+	if (Matches2("CREATE", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	/* Complete CREATE VIEW <name> with AS */
 	if (Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
@@ -2213,18 +2335,43 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* DROP INDEX */
 	if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
-									  ADDLIST1("CONCURRENTLY"));
+									  ADDLIST2("CONCURRENTLY", "IF EXISTS"));
+		if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
+			COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+										  ADDLIST1("IF EXISTS"));
 	if (HeadMatches3("DROP", "INDEX", "CONCURRENTLY"))
 		COLLAPSE(3, 1);
+	/* Remove IF EXISTS for further completion */
+	if (HeadMatches4("DROP", "INDEX", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches2("DROP", "INDEX"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
+	/* DROP VIEW */
+	if (Matches2("DROP", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_views,
+									  ADDLIST1("IF EXISTS"));
+	/* Remove IF EXISTS for further completion */
+	if (HeadMatches4("DROP", "VIEW", "IF", "EXISTS"))
+		COLLAPSE(3, 2);
+	/* Complet again without IF EXISTS */
+	if (Matches2("DROP", "VIEW"))
+		COMPLETE_THING(-1);
+
 	/* DROP MATERIALIZED VIEW */
 	if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 
-	/* DROP VIEW is suggested as a general thing */
-
+	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_matviews,
+									  ADDLIST1("IF EXISTS"));
+	/* Remove IF EXISTS for further completion */
+	if (HeadMatches5("DROP", "MATERIALIZED", "VIEW", "IF", "EXISTS"))
+		COLLAPSE(4, 2);
+	/* Complet again without IF EXISTS */
 	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 
@@ -2791,6 +2938,17 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* USER MAPPING */
 	if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
+	{
+		if (word_matches("DROP", prev3_wd))
+			COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR");
+		else
+			COMPLETE_WITH_CONST("FOR");
+	}
+	/* Remove IF EXISTS for further completion */
+	if (HeadMatches5("DROP", "USER", "MAPPING", "IF", "EXISTS"))
+		COLLAPSE(4, 2);
+	/* Complete again without IF EXISTS */
+	if (Matches3("DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
 	if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
-- 
2.9.2

0006-Add-README-for-tab-completion.patchtext/x-patch; charset=us-asciiDownload
From 0a2b92f858974a0f2c4ec0b9252d14e70037c092 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Wed, 26 Oct 2016 12:06:33 +0900
Subject: [PATCH 6/6] Add README for tab-completion.

---
 src/bin/psql/README.completion | 165 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 165 insertions(+)
 create mode 100644 src/bin/psql/README.completion

diff --git a/src/bin/psql/README.completion b/src/bin/psql/README.completion
new file mode 100644
index 0000000..65981e4
--- /dev/null
+++ b/src/bin/psql/README.completion
@@ -0,0 +1,165 @@
+Word completion of interactive psql
+===================================
+
+psql supports word completion on interactive input. The core function
+of the feature is psql_completion_internal in tab-complete.c. A bunch
+of macros are provided in order to make it easier to read and maintain
+the completion code. psql_completion is called with reference console
+input stored in char ** previous_words in reverse order but developers
+don't need to be aware of the detail. Most of the operations can be
+described using the provided macros.
+
+Basic structure of the completion code
+--------------------------------------
+
+The main part of the function is just a series of completion
+definitions, where the first match wins. Each definition basically is
+in the following shape.
+
+   if (*matching expression*)
+      *enumeration of words for completion, then return*
+
+The matching expression is examined against previous_words which
+contains the whole command line before the last space. The completion
+code enumerates the expected words. For example, for "CREATE <tab>"
+the word list to be matched is ["CREATE"] and the prefix for
+completion is nothing. For "CREATE INDEX i", the list is ["CREATE",
+"INDEX"] and the prefix for enumeration is "i".
+
+
+Matching expression macros
+--------------------------
+There are four types of matching expression macros.
+
+- MatchesN(word1, word2 .. , wordN)
+
+ true iff the word list is exactly the same as the paremeter.
+
+- HeadMatchesN(word1, word2 .., wordN)
+
+ true iff the first N words in the word list matches the parameter.
+
+- TailMatchesN(word1, word2 .., wordN)
+
+ true iff the last N words in the word list matches the parameter.
+
+- MidMatchesN(pos, word1, word2 .., wordN)
+
+ true iff N successive words starts from pos in the word list matches
+ the parameter. The position is 1-based.
+
+Special matching words
+----------------------
+
+A defined symbol MatchAny matches any word. If you want to match any
+of several words, multiple words concatenated by '|' can be
+used. "CREATE|UPDATE" matches any of "CREATE" and "UPDATE".
+
+
+Enumeration macros
+-----------------
+There are N types of word enumeration macros.
+
+- COMPLETE_WITH_QUERY(query), COMPLETE_WITH_QUERY_KW(query, addon)
+
+  Suggest completion words acquired using the given query. The details
+  of the query is seen in the comment for _complete_from_query(). Word
+  matching is case-sensitive.
+
+  The latter form takes an additional parameter, which should be a
+  fragment of query starts with " UNION " followed by a query string
+  which gives some additional words. For case-insensitive suggestion,
+  this could be as simple as a static query string but for
+  case-sensitive case, where the letter case of suggested words
+  follows input, ADDLISTN() macro can be used.
+
+- COMPLETE_WITH_SCHEMA_QUERY(squery),
+  COMPLETE_WITH_SCHEMA_QUERY_KW(squery, addon)
+
+  Suggest words based on a "schema query", which is a struct that
+  containing parameters. You will see the details in the comment for
+  _complete_from_query(). Word maching is case-sensitive.
+
+  Just same as COMPLETE_WITH_QUERY_KW, the latter form takes a
+  fragment query same to that for COMPLETE_WITH_QUERY_KW.
+
+- COMPLETE_WITH_LIST_CS(list)
+
+  Suggest completion words given as a string array. Word matching is
+  case-sensitive.
+
+- COMPLETE_WITH_LIST_CSN(s1, s2.. ,sN)
+
+  Shortcut for COMPLETE_WITH_LIST_CS.
+
+- COMPLETE_WITH_LIST(list)
+
+  Same as COMPLETE_WITH_LIST_CS except that word matching is
+  case-insensitive and the letter case of suggested words is
+  determined according to COMP_KEYWORD_CASE.
+
+- COMPLETE_WITH_LISTN(s1, s2.. ,sN)
+
+  Shortcut for COMPLETE_WITH_LIST.
+
+- COMPLETE_WITH_CONST(string)
+
+  Same as COMPLETE_WITH_LIST but with just one suggestion.
+
+- COMPLETE_WITH_ATTR(relation, addon)
+
+  Suggest attribute names for the given relation. Word matching is
+  case-sensitve.
+
+- COMPLETE_WITH_FUNCTION_ARG(function)
+
+  Suggest function name for the given SQL function. Word matching is
+  case-sensitve.
+
+- COMPLETE_THING(relpos)
+
+  Suggest any object name designated by the word at relpos from the
+  current word. COMPLETE_THING(-1) for "... TABLE " enumerates names
+  of all available tables.
+
+
+Additional keywords for COMPLETE_WITH(_SCHEMA)_QUERY
+----------------------------------------------------
+
+Some syntaxes need suggestion by mixture of object names and
+keywords. Object names are enumarated with
+COMPLETE_WITH(_SCHEMA)_QUERY but keywords should be added manually
+onto them. COMPLETE_WITH(_SCHEMA)_QUERY_KW takes a fragment query that
+gives keywords to be suggested with the object names. Since the
+framgent queriy is just appended to the main query so it is in the
+form of ' UNION <any sql>' that adds arbitrary number of tuples
+contain a text value. For example,
+
+" UNION ALL SELECT 'CURRENT_USER' UNION ALL SELECT 'PUBLIC'"
+
+If you want the case of the keywords to follow input, ADDLISTN() macro
+provides a fragment query containing such keywords.
+
+ADDLIST3("CURRENT_USER", "PUBLIC", "USER") for input 'c' returns a
+fragment query equivalent to the following,
+
+" UNION ALL SELECT 'current_user' UNION ALL SELECT 'public'"
+
+
+Removing "NOISE" words
+----------------------------------------------------
+
+Many syntaxes has "NOISE" words, that is, words that has no effect on
+the following completion. For example, the existence of the second
+word in "CREATE UNIQUE INDEX" makes no difference for further
+completion behavior. Removing such words makes further completion code
+simpler.
+
+COLLAPSE(s, l) removes l words starts from s from previous_words.
+
+if (Matches("CREATE", "UNIQUE", "INDEX"))
+   COLLAPSE(2, 1);
+
+After the above code, the previous_words is ["CREATE", "INDEX"] so the
+following completion definitions need not care about the removed words.
+
-- 
2.9.2

#53Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#52)
Re: IF (NOT) EXISTS in psql-completion

Hi

2016-11-15 12:26 GMT+01:00 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.
jp>:

Hello, I rebased this patch on the current master.

At Mon, 31 Oct 2016 10:15:48 +0900 (Tokyo Standard Time), Kyotaro
HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <
20161031.101548.162143279.horiguchi.kyotaro@lab.ntt.co.jp>

Anyway, I fixed space issues and addressed the
COMPLETE_WITH_QUERY()'s almost unused parameter problem. And
tried to write a README files.

tab-complete.c have gotten some improvements after this time.

577f0bdd2b8904cbdfde6c98f4bda6fd93a05ffc psql: Tab completion for
renaming enum values.
927d7bb6b120a2ca09a164898f887eb850b7a329 Improve tab completion for
CREATE TRIGGER.
1d15d0db50a5f39ab69c1fe60f2d5dcc7e2ddb9c psql: Tab-complete LOCK [TABLE]
... IN {ACCESS|ROW|SHARE}.

The attached patchset is rebsaed on the master including these
patches.

I checked patches 0001, 0002, 0003 patches

There are no any problems with patching, compiling, it is working as
expected

These patches can be committed separately - they are long, but the code is
almost mechanical

The README is perfect

Regards

Pavel

Show quoted text

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#54Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Pavel Stehule (#53)
Re: IF (NOT) EXISTS in psql-completion

Hello,

Thank you for looking this long-and-bothersome patch.

At Wed, 23 Nov 2016 07:12:00 +0100, Pavel Stehule <pavel.stehule@gmail.com> wrote in <CAFj8pRBxgUrg-6CKbVOy4VqwSFkrf--uCzj3q-vd9FgpSGV+qQ@mail.gmail.com>

Hi

2016-11-15 12:26 GMT+01:00 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.
jp>:

Hello, I rebased this patch on the current master.

At Mon, 31 Oct 2016 10:15:48 +0900 (Tokyo Standard Time), Kyotaro
HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <
20161031.101548.162143279.horiguchi.kyotaro@lab.ntt.co.jp>

Anyway, I fixed space issues and addressed the
COMPLETE_WITH_QUERY()'s almost unused parameter problem. And
tried to write a README files.

tab-complete.c have gotten some improvements after this time.

577f0bdd2b8904cbdfde6c98f4bda6fd93a05ffc psql: Tab completion for
renaming enum values.
927d7bb6b120a2ca09a164898f887eb850b7a329 Improve tab completion for
CREATE TRIGGER.
1d15d0db50a5f39ab69c1fe60f2d5dcc7e2ddb9c psql: Tab-complete LOCK [TABLE]
... IN {ACCESS|ROW|SHARE}.

The attached patchset is rebsaed on the master including these
patches.

I checked patches 0001, 0002, 0003 patches

There are no any problems with patching, compiling, it is working as
expected

These patches can be committed separately - they are long, but the code is
almost mechanical

Thanks.

You're right. I haven't consider about relations among them.

0001 (if-else refactoring) does not anyting functionally. It is
required by 0004(word-shift-and-removal) and 0005(if-not-exists).

0002 (keywords case improvement) is almost independent from all
other patches in this patch set. And it brings an obvious
improvement.

0003 (addition to 0002) is move embedded keywords out of defined
queries. Functionally can be united to 0002 but separated for
understandability

0004 (word-shift-and-removal) is quite arguable one. This
introduces an ability to modify (or destroy) previous_words
array. This reduces almost redundant matching predicates such as,

if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))

into

if (Matches3("CREATE", "INDEX", MatchAnyExcept("ON")))

by removing "CONCURRENTLY". This obviously simplifies the
predicates literally but it the code implies history of
modification. The implied history might be worse than the
previous shape, especially for the simple cases like this. For a
complex case of CREATE TRIGGER, it seems worse than the original
shape... I'll consider this a bit more. Maybe match-and-collapse
template should be written a predicate.

0005 (if-not-exists). I have admit that this is arguable
feature...

0006 is the terder point:( but.

The README is perfect

Thank you, I'm relieved by hearing that.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#55Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#54)
Re: IF (NOT) EXISTS in psql-completion

2016-11-25 2:24 GMT+01:00 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp

:

Hello,

Thank you for looking this long-and-bothersome patch.

At Wed, 23 Nov 2016 07:12:00 +0100, Pavel Stehule <pavel.stehule@gmail.com>
wrote in <CAFj8pRBxgUrg-6CKbVOy4VqwSFkrf--uCzj3q-
vd9FgpSGV+qQ@mail.gmail.com>

Hi

2016-11-15 12:26 GMT+01:00 Kyotaro HORIGUCHI <

horiguchi.kyotaro@lab.ntt.co.

jp>:

Hello, I rebased this patch on the current master.

At Mon, 31 Oct 2016 10:15:48 +0900 (Tokyo Standard Time), Kyotaro
HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <
20161031.101548.162143279.horiguchi.kyotaro@lab.ntt.co.jp>

Anyway, I fixed space issues and addressed the
COMPLETE_WITH_QUERY()'s almost unused parameter problem. And
tried to write a README files.

tab-complete.c have gotten some improvements after this time.

577f0bdd2b8904cbdfde6c98f4bda6fd93a05ffc psql: Tab completion for
renaming enum values.
927d7bb6b120a2ca09a164898f887eb850b7a329 Improve tab completion for
CREATE TRIGGER.
1d15d0db50a5f39ab69c1fe60f2d5dcc7e2ddb9c psql: Tab-complete LOCK

[TABLE]

... IN {ACCESS|ROW|SHARE}.

The attached patchset is rebsaed on the master including these
patches.

I checked patches 0001, 0002, 0003 patches

There are no any problems with patching, compiling, it is working as
expected

These patches can be committed separately - they are long, but the code

is

almost mechanical

Thanks.

You're right. I haven't consider about relations among them.

I am sure about benefit of all patches - but it is lot of changes in one
moment, and it is not necessary in this moment.

patches 0004 and 0005 does some bigger mental changes, and the work can be
separated.

Regards

Pavel

Show quoted text

0001 (if-else refactoring) does not anyting functionally. It is
required by 0004(word-shift-and-removal) and 0005(if-not-exists).

0002 (keywords case improvement) is almost independent from all
other patches in this patch set. And it brings an obvious
improvement.

0003 (addition to 0002) is move embedded keywords out of defined
queries. Functionally can be united to 0002 but separated for
understandability

0004 (word-shift-and-removal) is quite arguable one. This
introduces an ability to modify (or destroy) previous_words
array. This reduces almost redundant matching predicates such as,

if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))

into

if (Matches3("CREATE", "INDEX", MatchAnyExcept("ON")))

by removing "CONCURRENTLY". This obviously simplifies the
predicates literally but it the code implies history of
modification. The implied history might be worse than the
previous shape, especially for the simple cases like this. For a
complex case of CREATE TRIGGER, it seems worse than the original
shape... I'll consider this a bit more. Maybe match-and-collapse
template should be written a predicate.

0005 (if-not-exists). I have admit that this is arguable
feature...

0006 is the terder point:( but.

The README is perfect

Thank you, I'm relieved by hearing that.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#56Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Pavel Stehule (#55)
Re: IF (NOT) EXISTS in psql-completion

Hello,

At Fri, 25 Nov 2016 06:51:43 +0100, Pavel Stehule <pavel.stehule@gmail.com> wrote in <CAFj8pRAm2CsafiH0CPxoWyTccJSm+y=TVgzq07gGS5ydS0qwCA@mail.gmail.com>

I am sure about benefit of all patches - but it is lot of changes in one
moment, and it is not necessary in this moment.

patches 0004 and 0005 does some bigger mental changes, and the work can be
separated.

The patches are collestions of sporadic changes on the same
basis. I agree that the result is too big too look at. (And the
code itself is confused) Please wait for a while for separated
patches.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#57Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#56)
12 attachment(s)
Re: IF (NOT) EXISTS in psql-completion

Thanks for reviewing but I ran out of time for this CF..

I'm going to move this to the next CF.

At Fri, 25 Nov 2016 15:14:27 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <20161125.151427.53669441.horiguchi.kyotaro@lab.ntt.co.jp>

Hello,

At Fri, 25 Nov 2016 06:51:43 +0100, Pavel Stehule <pavel.stehule@gmail.com> wrote in <CAFj8pRAm2CsafiH0CPxoWyTccJSm+y=TVgzq07gGS5ydS0qwCA@mail.gmail.com>

I am sure about benefit of all patches - but it is lot of changes in one
moment, and it is not necessary in this moment.

patches 0004 and 0005 does some bigger mental changes, and the work can be
separated.

The patches are collestions of sporadic changes on the same
basis. I agree that the result is too big too look at. (And the
code itself is confused) Please wait for a while for separated
patches.

I'm working on it but ran out of time. I'm going to be unplugged
until next Monday. So I move this to the next CF.

The attached pathes are an incomplete work of the
separation. Still contains some confusions among patches but far
easier to look at, I think.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

0001-Refactoring-tab-complete-to-make-psql_completion-cod.patchtext/x-patch; charset=us-asciiDownload
From f4d9f2e8a7675da46919148f028998ae46b1ffd6 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Wed, 14 Sep 2016 11:59:04 +0900
Subject: [PATCH 01/12] Refactoring tab-complete to make psql_completion code
 simpler.

psql_completion consists of a long-long sequence of "else-if"s and
succeeding finishing code. This structure enforces us to use a very
tricky way to put something other than matching-completion pairs in
the midst of the sequence. This patch separates the
matching-completion pairs into individual function
"psql_completion_internal". Returning at the points of completion let
us free from the else-if sequence and let us put anything we want into
the matching-completion sequence.

Addition to that the amount of the code seems to be a good reason to
move it out of tab-complete.c. This patch searates it out as
tab-complete-macros.h
---
 src/bin/psql/Makefile              |    2 +
 src/bin/psql/tab-complete-macros.h |  430 +++++++++++
 src/bin/psql/tab-complete.c        | 1378 ++++++++++++++----------------------
 3 files changed, 960 insertions(+), 850 deletions(-)
 create mode 100644 src/bin/psql/tab-complete-macros.h

diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index 1f6a289..51f88ba 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -47,6 +47,8 @@ ifeq ($(GCC),yes)
 psqlscanslash.o: CFLAGS += -Wno-error
 endif
 
+tab-complete.o: tab-complete-macros.h
+
 distprep: sql_help.h psqlscanslash.c
 
 install: all installdirs
diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
new file mode 100644
index 0000000..97ffcd1
--- /dev/null
+++ b/src/bin/psql/tab-complete-macros.h
@@ -0,0 +1,430 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2016, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/tab-complete-macros.h
+ */
+#ifndef TAB_COMPLETE_MACROS_H
+#define TAB_COMPLETE_MACROS_H
+
+/*
+ * For compactness, we use these macros to reference previous_words[].
+ * Caution: do not access a previous_words[] entry without having checked
+ * previous_words_count to be sure it's valid.  In most cases below, that
+ * check is implicit in a TailMatches() or similar macro, but in some places
+ * we have to check it explicitly.
+ */
+#define prev_wd   (previous_words[0])
+#define prev2_wd  (previous_words[1])
+#define prev3_wd  (previous_words[2])
+#define prev4_wd  (previous_words[3])
+#define prev5_wd  (previous_words[4])
+#define prev6_wd  (previous_words[5])
+#define prev7_wd  (previous_words[6])
+#define prev8_wd  (previous_words[7])
+#define prev9_wd  (previous_words[8])
+
+/*
+ * Return the index in previous_words for index from the beginning. n is
+ * 1-based and the result is 0-based.
+ */
+#define HEAD_INDEX(n) \
+	(previous_words_count - (n))
+
+/*
+ * Macros for matching the last N words before point, and after head_sift,
+ * case-insensitively.
+ */
+#define TailMatches1(p1) \
+	(previous_words_count >= 1 && \
+	 word_matches(p1, prev_wd))
+
+#define TailMatches2(p2, p1) \
+	(previous_words_count >= 2 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd))
+
+#define TailMatches3(p3, p2, p1) \
+	(previous_words_count >= 3 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd))
+
+#define TailMatches4(p4, p3, p2, p1) \
+	(previous_words_count >= 4 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd))
+
+#define TailMatches5(p5, p4, p3, p2, p1) \
+	(previous_words_count >= 5 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd))
+
+#define TailMatches6(p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 6 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd))
+
+#define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 7 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd))
+
+#define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 8 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd) && \
+	 word_matches(p8, prev8_wd))
+
+#define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 9 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd) && \
+	 word_matches(p8, prev8_wd) && \
+	 word_matches(p9, prev9_wd))
+
+	/*
+	 * Macros for matching the last N words before point, and after
+	 * head_shift, case-sensitively.
+	 */
+#define TailMatchesCS1(p1) \
+	(previous_words_count >= 1 && \
+	 word_matches_cs(p1, prev_wd))
+#define TailMatchesCS2(p2, p1) \
+	(previous_words_count >= 2 && \
+	 word_matches_cs(p1, prev_wd) && \
+	 word_matches_cs(p2, prev2_wd))
+
+	/*
+	 * Macros for matching N words exactly to the line, and after head_shift,
+	 * case-insensitively.
+	 */
+#define Matches1(p1) \
+	(previous_words_count == 1 && \
+	 TailMatches1(p1))
+#define Matches2(p1, p2) \
+	(previous_words_count == 2 && \
+	 TailMatches2(p1, p2))
+#define Matches3(p1, p2, p3) \
+	(previous_words_count == 3 && \
+	 TailMatches3(p1, p2, p3))
+#define Matches4(p1, p2, p3, p4) \
+	(previous_words_count == 4 && \
+	 TailMatches4(p1, p2, p3, p4))
+#define Matches5(p1, p2, p3, p4, p5) \
+	(previous_words_count == 5 && \
+	 TailMatches5(p1, p2, p3, p4, p5))
+#define Matches6(p1, p2, p3, p4, p5, p6) \
+	(previous_words_count == 6 && \
+	 TailMatches6(p1, p2, p3, p4, p5, p6))
+#define Matches7(p1, p2, p3, p4, p5, p6, p7) \
+	(previous_words_count == 7 && \
+	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
+#define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
+	(previous_words_count == 8 && \
+	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
+#define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
+	(previous_words_count == 9 && \
+	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
+
+/*
+ * Macros for matching N words after head_shift + sth in the line, regardless
+ * of what is after them, case-insensitively.
+ */
+#define MidMatches1(s, p1) \
+	(HEAD_INDEX(s) >=0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]))
+
+#define MidMatches2(s, p1, p2) \
+	(HEAD_INDEX((s) + 1) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]))
+
+#define MidMatches3(s, p1, p2, p3) \
+	(HEAD_INDEX((s) + 2) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]))
+
+#define MidMatches4(s, p1, p2, p3, p4) \
+	(HEAD_INDEX((s) + 3) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]))
+
+#define MidMatches5(s, p1, p2, p3, p4, p5) \
+	(HEAD_INDEX((s) + 4) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&	\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&		\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]))
+
+#define MidMatches6(s,p1, p2, p3, p4, p5, p6)		\
+	(HEAD_INDEX((s) + 5) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]))
+
+#define MidMatches7(s,p1, p2, p3, p4, p5, p6, p7)	\
+	(HEAD_INDEX((s) + 6) >= 0 &&							\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&			\
+	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]))
+
+#define HeadMatches1(p1) \
+	MidMatches1(1, p1)
+#define HeadMatches2(p1, p2) \
+	MidMatches2(1, p1, p2)
+#define HeadMatches3(p1, p2, p3) \
+	MidMatches3(1, p1, p2, p3)
+#define HeadMatches4(p1, p2, p3, p4) \
+	MidMatches4(1, p1, p2, p3, p4)
+#define HeadMatches5(p1, p2, p3, p4, p5) \
+	MidMatches5(1, p1, p2, p3, p4, p5)
+#define HeadMatches6(p1, p2, p3, p4, p5, p6) \
+	MidMatches6(1, p1, p2, p3, p4, p5, p6)
+#define HeadMatches7(p1, p2, p3, p4, p5, p6, p7) \
+	MidMatches7(1, p1, p2, p3, p4, p5, p6, p7)
+
+/*
+ * A few macros to ease typing. You can use these to complete the given
+ * string with
+ * 1) The results from a query you pass it. (Perhaps one of those below?)
+ * 2) The results from a schema query you pass it.
+ * 3) The items from a null-pointer-terminated list (with or without
+ *	  case-sensitive comparison; see also COMPLETE_WITH_LISTn, below).
+ * 4) A string constant.
+ * 5) The list of attributes of the given table (possibly schema-qualified).
+ * 6) The list of arguments to the given function (possibly schema-qualified).
+ */
+#define COMPLETE_WITH_QUERY(query) \
+do { \
+	completion_charp = query;	\
+	return completion_matches(text, complete_from_query);	\
+} while (0)
+
+#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
+do { \
+	completion_squery = &(query); \
+	completion_charp = addon; \
+	return completion_matches(text, complete_from_schema_query); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS(list) \
+do { \
+	completion_charpp = list; \
+	completion_case_sensitive = true; \
+	return completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST(list) \
+do { \
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	return completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_CONST(string) \
+do { \
+	completion_charp = string;	\
+	completion_case_sensitive = false; \
+	return completion_matches(text, complete_from_const);	\
+} while (0)
+
+#define COMPLETE_WITH_ATTR(relation, addon) \
+do { \
+	char   *_completion_schema; \
+	char   *_completion_table; \
+\
+	_completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \
+								 false, false, pset.encoding); \
+	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+				   false, false, pset.encoding); \
+	_completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+								false, false, pset.encoding); \
+	if (_completion_table == NULL) \
+	{ \
+		completion_charp = Query_for_list_of_attributes addon; \
+		completion_info_charp = relation; \
+	} \
+	else \
+	{ \
+		completion_charp = Query_for_list_of_attributes_with_schema addon; \
+		completion_info_charp = _completion_table; \
+		completion_info_charp2 = _completion_schema; \
+	} \
+	return completion_matches(text, complete_from_query); \
+} while (0)
+
+#define COMPLETE_WITH_ENUM_VALUE(type) \
+do { \
+	char   *_completion_schema; \
+	char   *_completion_type; \
+\
+	_completion_schema = strtokx(type, " \t\n\r", ".", "\"", 0, \
+								 false, false, pset.encoding); \
+	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+				   false, false, pset.encoding); \
+	_completion_type = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+							   false, false, pset.encoding);  \
+	if (_completion_type == NULL)\
+	{ \
+		completion_charp = Query_for_list_of_enum_values;	\
+		completion_info_charp = type; \
+	} \
+	else \
+	{ \
+		completion_charp = Query_for_list_of_enum_values_with_schema;	\
+		completion_info_charp = _completion_type; \
+		completion_info_charp2 = _completion_schema; \
+	} \
+	return completion_matches(text, complete_from_query);	\
+} while (0)
+
+#define COMPLETE_WITH_FUNCTION_ARG(function) \
+do { \
+	char   *_completion_schema; \
+	char   *_completion_function; \
+\
+	_completion_schema = strtokx(function, " \t\n\r", ".", "\"", 0, \
+								 false, false, pset.encoding); \
+	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+				   false, false, pset.encoding); \
+	_completion_function = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+								   false, false, pset.encoding); \
+	if (_completion_function == NULL) \
+	{ \
+		completion_charp = Query_for_list_of_arguments; \
+		completion_info_charp = function; \
+	} \
+	else \
+	{ \
+		completion_charp = Query_for_list_of_arguments_with_schema;	\
+		completion_info_charp = _completion_function; \
+		completion_info_charp2 = _completion_schema; \
+	} \
+	return completion_matches(text, complete_from_query); \
+} while (0)
+
+/*
+ * These macros simplify use of COMPLETE_WITH_LIST for short, fixed lists.
+ * There is no COMPLETE_WITH_LIST1; use COMPLETE_WITH_CONST for that case.
+ */
+#define COMPLETE_WITH_LIST2(s1, s2) \
+do { \
+	static const char *const list[] = { s1, s2, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST3(s1, s2, s3) \
+do { \
+	static const char *const list[] = { s1, s2, s3, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST4(s1, s2, s3, s4) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST5(s1, s2, s3, s4, s5) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST6(s1, s2, s3, s4, s5, s6) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST7(s1, s2, s3, s4, s5, s6, s7) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST8(s1, s2, s3, s4, s5, s6, s7, s8) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST9(s1, s2, s3, s4, s5, s6, s7, s8, s9) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST10(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+/*
+ * Likewise for COMPLETE_WITH_LIST_CS.
+ */
+#define COMPLETE_WITH_LIST_CS2(s1, s2) \
+do { \
+	static const char *const list[] = { s1, s2, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS3(s1, s2, s3) \
+do { \
+	static const char *const list[] = { s1, s2, s3, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS4(s1, s2, s3, s4) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS5(s1, s2, s3, s4, s5) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+
+
+#endif   /* TAB_COMPLETE_MACROS_H */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b556c00..24669d0 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -34,6 +34,7 @@
 
 #include "postgres_fe.h"
 #include "tab-complete.h"
+#include "tab-complete-macros.h"
 #include "input.h"
 
 /* If we don't have this, we might as well forget about the whole thing: */
@@ -133,211 +134,6 @@ static const SchemaQuery *completion_squery;	/* to pass a SchemaQuery */
 static bool completion_case_sensitive;	/* completion is case sensitive */
 
 /*
- * A few macros to ease typing. You can use these to complete the given
- * string with
- * 1) The results from a query you pass it. (Perhaps one of those below?)
- * 2) The results from a schema query you pass it.
- * 3) The items from a null-pointer-terminated list (with or without
- *	  case-sensitive comparison; see also COMPLETE_WITH_LISTn, below).
- * 4) A string constant.
- * 5) The list of attributes of the given table (possibly schema-qualified).
- * 6/ The list of arguments to the given function (possibly schema-qualified).
- */
-#define COMPLETE_WITH_QUERY(query) \
-do { \
-	completion_charp = query; \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
-do { \
-	completion_squery = &(query); \
-	completion_charp = addon; \
-	matches = completion_matches(text, complete_from_schema_query); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS(list) \
-do { \
-	completion_charpp = list; \
-	completion_case_sensitive = true; \
-	matches = completion_matches(text, complete_from_list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST(list) \
-do { \
-	completion_charpp = list; \
-	completion_case_sensitive = false; \
-	matches = completion_matches(text, complete_from_list); \
-} while (0)
-
-#define COMPLETE_WITH_CONST(string) \
-do { \
-	completion_charp = string; \
-	completion_case_sensitive = false; \
-	matches = completion_matches(text, complete_from_const); \
-} while (0)
-
-#define COMPLETE_WITH_ATTR(relation, addon) \
-do { \
-	char   *_completion_schema; \
-	char   *_completion_table; \
-\
-	_completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-								false, false, pset.encoding); \
-	if (_completion_table == NULL) \
-	{ \
-		completion_charp = Query_for_list_of_attributes  addon; \
-		completion_info_charp = relation; \
-	} \
-	else \
-	{ \
-		completion_charp = Query_for_list_of_attributes_with_schema  addon; \
-		completion_info_charp = _completion_table; \
-		completion_info_charp2 = _completion_schema; \
-	} \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-#define COMPLETE_WITH_ENUM_VALUE(type) \
-do { \
-	char   *_completion_schema; \
-	char   *_completion_type; \
-\
-	_completion_schema = strtokx(type, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_type = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-							   false, false, pset.encoding);  \
-	if (_completion_type == NULL)\
-	{ \
-		completion_charp = Query_for_list_of_enum_values; \
-		completion_info_charp = type; \
-	} \
-	else \
-	{ \
-		completion_charp = Query_for_list_of_enum_values_with_schema; \
-		completion_info_charp = _completion_type; \
-		completion_info_charp2 = _completion_schema; \
-	} \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-#define COMPLETE_WITH_FUNCTION_ARG(function) \
-do { \
-	char   *_completion_schema; \
-	char   *_completion_function; \
-\
-	_completion_schema = strtokx(function, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_function = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-								   false, false, pset.encoding); \
-	if (_completion_function == NULL) \
-	{ \
-		completion_charp = Query_for_list_of_arguments; \
-		completion_info_charp = function; \
-	} \
-	else \
-	{ \
-		completion_charp = Query_for_list_of_arguments_with_schema; \
-		completion_info_charp = _completion_function; \
-		completion_info_charp2 = _completion_schema; \
-	} \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-/*
- * These macros simplify use of COMPLETE_WITH_LIST for short, fixed lists.
- * There is no COMPLETE_WITH_LIST1; use COMPLETE_WITH_CONST for that case.
- */
-#define COMPLETE_WITH_LIST2(s1, s2) \
-do { \
-	static const char *const list[] = { s1, s2, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST3(s1, s2, s3) \
-do { \
-	static const char *const list[] = { s1, s2, s3, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST4(s1, s2, s3, s4) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST5(s1, s2, s3, s4, s5) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST6(s1, s2, s3, s4, s5, s6) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST7(s1, s2, s3, s4, s5, s6, s7) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST8(s1, s2, s3, s4, s5, s6, s7, s8) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST9(s1, s2, s3, s4, s5, s6, s7, s8, s9) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST10(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-/*
- * Likewise for COMPLETE_WITH_LIST_CS.
- */
-#define COMPLETE_WITH_LIST_CS2(s1, s2) \
-do { \
-	static const char *const list[] = { s1, s2, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS3(s1, s2, s3) \
-do { \
-	static const char *const list[] = { s1, s2, s3, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS4(s1, s2, s3, s4) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS5(s1, s2, s3, s4, s5) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-/*
  * Assembly instructions for schema queries
  */
 
@@ -1007,6 +803,8 @@ static char **get_previous_words(int point, char **buffer, int *nwords);
 
 static char *get_guctype(const char *varname);
 
+static char **psql_completion_internal(const char *text, char **previous_words,
+										   int previous_words_count);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -1162,156 +960,58 @@ psql_completion(const char *text, int start, int end)
 	/* The number of words found on the input line. */
 	int			previous_words_count;
 
-	/*
-	 * For compactness, we use these macros to reference previous_words[].
-	 * Caution: do not access a previous_words[] entry without having checked
-	 * previous_words_count to be sure it's valid.  In most cases below, that
-	 * check is implicit in a TailMatches() or similar macro, but in some
-	 * places we have to check it explicitly.
-	 */
-#define prev_wd   (previous_words[0])
-#define prev2_wd  (previous_words[1])
-#define prev3_wd  (previous_words[2])
-#define prev4_wd  (previous_words[3])
-#define prev5_wd  (previous_words[4])
-#define prev6_wd  (previous_words[5])
-#define prev7_wd  (previous_words[6])
-#define prev8_wd  (previous_words[7])
-#define prev9_wd  (previous_words[8])
-
-	/* Macros for matching the last N words before point, case-insensitively. */
-#define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, prev_wd))
-
-#define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd))
-
-#define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd))
-
-#define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd))
-
-#define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd))
-
-#define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd))
-
-#define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd))
-
-#define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd) && \
-	 word_matches(p8, prev8_wd))
-
-#define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd) && \
-	 word_matches(p8, prev8_wd) && \
-	 word_matches(p9, prev9_wd))
-
-	/* Macros for matching the last N words before point, case-sensitively. */
-#define TailMatchesCS1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches_cs(p1, prev_wd))
-#define TailMatchesCS2(p2, p1) \
-	(previous_words_count >= 2 && \
-	 word_matches_cs(p1, prev_wd) && \
-	 word_matches_cs(p2, prev2_wd))
+	(void) end;					/* "end" is not used */
 
-	/*
-	 * Macros for matching N words beginning at the start of the line,
-	 * case-insensitively.
-	 */
-#define Matches1(p1) \
-	(previous_words_count == 1 && \
-	 TailMatches1(p1))
-#define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
-	 TailMatches2(p1, p2))
-#define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
-	 TailMatches3(p1, p2, p3))
-#define Matches4(p1, p2, p3, p4) \
-	(previous_words_count == 4 && \
-	 TailMatches4(p1, p2, p3, p4))
-#define Matches5(p1, p2, p3, p4, p5) \
-	(previous_words_count == 5 && \
-	 TailMatches5(p1, p2, p3, p4, p5))
-#define Matches6(p1, p2, p3, p4, p5, p6) \
-	(previous_words_count == 6 && \
-	 TailMatches6(p1, p2, p3, p4, p5, p6))
-#define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(previous_words_count == 7 && \
-	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
-#define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(previous_words_count == 8 && \
-	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
-#define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(previous_words_count == 9 && \
-	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
+#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
+	rl_completion_append_character = ' ';
+#endif
+
+	/* Clear a few things. */
+	completion_charp = NULL;
+	completion_charpp = NULL;
+	completion_info_charp = NULL;
+	completion_info_charp2 = NULL;
 
 	/*
-	 * Macros for matching N words at the start of the line, regardless of
-	 * what is after them, case-insensitively.
+	 * Scan the input line to extract the words before our current position.
+	 * According to those we'll make some smart decisions on what the user is
+	 * probably intending to type.
 	 */
-#define HeadMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]))
+	previous_words = get_previous_words(start,
+										&words_buffer,
+										&previous_words_count);
+
+	matches  = psql_completion_internal(text, previous_words,
+										previous_words_count);
 
-#define HeadMatches2(p1, p2) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]))
+	/* free storage */
+	free(previous_words);
+	free(words_buffer);
 
-#define HeadMatches3(p1, p2, p3) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]) && \
-	 word_matches(p3, previous_words[previous_words_count - 3]))
+	if (matches != NULL)
+		return matches;
+
+	/*
+	 * If we still don't have anything to match we have to fabricate some sort
+	 * of default list. If we were to just return NULL, readline automatically
+	 * attempts filename completion, and that's usually no good.
+	 */
+#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
+	rl_completion_append_character = '\0';
+#endif
+	COMPLETE_WITH_CONST("");		/* This returns matches */
+}
 
+/*
+ * The completion function.
+ *
+ * Makes completion list.
+ * Note: COMPLETE_WITH_* macros immediately return to the caller.
+ */
+static char **
+psql_completion_internal(const char *text, char **previous_words,
+						 int previous_words_count)
+{
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
@@ -1343,90 +1043,80 @@ psql_completion(const char *text, int start, int end)
 		"\\timing", "\\unset", "\\x", "\\w", "\\watch", "\\z", "\\!", NULL
 	};
 
-	(void) end;					/* "end" is not used */
-
-#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
-	rl_completion_append_character = ' ';
-#endif
-
-	/* Clear a few things. */
-	completion_charp = NULL;
-	completion_charpp = NULL;
-	completion_info_charp = NULL;
-	completion_info_charp2 = NULL;
-
-	/*
-	 * Scan the input line to extract the words before our current position.
-	 * According to those we'll make some smart decisions on what the user is
-	 * probably intending to type.
-	 */
-	previous_words = get_previous_words(start,
-										&words_buffer,
-										&previous_words_count);
-
 	/* If current word is a backslash command, offer completions for that */
 	if (text[0] == '\\')
 		COMPLETE_WITH_LIST_CS(backslash_commands);
 
 	/* If current word is a variable interpolation, handle that case */
-	else if (text[0] == ':' && text[1] != ':')
+	if (text[0] == ':' && text[1] != ':')
 	{
 		if (text[1] == '\'')
-			matches = complete_from_variables(text, ":'", "'", true);
-		else if (text[1] == '"')
-			matches = complete_from_variables(text, ":\"", "\"", true);
-		else
-			matches = complete_from_variables(text, ":", "", true);
+			return complete_from_variables(text, ":'", "'", true);
+		if (text[1] == '"')
+			return complete_from_variables(text, ":\"", "\"", true);
+
+		return complete_from_variables(text, ":", "", true);
 	}
 
 	/* If no previous word, suggest one of the basic sql commands */
-	else if (previous_words_count == 0)
+	if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
 /* CREATE */
 	/* complete with something you can create */
-	else if (TailMatches1("CREATE"))
-		matches = completion_matches(text, create_command_generator);
+	if (Matches1("CREATE"))
+		return completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
-	else if (Matches1("DROP"))
-		matches = completion_matches(text, drop_command_generator);
+	if (Matches1("DROP"))
+		return completion_matches(text, drop_command_generator);
 
 /* ALTER */
 
 	/* ALTER TABLE */
-	else if (Matches2("ALTER", "TABLE"))
+	if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
 	/* ALTER something */
-	else if (Matches1("ALTER"))
+	if (Matches1("ALTER"))
 	{
 		static const char *const list_ALTER[] =
-		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
-			"EVENT TRIGGER", "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
-			"GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW", "OPERATOR",
-			"POLICY", "ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM", "TABLE",
-			"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
-		"USER", "USER MAPPING FOR", "VIEW", NULL};
+		{"AGGREGATE",
+		 "COLLATION", "CONVERSION",
+		 "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
+		 "EVENT TRIGGER", "EXTENSION",
+		 "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
+		 "GROUP",
+		 "INDEX",
+		 "LANGUAGE", "LARGE OBJECT",
+		 "MATERIALIZED VIEW", 
+		 "OPERATOR",
+		 "POLICY",
+		 "ROLE", "RULE",
+		 "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM",
+		 "TABLE", "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
+		 "USER", "USER MAPPING FOR",
+		 "VIEW",
+		 NULL};
 
 		COMPLETE_WITH_LIST(list_ALTER);
 	}
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx */
-	else if (TailMatches4("ALL", "IN", "TABLESPACE", MatchAny))
+	if (TailMatches4("ALL", "IN", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("SET TABLESPACE", "OWNED BY");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY */
-	else if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
+	if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY xxx */
-	else if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
+	if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("SET TABLESPACE");
 	/* ALTER AGGREGATE,FUNCTION <name> */
-	else if (Matches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
+	if (Matches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION <name> (...) */
-	else if (Matches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
+	if (Matches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
 	{
 		if (ends_with(prev_wd, ')'))
 			COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
@@ -1435,63 +1125,63 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER SCHEMA <name> */
-	else if (Matches3("ALTER", "SCHEMA", MatchAny))
+	if (Matches3("ALTER", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER TO", "RENAME TO");
 
 	/* ALTER COLLATION <name> */
-	else if (Matches3("ALTER", "COLLATION", MatchAny))
+	if (Matches3("ALTER", "COLLATION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER CONVERSION <name> */
-	else if (Matches3("ALTER", "CONVERSION", MatchAny))
+	if (Matches3("ALTER", "CONVERSION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER DATABASE <name> */
-	else if (Matches3("ALTER", "DATABASE", MatchAny))
+	if (Matches3("ALTER", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST7("RESET", "SET", "OWNER TO", "RENAME TO",
 							"IS_TEMPLATE", "ALLOW_CONNECTIONS",
 							"CONNECTION LIMIT");
 
 	/* ALTER EVENT TRIGGER */
-	else if (Matches3("ALTER", "EVENT", "TRIGGER"))
+	if (Matches3("ALTER", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
+	if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST4("DISABLE", "ENABLE", "OWNER TO", "RENAME TO");
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
+	if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST2("REPLICA", "ALWAYS");
 
 	/* ALTER EXTENSION <name> */
-	else if (Matches3("ALTER", "EXTENSION", MatchAny))
+	if (Matches3("ALTER", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST4("ADD", "DROP", "UPDATE", "SET SCHEMA");
 
 	/* ALTER EXTENSION <name> UPDATE */
-	else if (Matches4("ALTER", "EXTENSION", MatchAny, "UPDATE"))
+	if (Matches4("ALTER", "EXTENSION", MatchAny, "UPDATE"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions_with_TO);
 	}
 
 	/* ALTER EXTENSION <name> UPDATE TO */
-	else if (Matches5("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
+	if (Matches5("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
 	}
 
 	/* ALTER FOREIGN */
-	else if (Matches2("ALTER", "FOREIGN"))
+	if (Matches2("ALTER", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
 	/* ALTER FOREIGN TABLE <name> */
-	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
+	if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
 		{"ADD", "ALTER", "DISABLE TRIGGER", "DROP", "ENABLE", "INHERIT",
@@ -1502,42 +1192,42 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER INDEX */
-	else if (Matches2("ALTER", "INDEX"))
+	if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 	/* ALTER INDEX <name> */
-	else if (Matches3("ALTER", "INDEX", MatchAny))
+	if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
 	/* ALTER INDEX <name> SET */
-	else if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
+	if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
 		COMPLETE_WITH_LIST2("(", "TABLESPACE");
 	/* ALTER INDEX <name> RESET */
-	else if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
+	if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER INDEX <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
+	if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
 		COMPLETE_WITH_LIST3("fillfactor", "fastupdate",
 							"gin_pending_list_limit");
-	else if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
+	if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST3("fillfactor =", "fastupdate =",
 							"gin_pending_list_limit =");
 
 	/* ALTER LANGUAGE <name> */
-	else if (Matches3("ALTER", "LANGUAGE", MatchAny))
+	if (Matches3("ALTER", "LANGUAGE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER_TO", "RENAME TO");
 
 	/* ALTER LARGE OBJECT <oid> */
-	else if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
+	if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
 		COMPLETE_WITH_CONST("OWNER TO");
 
 	/* ALTER MATERIALIZED VIEW */
-	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
+	if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
 	/* ALTER USER,ROLE <name> */
-	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
-			 !TailMatches2("USER", "MAPPING"))
+	if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
+		!TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_ALTERUSER[] =
 		{"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -1551,7 +1241,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER USER,ROLE <name> WITH */
-	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
+	if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_ALTERUSER_WITH[] =
@@ -1566,43 +1256,43 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
-	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	if (Matches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* ALTER DEFAULT PRIVILEGES */
-	else if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
+	if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
 		COMPLETE_WITH_LIST3("FOR ROLE", "FOR USER", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR */
-	else if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
+	if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
 		COMPLETE_WITH_LIST2("ROLE", "USER");
 	/* ALTER DEFAULT PRIVILEGES { FOR ROLE ... | IN SCHEMA ... } */
-	else if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER", MatchAny) ||
+	if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER", MatchAny) ||
 		Matches6("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("GRANT", "REVOKE");
 	/* ALTER DOMAIN <name> */
-	else if (Matches3("ALTER", "DOMAIN", MatchAny))
+	if (Matches3("ALTER", "DOMAIN", MatchAny))
 		COMPLETE_WITH_LIST6("ADD", "DROP", "OWNER TO", "RENAME", "SET",
 							"VALIDATE CONSTRAINT");
 	/* ALTER DOMAIN <sth> DROP */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
 	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
 	}
 	/* ALTER DOMAIN <sth> RENAME */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("CONSTRAINT", "TO");
 	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
-	else if (Matches6("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
+	if (Matches6("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER DOMAIN <sth> SET */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	/* ALTER SEQUENCE <name> */
-	else if (Matches3("ALTER", "SEQUENCE", MatchAny))
+	if (Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1611,74 +1301,74 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
 	/* ALTER SEQUENCE <name> NO */
-	else if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
+	if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 	/* ALTER SERVER <name> */
-	else if (Matches3("ALTER", "SERVER", MatchAny))
+	if (Matches3("ALTER", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST4("VERSION", "OPTIONS", "OWNER TO", "RENAME TO");
 	/* ALTER SERVER <name> VERSION <version> */
-	else if (Matches5("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
+	if (Matches5("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
 		COMPLETE_WITH_CONST("OPTIONS");
 	/* ALTER SYSTEM SET, RESET, RESET ALL */
-	else if (Matches2("ALTER", "SYSTEM"))
+	if (Matches2("ALTER", "SYSTEM"))
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
-	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
+	if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
 	/* ALTER VIEW <name> */
-	else if (Matches3("ALTER", "VIEW", MatchAny))
+	if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
-	else if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
 	/* ALTER POLICY <name> */
-	else if (Matches2("ALTER", "POLICY"))
+	if (Matches2("ALTER", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* ALTER POLICY <name> ON */
-	else if (Matches3("ALTER", "POLICY", MatchAny))
+	if (Matches3("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
-	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
+	if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 	/* ALTER POLICY <name> ON <table> - show options */
-	else if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING (", "WITH CHECK (");
 	/* ALTER POLICY <name> ON <table> TO <role> */
-	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* ALTER POLICY <name> ON <table> USING ( */
-	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
-	else if (Matches7("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
+	if (Matches7("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
 		COMPLETE_WITH_CONST("(");
 
 	/* ALTER RULE <name>, add ON */
-	else if (Matches3("ALTER", "RULE", MatchAny))
+	if (Matches3("ALTER", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
-	else if (Matches4("ALTER", "RULE", MatchAny, "ON"))
+	if (Matches4("ALTER", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
 
 	/* ALTER RULE <name> ON <name> */
-	else if (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/* ALTER TRIGGER <name>, add ON */
-	else if (Matches3("ALTER", "TRIGGER", MatchAny))
+	if (Matches3("ALTER", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
-	else if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
+	if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
@@ -1687,17 +1377,17 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
-	else if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
+	if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER TRIGGER <name> ON <name> */
-	else if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/*
 	 * If we detect ALTER TABLE <name>, suggest sub commands
 	 */
-	else if (Matches3("ALTER", "TABLE", MatchAny))
+	if (Matches3("ALTER", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER2[] =
 		{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
@@ -1707,114 +1397,114 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
 	/* ALTER TABLE xxx ENABLE */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST5("ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE",
 							"TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
 		COMPLETE_WITH_LIST2("RULE", "TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
 	/* ALTER TABLE xxx INHERIT */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx NO INHERIT */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx DISABLE */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
 		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
 
 	/* ALTER TABLE xxx ALTER */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
 
 	/* ALTER TABLE xxx RENAME */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/* ALTER TABLE xxx RENAME yyy */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
+	if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
-	else if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
+	if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* If we have ALTER TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
+	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
 			 Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
 		COMPLETE_WITH_LIST4("TYPE", "SET", "RESET", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
 			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
 		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
+	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
 		 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
+	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
 	Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
 		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
 			 Matches8("ALTER", "TABLE", MatchAny, "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("DEFAULT", "NOT NULL");
-	else if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
 	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
 		COMPLETE_WITH_LIST7("(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED",
 							"WITH", "WITHOUT");
 
@@ -1822,19 +1512,19 @@ psql_completion(const char *text, int start, int end)
 	 * If we have ALTER TABLE <sth> SET TABLESPACE provide a list of
 	 * tablespaces
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	/* If we have ALTER TABLE <sth> SET WITH provide OIDS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
 		COMPLETE_WITH_CONST("OIDS");
 	/* If we have ALTER TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
 		COMPLETE_WITH_LIST2("CLUSTER", "OIDS");
 	/* ALTER TABLE <foo> RESET */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLE <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
 	{
 		static const char *const list_TABLEOPTIONS[] =
 		{
@@ -1872,108 +1562,108 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
-	else if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
+	if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
 		COMPLETE_WITH_LIST4("FULL", "NOTHING", "DEFAULT", "USING");
-	else if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
 		COMPLETE_WITH_CONST("IDENTITY");
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
-	else if (Matches3("ALTER", "TABLESPACE", MatchAny))
+	if (Matches3("ALTER", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "OWNER TO", "SET", "RESET");
 	/* ALTER TABLESPACE <foo> SET|RESET */
-	else if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
+	if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
+	if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
 		COMPLETE_WITH_LIST3("seq_page_cost", "random_page_cost",
 							"effective_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
-	else if (Matches3("ALTER", "TEXT", "SEARCH"))
+	if (Matches3("ALTER", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
 		COMPLETE_WITH_LIST2("RENAME TO", "SET SCHEMA");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING",
 							"DROP MAPPING FOR",
 							"OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* complete ALTER TYPE <foo> with actions */
-	else if (Matches3("ALTER", "TYPE", MatchAny))
+	if (Matches3("ALTER", "TYPE", MatchAny))
 		COMPLETE_WITH_LIST7("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE",
 							"DROP ATTRIBUTE",
 							"OWNER TO", "RENAME", "SET SCHEMA");
 	/* complete ALTER TYPE <foo> ADD with actions */
-	else if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
+	if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
 	/* ALTER TYPE <foo> RENAME	*/
-	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST3("ATTRIBUTE", "TO", "VALUE");
 	/* ALTER TYPE xxx RENAME (ATTRIBUTE|VALUE) yyy */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE|VALUE", MatchAny))
+	if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE|VALUE", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 	/*
 	 * If we have ALTER TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list
 	 * of attributes
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
+	if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
+	if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* complete ALTER GROUP <foo> */
-	else if (Matches3("ALTER", "GROUP", MatchAny))
+	if (Matches3("ALTER", "GROUP", MatchAny))
 		COMPLETE_WITH_LIST3("ADD USER", "DROP USER", "RENAME TO");
 	/* complete ALTER GROUP <foo> ADD|DROP with USER */
-	else if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
+	if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
 		COMPLETE_WITH_CONST("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
-	else if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
+	if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 	/*
 	 * If we have ALTER TYPE <sth> RENAME VALUE, provide list of enum values
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "RENAME", "VALUE"))
+	if (Matches5("ALTER", "TYPE", MatchAny, "RENAME", "VALUE"))
 		COMPLETE_WITH_ENUM_VALUE(prev3_wd);
 
 /* BEGIN */
-	else if (Matches1("BEGIN"))
+	if (Matches1("BEGIN"))
 		COMPLETE_WITH_LIST6("WORK", "TRANSACTION", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
 /* END, ABORT */
-	else if (Matches1("END|ABORT"))
+	if (Matches1("END|ABORT"))
 		COMPLETE_WITH_LIST2("WORK", "TRANSACTION");
 /* COMMIT */
-	else if (Matches1("COMMIT"))
+	if (Matches1("COMMIT"))
 		COMPLETE_WITH_LIST3("WORK", "TRANSACTION", "PREPARED");
 /* RELEASE SAVEPOINT */
-	else if (Matches1("RELEASE"))
+	if (Matches1("RELEASE"))
 		COMPLETE_WITH_CONST("SAVEPOINT");
 /* ROLLBACK */
-	else if (Matches1("ROLLBACK"))
+	if (Matches1("ROLLBACK"))
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
-	else if (Matches1("CLUSTER"))
+	if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
-	else if (Matches2("CLUSTER", "VERBOSE"))
+	if (Matches2("CLUSTER", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 	/* If we have CLUSTER <sth>, then add "USING" */
-	else if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
+	if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	else if (Matches3("CLUSTER", "VERBOSE", MatchAny))
+	if (Matches3("CLUSTER", "VERBOSE", MatchAny))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER <sth> USING, then add the index as well */
-	else if (Matches3("CLUSTER", MatchAny, "USING") ||
+	if (Matches3("CLUSTER", MatchAny, "USING") ||
 			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
@@ -1981,9 +1671,9 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* COMMENT */
-	else if (Matches1("COMMENT"))
+	if (Matches1("COMMENT"))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches2("COMMENT", "ON"))
+	if (Matches2("COMMENT", "ON"))
 	{
 		static const char *const list_COMMENT[] =
 		{"ACCESS METHOD", "CAST", "COLLATION", "CONVERSION", "DATABASE",
@@ -1996,26 +1686,26 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
-	else if (Matches4("COMMENT", "ON", "ACCESS", "METHOD"))
+	if (Matches4("COMMENT", "ON", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (Matches3("COMMENT", "ON", "FOREIGN"))
+	if (Matches3("COMMENT", "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
-	else if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
+	if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches3("COMMENT", "ON", "CONSTRAINT"))
+	if (Matches3("COMMENT", "ON", "CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
-	else if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
+	if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
+	if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
 	}
-	else if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
+	if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
+	if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	else if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
+	if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
 		Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
 			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
 		COMPLETE_WITH_CONST("IS");
@@ -2026,97 +1716,97 @@ psql_completion(const char *text, int start, int end)
 	 * If we have COPY, offer list of tables or "(" (Also cover the analogous
 	 * backslash command).
 	 */
-	else if (Matches1("COPY|\\copy"))
+	if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION ALL SELECT '('");
 	/* If we have COPY BINARY, complete with list of tables */
-	else if (Matches2("COPY", "BINARY"))
+	if (Matches2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* If we have COPY (, complete it with legal commands */
-	else if (Matches2("COPY|\\copy", "("))
+	if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
 	/* If we have COPY [BINARY] <sth>, complete it with "TO" or "FROM" */
-	else if (Matches2("COPY|\\copy", MatchAny) ||
+	if (Matches2("COPY|\\copy", MatchAny) ||
 			 Matches3("COPY", "BINARY", MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "TO");
 	/* If we have COPY [BINARY] <sth> FROM|TO, complete with filename */
-	else if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
+	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
 			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
 	{
 		completion_charp = "";
-		matches = completion_matches(text, complete_from_files);
+		return completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename */
-	else if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
+	if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
 			 Matches5("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
 		COMPLETE_WITH_LIST6("BINARY", "OIDS", "DELIMITER", "NULL", "CSV",
 							"ENCODING");
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename CSV */
-	else if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
+	if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
 			 Matches6("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny, "CSV"))
 		COMPLETE_WITH_LIST5("HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE",
 							"FORCE NOT NULL");
 
 	/* CREATE ACCESS METHOD */
 	/* Complete "CREATE ACCESS METHOD <name>" */
-	else if (Matches4("CREATE", "ACCESS", "METHOD", MatchAny))
+	if (Matches4("CREATE", "ACCESS", "METHOD", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE" */
-	else if (Matches5("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
+	if (Matches5("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
 		COMPLETE_WITH_CONST("INDEX");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE <type>" */
-	else if (Matches6("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
+	if (Matches6("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
 		COMPLETE_WITH_CONST("HANDLER");
 
 	/* CREATE DATABASE */
-	else if (Matches3("CREATE", "DATABASE", MatchAny))
+	if (Matches3("CREATE", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST9("OWNER", "TEMPLATE", "ENCODING", "TABLESPACE",
 							"IS_TEMPLATE",
 							"ALLOW_CONNECTIONS", "CONNECTION LIMIT",
 							"LC_COLLATE", "LC_CTYPE");
 
-	else if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
+	if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
-	else if (Matches2("CREATE", "EXTENSION"))
+	if (Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
-	else if (Matches3("CREATE", "EXTENSION", MatchAny))
+	if (Matches3("CREATE", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
 	/* CREATE EXTENSION <name> VERSION */
-	else if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
+	if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
 	}
 
 	/* CREATE FOREIGN */
-	else if (Matches2("CREATE", "FOREIGN"))
+	if (Matches2("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* CREATE FOREIGN DATA WRAPPER */
-	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	else if (TailMatches2("CREATE", "UNIQUE"))
+	if (TailMatches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
 	/*
 	 * If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY", and
 	 * existing indexes
 	 */
-	else if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	if (TailMatches2("CREATE|UNIQUE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
 								   " UNION SELECT 'CONCURRENTLY'");
 	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	else if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
+	if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
 			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
@@ -2124,11 +1814,11 @@ psql_completion(const char *text, int start, int end)
 	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
 	 * indexes
 	 */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+	if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'");
 	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
+	if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
 			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
@@ -2136,117 +1826,117 @@ psql_completion(const char *text, int start, int end)
 	 * Complete INDEX <name> ON <table> with a list of table columns (which
 	 * should really be in parens)
 	 */
-	else if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-			 TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+	if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
+		TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-			 TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
+	if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
+		TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* same if you put in USING */
-	else if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
+	if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	else if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
+	if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
+	if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
 			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
 			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-	else if (Matches3("CREATE", "POLICY", MatchAny))
+	if (Matches3("CREATE", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
-	else if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
+	if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
-	else if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
+	if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING (", "WITH CHECK (");
 	/* CREATE POLICY <name> ON <table> FOR ALL|SELECT|INSERT|UPDATE|DELETE */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
 		COMPLETE_WITH_LIST5("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
 	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
 		COMPLETE_WITH_LIST2("TO", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> FOR SELECT|DELETE TO|USING" */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
 		COMPLETE_WITH_LIST2("TO", "USING (");
 	/* CREATE POLICY <name> ON <table> FOR ALL|UPDATE TO|USING|WITH CHECK */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
 		COMPLETE_WITH_LIST3("TO", "USING (", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS ON" */
-	else if (Matches3("CREATE", "RULE", MatchAny))
+	if (Matches3("CREATE", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("AS ON");
 	/* Complete "CREATE RULE <sth> AS" with "ON" */
-	else if (Matches4("CREATE", "RULE", MatchAny, "AS"))
+	if (Matches4("CREATE", "RULE", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE RULE <sth> AS ON" with SELECT|UPDATE|INSERT|DELETE */
-	else if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
+	if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
 	/* Complete "AS ON SELECT|UPDATE|INSERT|DELETE" with a "TO" */
-	else if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
+	if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
-	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
+	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	else if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
+	if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
 			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	else if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
+	if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
 		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
-	else if (Matches3("CREATE", "SERVER", MatchAny))
+	if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
 /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	else if (TailMatches2("CREATE", "UNLOGGED"))
+	if (TailMatches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
 /* CREATE TABLESPACE */
-	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
+	if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
 	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
-	else if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
+	if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
 		COMPLETE_WITH_CONST("LOCATION");
 
 /* CREATE TEXT SEARCH */
-	else if (Matches3("CREATE", "TEXT", "SEARCH"))
+	if (Matches3("CREATE", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	else if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	if (TailMatches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+	if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
 	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
@@ -2254,10 +1944,10 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
 	 * tables
 	 */
-	else if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
+	if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
-	else if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
+	if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("ON", MatchAny))
 		COMPLETE_WITH_LIST7("NOT DEFERRABLE", "DEFERRABLE", "INITIALLY",
@@ -2292,13 +1982,13 @@ psql_completion(const char *text, int start, int end)
 			  TailMatches2("FOR", "ROW|STATEMENT")))
 		COMPLETE_WITH_LIST2("WHEN (", "EXECUTE PROCEDURE");
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
 	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("EXECUTE", "PROCEDURE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 
 /* CREATE ROLE,USER,GROUP <name> */
-	else if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
+	if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_CREATEROLE[] =
@@ -2313,7 +2003,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE ROLE,USER,GROUP <name> WITH */
-	else if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
+	if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_CREATEROLE_WITH[] =
@@ -2331,70 +2021,70 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
 	 * PASSWORD
 	 */
-	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
-	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
+	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE VIEW <name> with AS */
-	else if (TailMatches3("CREATE", "VIEW", MatchAny))
+	if (TailMatches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
+	if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches2("CREATE", "MATERIALIZED"))
+	if (Matches2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE EVENT TRIGGER */
-	else if (Matches2("CREATE", "EVENT"))
+	if (Matches2("CREATE", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	/* Complete CREATE EVENT TRIGGER <name> with ON */
-	else if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
+	if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
-	else if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
+	if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_LIST3("ddl_command_start", "ddl_command_end", "sql_drop");
 
 /* DECLARE */
-	else if (Matches2("DECLARE", MatchAny))
+	if (Matches2("DECLARE", MatchAny))
 		COMPLETE_WITH_LIST5("BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL",
 							"CURSOR");
-	else if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
+	if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
 		COMPLETE_WITH_LIST3("WITH HOLD", "WITHOUT HOLD", "FOR");
 
 /* DELETE --- can be inside EXPLAIN, RULE, etc */
 	/* ... despite which, only complete DELETE with FROM at start of line */
-	else if (Matches1("DELETE"))
+	if (Matches1("DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
-	else if (TailMatches2("DELETE", "FROM"))
+	if (TailMatches2("DELETE", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete DELETE FROM <table> */
-	else if (TailMatches3("DELETE", "FROM", MatchAny))
+	if (TailMatches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
 	/* XXX: implement tab completion for DELETE ... USING */
 
 /* DISCARD */
-	else if (Matches1("DISCARD"))
+	if (Matches1("DISCARD"))
 		COMPLETE_WITH_LIST4("ALL", "PLANS", "SEQUENCES", "TEMP");
 
 /* DO */
-	else if (Matches1("DO"))
+	if (Matches1("DO"))
 		COMPLETE_WITH_CONST("LANGUAGE");
 
 /* DROP */
 	/* Complete DROP object with CASCADE / RESTRICT */
-	else if (Matches3("DROP",
+	if (Matches3("DROP",
 					  "COLLATION|CONVERSION|DOMAIN|EXTENSION|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW",
 					  MatchAny) ||
 			 Matches4("DROP", "ACCESS", "METHOD", MatchAny) ||
@@ -2407,88 +2097,88 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
+	if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
-	else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
+	if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
-	else if (Matches2("DROP", "FOREIGN"))
+	if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* DROP INDEX */
-	else if (Matches2("DROP", "INDEX"))
+	if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
+	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches3("DROP", "INDEX", MatchAny))
+	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	else if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
+	if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
-	else if (Matches2("DROP", "MATERIALIZED"))
+	if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
 	/* DROP OWNED BY */
-	else if (Matches2("DROP", "OWNED"))
+	if (Matches2("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (Matches3("DROP", "OWNED", "BY"))
+	if (Matches3("DROP", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
-	else if (Matches3("DROP", "TEXT", "SEARCH"))
+	if (Matches3("DROP", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (Matches3("DROP", "TRIGGER", MatchAny))
+	if (Matches3("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
+	if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
 	}
-	else if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
+	if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP ACCESS METHOD */
-	else if (Matches2("DROP", "ACCESS"))
+	if (Matches2("DROP", "ACCESS"))
 		COMPLETE_WITH_CONST("METHOD");
-	else if (Matches3("DROP", "ACCESS", "METHOD"))
+	if (Matches3("DROP", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 
 	/* DROP EVENT TRIGGER */
-	else if (Matches2("DROP", "EVENT"))
+	if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
-	else if (Matches3("DROP", "EVENT", "TRIGGER"))
+	if (Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* DROP POLICY <name>  */
-	else if (Matches2("DROP", "POLICY"))
+	if (Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* DROP POLICY <name> ON */
-	else if (Matches3("DROP", "POLICY", MatchAny))
+	if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* DROP POLICY <name> ON <table> */
-	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
+	if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 
 	/* DROP RULE */
-	else if (Matches3("DROP", "RULE", MatchAny))
+	if (Matches3("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
+	if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
-	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
+	if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 /* EXECUTE */
-	else if (Matches1("EXECUTE"))
+	if (Matches1("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
 
 /* EXPLAIN */
@@ -2496,22 +2186,22 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
-	else if (Matches1("EXPLAIN"))
+	if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"ANALYZE", "VERBOSE");
-	else if (Matches2("EXPLAIN", "ANALYZE"))
+	if (Matches2("EXPLAIN", "ANALYZE"))
 		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"VERBOSE");
-	else if (Matches2("EXPLAIN", "VERBOSE") ||
+	if (Matches2("EXPLAIN", "VERBOSE") ||
 			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
 		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-	else if (Matches1("FETCH|MOVE"))
+	if (Matches1("FETCH|MOVE"))
 		COMPLETE_WITH_LIST4("ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE");
 	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
-	else if (Matches2("FETCH|MOVE", MatchAny))
+	if (Matches2("FETCH|MOVE", MatchAny))
 		COMPLETE_WITH_LIST3("ALL", "NEXT", "PRIOR");
 
 	/*
@@ -2519,31 +2209,31 @@ psql_completion(const char *text, int start, int end)
 	 * but we may as well tab-complete both: perhaps some users prefer one
 	 * variant or the other.
 	 */
-	else if (Matches3("FETCH|MOVE", MatchAny, MatchAny))
+	if (Matches3("FETCH|MOVE", MatchAny, MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "IN");
 
 /* FOREIGN DATA WRAPPER */
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
-	else if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
+	if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
 			 !TailMatches4("CREATE", MatchAny, MatchAny, MatchAny))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 	/* applies in CREATE SERVER */
-	else if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
+	if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
 			 HeadMatches2("CREATE", "SERVER"))
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /* FOREIGN TABLE */
-	else if (TailMatches2("FOREIGN", "TABLE") &&
+	if (TailMatches2("FOREIGN", "TABLE") &&
 			 !TailMatches3("CREATE", MatchAny, MatchAny))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* FOREIGN SERVER */
-	else if (TailMatches2("FOREIGN", "SERVER"))
+	if (TailMatches2("FOREIGN", "SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
 
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	else if (TailMatches1("GRANT|REVOKE"))
+	if (TailMatches1("GRANT|REVOKE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'SELECT'"
 							" UNION SELECT 'INSERT'"
@@ -2563,11 +2253,11 @@ psql_completion(const char *text, int start, int end)
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	else if (TailMatches2("GRANT|REVOKE", MatchAny))
+	if (TailMatches2("GRANT|REVOKE", MatchAny))
 	{
 		if (TailMatches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
-		else if (TailMatches2("GRANT", MatchAny))
+		if (TailMatches2("GRANT", MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2584,7 +2274,7 @@ psql_completion(const char *text, int start, int end)
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
 	 */
-	else if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
+	if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
 								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
 								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
@@ -2602,11 +2292,11 @@ psql_completion(const char *text, int start, int end)
 								   " UNION SELECT 'TABLESPACE'"
 								   " UNION SELECT 'TYPE'");
 
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
 							"TABLES IN SCHEMA");
 
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/*
@@ -2615,27 +2305,27 @@ psql_completion(const char *text, int start, int end)
 	 *
 	 * Complete "GRANT/REVOKE * ON *" with "TO/FROM".
 	 */
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
 		if (TailMatches1("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-		else if (TailMatches1("DOMAIN"))
+		if (TailMatches1("DOMAIN"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-		else if (TailMatches1("FUNCTION"))
+		if (TailMatches1("FUNCTION"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-		else if (TailMatches1("LANGUAGE"))
+		if (TailMatches1("LANGUAGE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-		else if (TailMatches1("SCHEMA"))
+		if (TailMatches1("SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-		else if (TailMatches1("SEQUENCE"))
+		if (TailMatches1("SEQUENCE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-		else if (TailMatches1("TABLE"))
+		if (TailMatches1("TABLE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-		else if (TailMatches1("TABLESPACE"))
+		if (TailMatches1("TABLESPACE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-		else if (TailMatches1("TYPE"))
+		if (TailMatches1("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-		else if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
+		if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2645,18 +2335,18 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "GRANT/REVOKE ... TO/FROM" with username, PUBLIC,
 	 * CURRENT_USER, or SESSION_USER.
 	 */
-	else if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
+	if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
 			 (HeadMatches1("REVOKE") && TailMatches1("FROM")))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 
 	/* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */
-	else if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
+	if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (HeadMatches1("REVOKE") && TailMatches3("ON", MatchAny, MatchAny))
+	if (HeadMatches1("REVOKE") && TailMatches3("ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("FROM");
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	else if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
+	if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
 	{
 		if (TailMatches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2665,7 +2355,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	else if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 	{
 		if (TailMatches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2674,7 +2364,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	else if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
+	if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
 	{
 		if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2683,62 +2373,62 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* GROUP BY */
-	else if (TailMatches3("FROM", MatchAny, "GROUP"))
+	if (TailMatches3("FROM", MatchAny, "GROUP"))
 		COMPLETE_WITH_CONST("BY");
 
 /* IMPORT FOREIGN SCHEMA */
-	else if (Matches1("IMPORT"))
+	if (Matches1("IMPORT"))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
-	else if (Matches2("IMPORT", "FOREIGN"))
+	if (Matches2("IMPORT", "FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT --- can be inside EXPLAIN, RULE, etc */
 	/* Complete INSERT with "INTO" */
-	else if (TailMatches1("INSERT"))
+	if (TailMatches1("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
-	else if (TailMatches2("INSERT", "INTO"))
+	if (TailMatches2("INSERT", "INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete "INSERT INTO <table> (" with attribute names */
-	else if (TailMatches4("INSERT", "INTO", MatchAny, "("))
+	if (TailMatches4("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES"
 	 */
-	else if (TailMatches3("INSERT", "INTO", MatchAny))
+	if (TailMatches3("INSERT", "INTO", MatchAny))
 		COMPLETE_WITH_LIST5("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES");
 
 	/*
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE"
 	 */
-	else if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
+	if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
 			 ends_with(prev_wd, ')'))
 		COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES");
 
 	/* Insert an open parenthesis after "VALUES" */
-	else if (TailMatches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
+	if (TailMatches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
 		COMPLETE_WITH_CONST("(");
 
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
-	else if (Matches1("LOCK"))
+	if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION SELECT 'TABLE'");
-	else if (Matches2("LOCK", "TABLE"))
+	if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* For the following, handle the case of a single table only for now */
 
 	/* Complete LOCK [TABLE] <table> with "IN" */
-	else if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
+	if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
 			 Matches3("LOCK", "TABLE", MatchAny))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	else if (Matches3("LOCK", MatchAny, "IN") ||
+	if (Matches3("LOCK", MatchAny, "IN") ||
 			 Matches4("LOCK", "TABLE", MatchAny, "IN"))
 		COMPLETE_WITH_LIST8("ACCESS SHARE MODE",
 							"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
@@ -2747,36 +2437,36 @@ psql_completion(const char *text, int start, int end)
 							"EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE");
 
 	/* Complete LOCK [TABLE] <table> IN ACCESS|ROW with rest of lock mode */
-	else if (Matches4("LOCK", MatchAny, "IN", "ACCESS|ROW") ||
+	if (Matches4("LOCK", MatchAny, "IN", "ACCESS|ROW") ||
 			 Matches5("LOCK", "TABLE", MatchAny, "IN", "ACCESS|ROW"))
 		COMPLETE_WITH_LIST2("EXCLUSIVE MODE", "SHARE MODE");
 
 	/* Complete LOCK [TABLE] <table> IN SHARE with rest of lock mode */
-	else if (Matches4("LOCK", MatchAny, "IN", "SHARE") ||
+	if (Matches4("LOCK", MatchAny, "IN", "SHARE") ||
 			 Matches5("LOCK", "TABLE", MatchAny, "IN", "SHARE"))
 		COMPLETE_WITH_LIST3("MODE", "ROW EXCLUSIVE MODE",
 							"UPDATE EXCLUSIVE MODE");
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
-	else if (TailMatches1("NOTIFY"))
+	if (TailMatches1("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
 
 /* OPTIONS */
-	else if (TailMatches1("OPTIONS"))
+	if (TailMatches1("OPTIONS"))
 		COMPLETE_WITH_CONST("(");
 
 /* OWNER TO  - complete with available roles */
-	else if (TailMatches2("OWNER", "TO"))
+	if (TailMatches2("OWNER", "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* ORDER BY */
-	else if (TailMatches3("FROM", MatchAny, "ORDER"))
+	if (TailMatches3("FROM", MatchAny, "ORDER"))
 		COMPLETE_WITH_CONST("BY");
-	else if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
+	if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 /* PREPARE xx AS */
-	else if (Matches3("PREPARE", MatchAny, "AS"))
+	if (Matches3("PREPARE", MatchAny, "AS"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
 
 /*
@@ -2785,60 +2475,60 @@ psql_completion(const char *text, int start, int end)
  */
 
 /* REASSIGN OWNED BY xxx TO yyy */
-	else if (Matches1("REASSIGN"))
+	if (Matches1("REASSIGN"))
 		COMPLETE_WITH_CONST("OWNED BY");
-	else if (Matches2("REASSIGN", "OWNED"))
+	if (Matches2("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (Matches3("REASSIGN", "OWNED", "BY"))
+	if (Matches3("REASSIGN", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
+	if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
+	if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* REFRESH MATERIALIZED VIEW */
-	else if (Matches1("REFRESH"))
+	if (Matches1("REFRESH"))
 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
-	else if (Matches2("REFRESH", "MATERIALIZED"))
+	if (Matches2("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
+	if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
+	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
+	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
+	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
-	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
+	if (Matches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
-	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
+	if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
-	else if (Matches7("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
+	if (Matches7("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
 
 /* REINDEX */
-	else if (Matches1("REINDEX"))
+	if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
-	else if (Matches2("REINDEX", "TABLE"))
+	if (Matches2("REINDEX", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-	else if (Matches2("REINDEX", "INDEX"))
+	if (Matches2("REINDEX", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches2("REINDEX", "SCHEMA"))
+	if (Matches2("REINDEX", "SCHEMA"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-	else if (Matches2("REINDEX", "SYSTEM|DATABASE"))
+	if (Matches2("REINDEX", "SYSTEM|DATABASE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 
 /* SECURITY LABEL */
-	else if (Matches1("SECURITY"))
+	if (Matches1("SECURITY"))
 		COMPLETE_WITH_CONST("LABEL");
-	else if (Matches2("SECURITY", "LABEL"))
+	if (Matches2("SECURITY", "LABEL"))
 		COMPLETE_WITH_LIST2("ON", "FOR");
-	else if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
+	if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches3("SECURITY", "LABEL", "ON") ||
+	if (Matches3("SECURITY", "LABEL", "ON") ||
 			 Matches5("SECURITY", "LABEL", "FOR", MatchAny, "ON"))
 	{
 		static const char *const list_SECURITY_LABEL[] =
@@ -2849,7 +2539,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_SECURITY_LABEL);
 	}
-	else if (Matches5("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
+	if (Matches5("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("IS");
 
 /* SELECT */
@@ -2857,69 +2547,69 @@ psql_completion(const char *text, int start, int end)
 
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
-	else if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
+	if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
-	else if (Matches1("SHOW"))
+	if (Matches1("SHOW"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
 	/* Complete "SET TRANSACTION" */
-	else if (Matches2("SET", "TRANSACTION"))
+	if (Matches2("SET", "TRANSACTION"))
 		COMPLETE_WITH_LIST5("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	else if (Matches2("BEGIN|START", "TRANSACTION") ||
-			 Matches2("BEGIN", "WORK") ||
-			 Matches1("BEGIN") ||
-		  Matches5("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
+	if (Matches2("BEGIN|START", "TRANSACTION") ||
+		Matches2("BEGIN", "WORK") ||
+		Matches1("BEGIN") ||
+		Matches5("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
 		COMPLETE_WITH_LIST4("ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
-			 Matches2("BEGIN", "NOT") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
+		Matches2("BEGIN", "NOT") ||
+		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
 		COMPLETE_WITH_CONST("DEFERRABLE");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
-			 Matches2("BEGIN", "ISOLATION") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
+		Matches2("BEGIN", "ISOLATION") ||
+		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
 		COMPLETE_WITH_CONST("LEVEL");
-	else if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
-			 Matches3("BEGIN", "ISOLATION", "LEVEL") ||
-			 Matches7("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
+	if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
+		Matches3("BEGIN", "ISOLATION", "LEVEL") ||
+		Matches7("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
 		COMPLETE_WITH_LIST3("READ", "REPEATABLE READ", "SERIALIZABLE");
-	else if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
-			 Matches4("BEGIN", "ISOLATION", "LEVEL", "READ") ||
-			 Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
+	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
+		Matches4("BEGIN", "ISOLATION", "LEVEL", "READ") ||
+		Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
 		COMPLETE_WITH_LIST2("UNCOMMITTED", "COMMITTED");
-	else if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
-			 Matches4("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
-			 Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
+	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
+		Matches4("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
+		Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
 		COMPLETE_WITH_CONST("READ");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
-			 Matches2("BEGIN", "READ") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
+		Matches2("BEGIN", "READ") ||
+		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
-	else if (Matches2("SET", "CONSTRAINTS"))
+	if (Matches2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
-	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
+	if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
-	else if (Matches2("SET", "ROLE"))
+	if (Matches2("SET", "ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
-	else if (Matches2("SET", "SESSION"))
+	if (Matches2("SET", "SESSION"))
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
-	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
+	if (Matches3("SET", "SESSION", "AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
 	/* Complete RESET SESSION with AUTHORIZATION */
-	else if (Matches2("RESET", "SESSION"))
+	if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
 	/* Complete SET <var> with "TO" */
-	else if (Matches2("SET", MatchAny))
+	if (Matches2("SET", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete ALTER DATABASE|FUNCTION|ROLE|USER ... SET <name> */
-	else if (HeadMatches2("ALTER", "DATABASE|FUNCTION|ROLE|USER") &&
+	if (HeadMatches2("ALTER", "DATABASE|FUNCTION|ROLE|USER") &&
 			 TailMatches2("SET", MatchAny))
 		COMPLETE_WITH_LIST2("FROM CURRENT", "TO");
 	/* Suggest possible variable values */
-	else if (TailMatches3("SET", MatchAny, "TO|="))
+	if (TailMatches3("SET", MatchAny, "TO|="))
 	{
 		/* special cased code for individual GUCs */
 		if (TailMatches2("DateStyle", "TO|="))
@@ -2932,112 +2622,117 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST(my_list);
 		}
-		else if (TailMatches2("search_path", "TO|="))
+		if (TailMatches2("search_path", "TO|="))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
 								" AND nspname not like 'pg\\_toast%%' "
 								" AND nspname not like 'pg\\_temp%%' "
 								" UNION SELECT 'DEFAULT' ");
 		else
 		{
-			/* generic, type based, GUC support */
+			/* generic, type based, GUC support, guctype is malloc'ed */
 			char	   *guctype = get_guctype(prev2_wd);
 
 			if (guctype && strcmp(guctype, "enum") == 0)
 			{
 				char		querybuf[1024];
 
+				free(guctype);
 				snprintf(querybuf, sizeof(querybuf), Query_for_enum, prev2_wd);
 				COMPLETE_WITH_QUERY(querybuf);
 			}
-			else if (guctype && strcmp(guctype, "bool") == 0)
+
+			if (guctype && strcmp(guctype, "bool") == 0)
+			{
+				free(guctype);
 				COMPLETE_WITH_LIST9("on", "off", "true", "false", "yes", "no",
 									"1", "0", "DEFAULT");
-			else
-				COMPLETE_WITH_CONST("DEFAULT");
+			}
 
 			if (guctype)
 				free(guctype);
+
+			COMPLETE_WITH_CONST("DEFAULT");
 		}
 	}
 
 /* START TRANSACTION */
-	else if (Matches1("START"))
+	if (Matches1("START"))
 		COMPLETE_WITH_CONST("TRANSACTION");
 
 /* TABLE, but not TABLE embedded in other commands */
-	else if (Matches1("TABLE"))
+	if (Matches1("TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
 /* TABLESAMPLE */
-	else if (TailMatches1("TABLESAMPLE"))
+	if (TailMatches1("TABLESAMPLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods);
-	else if (TailMatches2("TABLESAMPLE", MatchAny))
+	if (TailMatches2("TABLESAMPLE", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
-	else if (Matches1("TRUNCATE"))
+	if (Matches1("TRUNCATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* UNLISTEN */
-	else if (Matches1("UNLISTEN"))
+	if (Matches1("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
 
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
-	else if (TailMatches1("UPDATE"))
+	if (TailMatches1("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete UPDATE <table> with "SET" */
-	else if (TailMatches2("UPDATE", MatchAny))
+	if (TailMatches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
 	/* Complete UPDATE <table> SET with list of attributes */
-	else if (TailMatches3("UPDATE", MatchAny, "SET"))
+	if (TailMatches3("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* UPDATE <table> SET <attr> = */
-	else if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
+	if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
+	if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
-	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
+	if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
 							" UNION SELECT 'PUBLIC'"
 							" UNION SELECT 'USER'");
-	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
+	if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
+	if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("SERVER");
-	else if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
+	if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /*
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
-	else if (Matches1("VACUUM"))
+	if (Matches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'FULL'"
 								   " UNION SELECT 'FREEZE'"
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches2("VACUUM", "FULL|FREEZE"))
+	if (Matches2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
+	if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
+	if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (Matches2("VACUUM", "VERBOSE"))
+	if (Matches2("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (Matches2("VACUUM", "ANALYZE"))
+	if (Matches2("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (HeadMatches1("VACUUM"))
+	if (HeadMatches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 /* WITH [RECURSIVE] */
@@ -3046,114 +2741,114 @@ psql_completion(const char *text, int start, int end)
 	 * Only match when WITH is the first word, as WITH may appear in many
 	 * other contexts.
 	 */
-	else if (Matches1("WITH"))
+	if (Matches1("WITH"))
 		COMPLETE_WITH_CONST("RECURSIVE");
 
 /* ANALYZE */
 	/* Complete with list of tables */
-	else if (Matches1("ANALYZE"))
+	if (Matches1("ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
-	else if (TailMatches2(MatchAny, "WHERE"))
+	if (TailMatches2(MatchAny, "WHERE"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
-	else if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
+	if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* ... JOIN ... */
-	else if (TailMatches1("JOIN"))
+	if (TailMatches1("JOIN"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
-	else if (TailMatchesCS1("\\?"))
+	if (TailMatchesCS1("\\?"))
 		COMPLETE_WITH_LIST_CS3("commands", "options", "variables");
-	else if (TailMatchesCS1("\\connect|\\c"))
+	if (TailMatchesCS1("\\connect|\\c"))
 	{
 		if (!recognized_connection_string(text))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 	}
-	else if (TailMatchesCS2("\\connect|\\c", MatchAny))
+	if (TailMatchesCS2("\\connect|\\c", MatchAny))
 	{
 		if (!recognized_connection_string(prev_wd))
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	}
-	else if (TailMatchesCS1("\\da*"))
+	if (TailMatchesCS1("\\da*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
-	else if (TailMatchesCS1("\\dA*"))
+	if (TailMatchesCS1("\\dA*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (TailMatchesCS1("\\db*"))
+	if (TailMatchesCS1("\\db*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-	else if (TailMatchesCS1("\\dD*"))
+	if (TailMatchesCS1("\\dD*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-	else if (TailMatchesCS1("\\des*"))
+	if (TailMatchesCS1("\\des*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
-	else if (TailMatchesCS1("\\deu*"))
+	if (TailMatchesCS1("\\deu*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (TailMatchesCS1("\\dew*"))
+	if (TailMatchesCS1("\\dew*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
-	else if (TailMatchesCS1("\\df*"))
+	if (TailMatchesCS1("\\df*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 
-	else if (TailMatchesCS1("\\dFd*"))
+	if (TailMatchesCS1("\\dFd*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
-	else if (TailMatchesCS1("\\dFp*"))
+	if (TailMatchesCS1("\\dFp*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
-	else if (TailMatchesCS1("\\dFt*"))
+	if (TailMatchesCS1("\\dFt*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
 	/* must be at end of \dF alternatives: */
-	else if (TailMatchesCS1("\\dF*"))
+	if (TailMatchesCS1("\\dF*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
 
-	else if (TailMatchesCS1("\\di*"))
+	if (TailMatchesCS1("\\di*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (TailMatchesCS1("\\dL*"))
+	if (TailMatchesCS1("\\dL*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-	else if (TailMatchesCS1("\\dn*"))
+	if (TailMatchesCS1("\\dn*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-	else if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
+	if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-	else if (TailMatchesCS1("\\ds*"))
+	if (TailMatchesCS1("\\ds*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-	else if (TailMatchesCS1("\\dt*"))
+	if (TailMatchesCS1("\\dt*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-	else if (TailMatchesCS1("\\dT*"))
+	if (TailMatchesCS1("\\dT*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-	else if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
+	if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (TailMatchesCS1("\\dv*"))
+	if (TailMatchesCS1("\\dv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (TailMatchesCS1("\\dx*"))
+	if (TailMatchesCS1("\\dx*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
-	else if (TailMatchesCS1("\\dm*"))
+	if (TailMatchesCS1("\\dm*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (TailMatchesCS1("\\dE*"))
+	if (TailMatchesCS1("\\dE*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
-	else if (TailMatchesCS1("\\dy*"))
+	if (TailMatchesCS1("\\dy*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* must be at end of \d alternatives: */
-	else if (TailMatchesCS1("\\d*"))
+	if (TailMatchesCS1("\\d*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
-	else if (TailMatchesCS1("\\ef"))
+	if (TailMatchesCS1("\\ef"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (TailMatchesCS1("\\ev"))
+	if (TailMatchesCS1("\\ev"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
-	else if (TailMatchesCS1("\\encoding"))
+	if (TailMatchesCS1("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
-	else if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
+	if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
 		COMPLETE_WITH_LIST(sql_commands);
-	else if (TailMatchesCS1("\\l*") && !TailMatchesCS1("\\lo*"))
+	if (TailMatchesCS1("\\l*") && !TailMatchesCS1("\\lo*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-	else if (TailMatchesCS1("\\password"))
+	if (TailMatchesCS1("\\password"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (TailMatchesCS1("\\pset"))
+	if (TailMatchesCS1("\\pset"))
 	{
 		static const char *const my_list[] =
 		{"border", "columns", "expanded", "fieldsep", "fieldsep_zero",
@@ -3164,7 +2859,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (TailMatchesCS2("\\pset", MatchAny))
+	if (TailMatchesCS2("\\pset", MatchAny))
 	{
 		if (TailMatchesCS1("format"))
 		{
@@ -3174,53 +2869,53 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (TailMatchesCS1("linestyle"))
+		if (TailMatchesCS1("linestyle"))
 			COMPLETE_WITH_LIST_CS3("ascii", "old-ascii", "unicode");
-		else if (TailMatchesCS1("unicode_border_linestyle|"
+		if (TailMatchesCS1("unicode_border_linestyle|"
 								"unicode_column_linestyle|"
 								"unicode_header_linestyle"))
 			COMPLETE_WITH_LIST_CS2("single", "double");
 	}
-	else if (TailMatchesCS1("\\unset"))
+	if (TailMatchesCS1("\\unset"))
 	{
-		matches = complete_from_variables(text, "", "", true);
+		return complete_from_variables(text, "", "", true);
 	}
-	else if (TailMatchesCS1("\\set"))
+	if (TailMatchesCS1("\\set"))
 	{
-		matches = complete_from_variables(text, "", "", false);
+		return complete_from_variables(text, "", "", false);
 	}
-	else if (TailMatchesCS2("\\set", MatchAny))
+	if (TailMatchesCS2("\\set", MatchAny))
 	{
 		if (TailMatchesCS1("AUTOCOMMIT|ON_ERROR_STOP|QUIET|"
 						   "SINGLELINE|SINGLESTEP"))
 			COMPLETE_WITH_LIST_CS2("on", "off");
-		else if (TailMatchesCS1("COMP_KEYWORD_CASE"))
+		if (TailMatchesCS1("COMP_KEYWORD_CASE"))
 			COMPLETE_WITH_LIST_CS4("lower", "upper",
 								   "preserve-lower", "preserve-upper");
-		else if (TailMatchesCS1("ECHO"))
+		if (TailMatchesCS1("ECHO"))
 			COMPLETE_WITH_LIST_CS4("errors", "queries", "all", "none");
-		else if (TailMatchesCS1("ECHO_HIDDEN"))
+		if (TailMatchesCS1("ECHO_HIDDEN"))
 			COMPLETE_WITH_LIST_CS3("noexec", "off", "on");
-		else if (TailMatchesCS1("HISTCONTROL"))
+		if (TailMatchesCS1("HISTCONTROL"))
 			COMPLETE_WITH_LIST_CS4("ignorespace", "ignoredups",
 								   "ignoreboth", "none");
-		else if (TailMatchesCS1("ON_ERROR_ROLLBACK"))
+		if (TailMatchesCS1("ON_ERROR_ROLLBACK"))
 			COMPLETE_WITH_LIST_CS3("on", "off", "interactive");
-		else if (TailMatchesCS1("SHOW_CONTEXT"))
+		if (TailMatchesCS1("SHOW_CONTEXT"))
 			COMPLETE_WITH_LIST_CS3("never", "errors", "always");
-		else if (TailMatchesCS1("VERBOSITY"))
+		if (TailMatchesCS1("VERBOSITY"))
 			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
 	}
-	else if (TailMatchesCS1("\\sf*"))
+	if (TailMatchesCS1("\\sf*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (TailMatchesCS1("\\sv*"))
+	if (TailMatchesCS1("\\sv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
+	if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
 							"\\ir|\\include_relative|\\o|\\out|"
 							"\\s|\\w|\\write|\\lo_import"))
 	{
 		completion_charp = "\\";
-		matches = completion_matches(text, complete_from_files);
+		return completion_matches(text, complete_from_files);
 	}
 
 	/*
@@ -3238,7 +2933,7 @@ psql_completion(const char *text, int start, int end)
 			{
 				if (words_after_create[i].query)
 					COMPLETE_WITH_QUERY(words_after_create[i].query);
-				else if (words_after_create[i].squery)
+				if (words_after_create[i].squery)
 					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
 											   NULL);
 				break;
@@ -3246,25 +2941,8 @@ psql_completion(const char *text, int start, int end)
 		}
 	}
 
-	/*
-	 * If we still don't have anything to match we have to fabricate some sort
-	 * of default list. If we were to just return NULL, readline automatically
-	 * attempts filename completion, and that's usually no good.
-	 */
-	if (matches == NULL)
-	{
-		COMPLETE_WITH_CONST("");
-#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
-		rl_completion_append_character = '\0';
-#endif
-	}
-
-	/* free storage */
-	free(previous_words);
-	free(words_buffer);
-
-	/* Return our Grand List O' Matches */
-	return matches;
+	/* We found no match */
+	return NULL;
 }
 
 
-- 
2.9.2

0002-Make-keywords-case-follow-to-input.patchtext/x-patch; charset=us-asciiDownload
From cf75392e8c6d90afdde8558074487e5bc037093a Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Wed, 14 Sep 2016 12:48:16 +0900
Subject: [PATCH 02/12] Make keywords' case follow to input

Currently some keywords suggested along with database objects are
always in upper case. This patch changes the behavior so that the case
of the additional keywords follow the setting of COMP_KEYWORD_CASE.

COMPLETE_WITH_ATTR needs completion_charp to be appendable, so this
patch changes it to a PQExpBuffer and adjust COMPLET_WITH_* macros to
that change.

Since COMPLETE_WITH_(QUERY|SCHEMA_QUERY|ATTR) are not given addons in
most cases so each of them are split into two macros that requires
addons (suffixed by _KW) and that don't.

This leaves keywords contained in Query_for_list_of_grant_roles and
Query_for_enum, but it is another problem.
---
 src/bin/psql/tab-complete-macros.h |  70 ++++++--
 src/bin/psql/tab-complete.c        | 358 ++++++++++++++++++++-----------------
 2 files changed, 246 insertions(+), 182 deletions(-)

diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
index 97ffcd1..44fd547 100644
--- a/src/bin/psql/tab-complete-macros.h
+++ b/src/bin/psql/tab-complete-macros.h
@@ -231,16 +231,46 @@
  * 5) The list of attributes of the given table (possibly schema-qualified).
  * 6) The list of arguments to the given function (possibly schema-qualified).
  */
-#define COMPLETE_WITH_QUERY(query) \
+#define APPEND_COMP_CHARP(charp) \
+	appendPQExpBufferStr(completion_charp, charp);
+
+#define SET_COMP_CHARP(charp) \
+	resetPQExpBuffer(completion_charp);	\
+	APPEND_COMP_CHARP(charp);
+
+#define COMPLETION_CHARP (completion_charp->data)
+
+#define COMPLETE_WITH_QUERY(query)				\
+do { \
+	SET_COMP_CHARP(query);	\
+	return completion_matches(text, complete_from_query);	\
+} while (0)
+
+/*
+ * COMPLETE_WITH_QUERY with additional keywords. Keywords are complete
+ * case-sensitively
+ */
+#define COMPLETE_WITH_QUERY_KW(query, addon)				\
 do { \
-	completion_charp = query;	\
+	SET_COMP_CHARP(query);	\
+	APPEND_COMP_CHARP(addon); \
 	return completion_matches(text, complete_from_query);	\
 } while (0)
 
-#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
+#define COMPLETE_WITH_SCHEMA_QUERY(query) \
 do { \
 	completion_squery = &(query); \
-	completion_charp = addon; \
+	return completion_matches(text, complete_from_schema_query); \
+} while (0)
+
+/*
+ * COMPLETE_WITH_SCHEMA_QUERY with additional keywords. Keywords are complete
+ * case-sensitively
+ */
+#define COMPLETE_WITH_SCHEMA_QUERY_KW(query, addon)	\
+do { \
+	completion_squery = &(query); \
+	SET_COMP_CHARP(addon); \
 	return completion_matches(text, complete_from_schema_query); \
 } while (0)
 
@@ -260,12 +290,12 @@ do { \
 
 #define COMPLETE_WITH_CONST(string) \
 do { \
-	completion_charp = string;	\
+	SET_COMP_CHARP(string);	\
 	completion_case_sensitive = false; \
 	return completion_matches(text, complete_from_const);	\
 } while (0)
 
-#define COMPLETE_WITH_ATTR(relation, addon) \
+#define COMPLETE_WITH_ATTR_KW(relation, addon) \
 do { \
 	char   *_completion_schema; \
 	char   *_completion_table; \
@@ -278,18 +308,22 @@ do { \
 								false, false, pset.encoding); \
 	if (_completion_table == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_attributes addon; \
+		SET_COMP_CHARP(Query_for_list_of_attributes); \
+		APPEND_COMP_CHARP(addon);					  \
 		completion_info_charp = relation; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_attributes_with_schema addon; \
+		SET_COMP_CHARP(Query_for_list_of_attributes_with_schema); \
+		APPEND_COMP_CHARP(addon); \
 		completion_info_charp = _completion_table; \
 		completion_info_charp2 = _completion_schema; \
 	} \
 	return completion_matches(text, complete_from_query); \
 } while (0)
 
+#define COMPLETE_WITH_ATTR(query) COMPLETE_WITH_ATTR_KW((query), "")
+
 #define COMPLETE_WITH_ENUM_VALUE(type) \
 do { \
 	char   *_completion_schema; \
@@ -303,12 +337,12 @@ do { \
 							   false, false, pset.encoding);  \
 	if (_completion_type == NULL)\
 	{ \
-		completion_charp = Query_for_list_of_enum_values;	\
+		SET_COMP_CHARP(Query_for_list_of_enum_values);	\
 		completion_info_charp = type; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_enum_values_with_schema;	\
+		SET_COMP_CHARP(Query_for_list_of_enum_values_with_schema);	\
 		completion_info_charp = _completion_type; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -328,12 +362,12 @@ do { \
 								   false, false, pset.encoding); \
 	if (_completion_function == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_arguments; \
+		SET_COMP_CHARP(Query_for_list_of_arguments); \
 		completion_info_charp = function; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_arguments_with_schema;	\
+		SET_COMP_CHARP(Query_for_list_of_arguments_with_schema); \
 		completion_info_charp = _completion_function; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -425,6 +459,16 @@ do { \
 	COMPLETE_WITH_LIST_CS(list); \
 } while (0)
 
-
+#define ADDLIST1(s1) additional_kw_query(text, 1, s1)
+#define ADDLIST2(s1, s2) additional_kw_query(text, 2, s1, s2)
+#define ADDLIST3(s1, s2, s3) additional_kw_query(text, 3, s1, s2, s3)
+#define ADDLIST4(s1, s2, s3, s4) \
+	additional_kw_query(text, 4, s1, s2, s3, s4)
+#define ADDLIST13(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
+	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13)
+#define ADDLIST15(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
+	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13, s14, s15)
 
 #endif   /* TAB_COMPLETE_MACROS_H */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 24669d0..21eb7ab 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -126,7 +126,7 @@ static int	completion_max_records;
  * Communication variables set by COMPLETE_WITH_FOO macros and then used by
  * the completion callback functions.  Ugly but there is no better way.
  */
-static const char *completion_charp;	/* to pass a string */
+static PQExpBuffer completion_charp = NULL;		/* to pass a string */
 static const char *const * completion_charpp;	/* to pass a list of strings */
 static const char *completion_info_charp;		/* to pass a second string */
 static const char *completion_info_charp2;		/* to pass a third string */
@@ -796,6 +796,7 @@ static char **complete_from_variables(const char *text,
 static char *complete_from_files(const char *text, int state);
 
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
+static char *additional_kw_query( const char *ref, int n, ...);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
 
@@ -967,7 +968,8 @@ psql_completion(const char *text, int start, int end)
 #endif
 
 	/* Clear a few things. */
-	completion_charp = NULL;
+	if (completion_charp == NULL)
+		completion_charp = createPQExpBuffer();
 	completion_charpp = NULL;
 	completion_info_charp = NULL;
 	completion_info_charp2 = NULL;
@@ -1076,8 +1078,8 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER TABLE */
 	if (Matches2("ALTER", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
+									  ADDLIST1("ALL IN TABLESPACE"));
 
 	/* ALTER something */
 	if (Matches1("ALTER"))
@@ -1193,8 +1195,8 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER INDEX */
 	if (Matches2("ALTER", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+									  ADDLIST1("ALL IN TABLESPACE"));
 	/* ALTER INDEX <name> */
 	if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
@@ -1222,8 +1224,8 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER MATERIALIZED VIEW */
 	if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_matviews,
+									  ADDLIST1("ALL IN TABLESPACE"));
 
 	/* ALTER USER,ROLE <name> */
 	if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
@@ -1378,7 +1380,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
 	if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 	/* ALTER TRIGGER <name> ON <name> */
 	if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
@@ -1424,10 +1426,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	}
 	/* ALTER TABLE xxx INHERIT */
 	if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* ALTER TABLE xxx NO INHERIT */
 	if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* ALTER TABLE xxx DISABLE */
 	if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
 		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
@@ -1444,13 +1446,13 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER TABLE xxx ALTER */
 	if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
+		COMPLETE_WITH_ATTR_KW(prev2_wd, ADDLIST2("COLUMN", "CONSTRAINT"));
 
 	/* ALTER TABLE xxx RENAME */
 	if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
+		COMPLETE_WITH_ATTR_KW(prev2_wd, ADDLIST3("COLUMN", "CONSTRAINT", "TO"));
 	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 
 	/* ALTER TABLE xxx RENAME yyy */
 	if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
@@ -1465,7 +1467,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
@@ -1616,7 +1618,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * of attributes
 	 */
 	if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
 	if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
@@ -1653,9 +1655,10 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
 	if (Matches1("CLUSTER"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("VERBOSE"));
 	if (Matches2("CLUSTER", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 	/* If we have CLUSTER <sth>, then add "USING" */
 	if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
 		COMPLETE_WITH_CONST("USING");
@@ -1702,7 +1705,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
 	}
 	if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 	if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 	if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
@@ -1717,11 +1720,11 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * backslash command).
 	 */
 	if (Matches1("COPY|\\copy"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION ALL SELECT '('");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
+									  ADDLIST1("("));
 	/* If we have COPY BINARY, complete with list of tables */
 	if (Matches2("COPY", "BINARY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* If we have COPY (, complete it with legal commands */
 	if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
@@ -1733,7 +1736,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
 			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
 	{
-		completion_charp = "";
+		SET_COMP_CHARP("");
 		return completion_matches(text, complete_from_files);
 	}
 
@@ -1802,21 +1805,20 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * existing indexes
 	 */
 	if (TailMatches2("CREATE|UNIQUE", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'ON'"
-								   " UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+									  ADDLIST2("ON", "CONCURRENTLY"));
 	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
 	if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
 			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 
 	/*
 	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
 	 * indexes
 	 */
 	if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'ON'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+									  ADDLIST1("ON"));
 	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
 	if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
 			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
@@ -1831,10 +1833,10 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("(", "USING");
 	if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
 		TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+		COMPLETE_WITH_ATTR(prev2_wd);
 	/* same if you put in USING */
 	if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev4_wd, "");
+		COMPLETE_WITH_ATTR(prev4_wd);
 	/* Complete USING with an index method */
 	if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
@@ -1851,7 +1853,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
 	if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
 	if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING (", "WITH CHECK (");
@@ -1889,7 +1891,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
 	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 /* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
@@ -1945,47 +1947,47 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * tables
 	 */
 	if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
 	if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("ON", MatchAny))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("ON", MatchAny))
 		COMPLETE_WITH_LIST7("NOT DEFERRABLE", "DEFERRABLE", "INITIALLY",
 							"REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
+	if (HeadMatches2("CREATE", "TRIGGER") &&
 			 (TailMatches1("DEFERRABLE") || TailMatches2("INITIALLY", "IMMEDIATE|DEFERRED")))
 		COMPLETE_WITH_LIST4("REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("REFERENCING"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("REFERENCING"))
 		COMPLETE_WITH_LIST2("OLD TABLE", "NEW TABLE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("OLD|NEW", "TABLE"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("OLD|NEW", "TABLE"))
 		COMPLETE_WITH_CONST("AS");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches5("REFERENCING", "OLD", "TABLE", "AS", MatchAny) ||
-			  TailMatches4("REFERENCING", "OLD", "TABLE", MatchAny)))
+	if (HeadMatches2("CREATE", "TRIGGER") &&
+		(TailMatches5("REFERENCING", "OLD", "TABLE", "AS", MatchAny) ||
+		 TailMatches4("REFERENCING", "OLD", "TABLE", MatchAny)))
 		COMPLETE_WITH_LIST4("NEW TABLE", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches5("REFERENCING", "NEW", "TABLE", "AS", MatchAny) ||
-			  TailMatches4("REFERENCING", "NEW", "TABLE", MatchAny)))
+	if (HeadMatches2("CREATE", "TRIGGER") &&
+		(TailMatches5("REFERENCING", "NEW", "TABLE", "AS", MatchAny) ||
+		 TailMatches4("REFERENCING", "NEW", "TABLE", MatchAny)))
 		COMPLETE_WITH_LIST4("OLD TABLE", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches9("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
-			  TailMatches8("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
-			  TailMatches8("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", MatchAny) ||
-			  TailMatches7("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", MatchAny)))
+	if (HeadMatches2("CREATE", "TRIGGER") &&
+		(TailMatches9("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
+		 TailMatches8("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
+		 TailMatches8("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", MatchAny) ||
+		 TailMatches7("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", MatchAny)))
 		COMPLETE_WITH_LIST3("FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("FOR"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("FOR"))
 		COMPLETE_WITH_LIST3("EACH", "ROW", "STATEMENT");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("FOR", "EACH"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("FOR", "EACH"))
 		COMPLETE_WITH_LIST2("ROW", "STATEMENT");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
+	if (HeadMatches2("CREATE", "TRIGGER") &&
 			 (TailMatches3("FOR", "EACH", "ROW|STATEMENT") ||
 			  TailMatches2("FOR", "ROW|STATEMENT")))
 		COMPLETE_WITH_LIST2("WHEN (", "EXECUTE PROCEDURE");
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
 	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("EXECUTE", "PROCEDURE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("EXECUTE", "PROCEDURE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 
 /* CREATE ROLE,USER,GROUP <name> */
 	if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
@@ -2068,7 +2070,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
 	if (TailMatches2("DELETE", "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete DELETE FROM <table> */
 	if (TailMatches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
@@ -2106,10 +2108,10 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* DROP INDEX */
 	if (Matches2("DROP", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+									  ADDLIST1("CONCURRENTLY"));
 	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 	if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
@@ -2119,7 +2121,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 
 	/* DROP OWNED BY */
 	if (Matches2("DROP", "OWNED"))
@@ -2225,7 +2227,7 @@ psql_completion_internal(const char *text, char **previous_words,
 /* FOREIGN TABLE */
 	if (TailMatches2("FOREIGN", "TABLE") &&
 			 !TailMatches3("CREATE", MatchAny, MatchAny))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables);
 
 /* FOREIGN SERVER */
 	if (TailMatches2("FOREIGN", "SERVER"))
@@ -2234,20 +2236,10 @@ psql_completion_internal(const char *text, char **previous_words,
 /* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
 	if (TailMatches1("GRANT|REVOKE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'SELECT'"
-							" UNION SELECT 'INSERT'"
-							" UNION SELECT 'UPDATE'"
-							" UNION SELECT 'DELETE'"
-							" UNION SELECT 'TRUNCATE'"
-							" UNION SELECT 'REFERENCES'"
-							" UNION SELECT 'TRIGGER'"
-							" UNION SELECT 'CREATE'"
-							" UNION SELECT 'CONNECT'"
-							" UNION SELECT 'TEMPORARY'"
-							" UNION SELECT 'EXECUTE'"
-							" UNION SELECT 'USAGE'"
-							" UNION SELECT 'ALL'");
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+			ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
+					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
+					  "EXECUTE", "USAGE", "ALL"));
 
 	/*
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
@@ -2275,22 +2267,22 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * privilege.
 	 */
 	if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
-								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
-								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
-								   " UNION SELECT 'ALL TABLES IN SCHEMA'"
-								   " UNION SELECT 'DATABASE'"
-								   " UNION SELECT 'DOMAIN'"
-								   " UNION SELECT 'FOREIGN DATA WRAPPER'"
-								   " UNION SELECT 'FOREIGN SERVER'"
-								   " UNION SELECT 'FUNCTION'"
-								   " UNION SELECT 'LANGUAGE'"
-								   " UNION SELECT 'LARGE OBJECT'"
-								   " UNION SELECT 'SCHEMA'"
-								   " UNION SELECT 'SEQUENCE'"
-								   " UNION SELECT 'TABLE'"
-								   " UNION SELECT 'TABLESPACE'"
-								   " UNION SELECT 'TYPE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tsvmf,
+			   ADDLIST15("ALL FUNCTIONS IN SCHEMA",
+						 "ALL SEQUENCES IN SCHEMA",
+						 "ALL TABLES IN SCHEMA",
+						 "DATABASE",
+						 "DOMAIN",
+						 "FOREIGN DATA WRAPPER",
+						 "FOREIGN SERVER",
+						 "FUNCTION",
+						 "LANGUAGE",
+						 "LARGE OBJECT",
+						 "SCHEMA",
+						 "SEQUENCE",
+						 "TABLE",
+						 "TABLESPACE",
+						 "TYPE"));
 
 	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
@@ -2310,21 +2302,21 @@ psql_completion_internal(const char *text, char **previous_words,
 		if (TailMatches1("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 		if (TailMatches1("DOMAIN"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains);
 		if (TailMatches1("FUNCTION"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 		if (TailMatches1("LANGUAGE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
 		if (TailMatches1("SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
 		if (TailMatches1("SEQUENCE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
 		if (TailMatches1("TABLE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf);
 		if (TailMatches1("TABLESPACE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 		if (TailMatches1("TYPE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 		if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
@@ -2388,10 +2380,10 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
 	if (TailMatches2("INSERT", "INTO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete "INSERT INTO <table> (" with attribute names */
 	if (TailMatches4("INSERT", "INTO", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+		COMPLETE_WITH_ATTR(prev2_wd);
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
@@ -2415,10 +2407,10 @@ psql_completion_internal(const char *text, char **previous_words,
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
 	if (Matches1("LOCK"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION SELECT 'TABLE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
+									  ADDLIST1("TABLE"));
 	if (Matches2("LOCK", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 	/* For the following, handle the case of a single table only for now */
 
@@ -2463,7 +2455,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatches3("FROM", MatchAny, "ORDER"))
 		COMPLETE_WITH_CONST("BY");
 	if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 
 /* PREPARE xx AS */
 	if (Matches3("PREPARE", MatchAny, "AS"))
@@ -2492,10 +2484,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_matviews,
+									  ADDLIST1("CONCURRENTLY"));
 	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
 	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
@@ -2513,9 +2505,9 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
 	if (Matches2("REINDEX", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 	if (Matches2("REINDEX", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	if (Matches2("REINDEX", "SCHEMA"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
 	if (Matches2("REINDEX", "SYSTEM|DATABASE"))
@@ -2585,7 +2577,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
 	if (Matches2("SET", "CONSTRAINTS"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_constraints_with_schema,
+									  ADDLIST1("ALL"));
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
 	if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
@@ -2597,7 +2590,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
 	if (Matches3("SET", "SESSION", "AUTHORIZATION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+							   ADDLIST1("DEFAULT"));
 	/* Complete RESET SESSION with AUTHORIZATION */
 	if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
@@ -2623,10 +2617,10 @@ psql_completion_internal(const char *text, char **previous_words,
 			COMPLETE_WITH_LIST(my_list);
 		}
 		if (TailMatches2("search_path", "TO|="))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
-								" AND nspname not like 'pg\\_toast%%' "
-								" AND nspname not like 'pg\\_temp%%' "
-								" UNION SELECT 'DEFAULT' ");
+			COMPLETE_WITH_QUERY_KW(Query_for_list_of_schemas
+								   " AND nspname not like 'pg\\_toast%%' "
+								   " AND nspname not like 'pg\\_temp%%' ",
+								   ADDLIST1("DEFAULT"));
 		else
 		{
 			/* generic, type based, GUC support, guctype is malloc'ed */
@@ -2661,7 +2655,7 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* TABLE, but not TABLE embedded in other commands */
 	if (Matches1("TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations);
 
 /* TABLESAMPLE */
 	if (TailMatches1("TABLESAMPLE"))
@@ -2671,7 +2665,7 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* TRUNCATE */
 	if (Matches1("TRUNCATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 /* UNLISTEN */
 	if (Matches1("UNLISTEN"))
@@ -2680,13 +2674,13 @@ psql_completion_internal(const char *text, char **previous_words,
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
 	if (TailMatches1("UPDATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete UPDATE <table> with "SET" */
 	if (TailMatches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
 	/* Complete UPDATE <table> SET with list of attributes */
 	if (TailMatches3("UPDATE", MatchAny, "SET"))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+		COMPLETE_WITH_ATTR(prev2_wd);
 	/* UPDATE <table> SET <attr> = */
 	if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
@@ -2695,10 +2689,8 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
 	if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'CURRENT_USER'"
-							" UNION SELECT 'PUBLIC'"
-							" UNION SELECT 'USER'");
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+			ADDLIST3("CURRENT_USER", "PUBLIC", "USER"));
 	if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
 	if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
@@ -2711,29 +2703,26 @@ psql_completion_internal(const char *text, char **previous_words,
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
 	if (Matches1("VACUUM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'FULL'"
-								   " UNION SELECT 'FREEZE'"
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST4("FULL", "FREEZE",
+											   "ANALYZE", "VERBOSE"));
 	if (Matches2("VACUUM", "FULL|FREEZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST2("ANALYZE", "VERBOSE"));
 	if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("VERBOSE"));
 	if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("ANALYZE"));
 	if (Matches2("VACUUM", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("ANALYZE"));
 	if (Matches2("VACUUM", "ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("VERBOSE"));
 	if (HeadMatches1("VACUUM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 
 /* WITH [RECURSIVE] */
 
@@ -2747,21 +2736,21 @@ psql_completion_internal(const char *text, char **previous_words,
 /* ANALYZE */
 	/* Complete with list of tables */
 	if (Matches1("ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf);
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
 	if (TailMatches2(MatchAny, "WHERE"))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+		COMPLETE_WITH_ATTR(prev2_wd);
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
 	if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf);
 
 /* ... JOIN ... */
 	if (TailMatches1("JOIN"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf);
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
@@ -2778,13 +2767,13 @@ psql_completion_internal(const char *text, char **previous_words,
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	}
 	if (TailMatchesCS1("\\da*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates);
 	if (TailMatchesCS1("\\dA*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 	if (TailMatchesCS1("\\db*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	if (TailMatchesCS1("\\dD*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains);
 	if (TailMatchesCS1("\\des*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
 	if (TailMatchesCS1("\\deu*"))
@@ -2792,7 +2781,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatchesCS1("\\dew*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 	if (TailMatchesCS1("\\df*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 
 	if (TailMatchesCS1("\\dFd*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
@@ -2805,40 +2794,40 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
 
 	if (TailMatchesCS1("\\di*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	if (TailMatchesCS1("\\dL*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
 	if (TailMatchesCS1("\\dn*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
 	if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf);
 	if (TailMatchesCS1("\\ds*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
 	if (TailMatchesCS1("\\dt*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	if (TailMatchesCS1("\\dT*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 	if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	if (TailMatchesCS1("\\dv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	if (TailMatchesCS1("\\dx*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
 	if (TailMatchesCS1("\\dm*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 	if (TailMatchesCS1("\\dE*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables);
 	if (TailMatchesCS1("\\dy*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* must be at end of \d alternatives: */
 	if (TailMatchesCS1("\\d*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations);
 
 	if (TailMatchesCS1("\\ef"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 	if (TailMatchesCS1("\\ev"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 
 	if (TailMatchesCS1("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
@@ -2907,14 +2896,14 @@ psql_completion_internal(const char *text, char **previous_words,
 			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
 	}
 	if (TailMatchesCS1("\\sf*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 	if (TailMatchesCS1("\\sv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
 							"\\ir|\\include_relative|\\o|\\out|"
 							"\\s|\\w|\\write|\\lo_import"))
 	{
-		completion_charp = "\\";
+		SET_COMP_CHARP("\\");
 		return completion_matches(text, complete_from_files);
 	}
 
@@ -2934,8 +2923,7 @@ psql_completion_internal(const char *text, char **previous_words,
 				if (words_after_create[i].query)
 					COMPLETE_WITH_QUERY(words_after_create[i].query);
 				if (words_after_create[i].squery)
-					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
-											   NULL);
+					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery);
 				break;
 			}
 		}
@@ -3182,13 +3170,13 @@ _complete_from_query(int is_schema_query, const char *text, int state)
 							  char_length, e_text);
 
 			/* If an addon query was provided, use it */
-			if (completion_charp)
-				appendPQExpBuffer(&query_buffer, "\n%s", completion_charp);
+			if (COMPLETION_CHARP[0])
+				appendPQExpBuffer(&query_buffer, "\n%s", COMPLETION_CHARP);
 		}
 		else
 		{
 			/* completion_charp is an sprintf-style format string */
-			appendPQExpBuffer(&query_buffer, completion_charp,
+			appendPQExpBuffer(&query_buffer, COMPLETION_CHARP,
 							  char_length, e_text,
 							  e_info_charp, e_info_charp,
 							  e_info_charp2, e_info_charp2);
@@ -3303,18 +3291,17 @@ complete_from_list(const char *text, int state)
 static char *
 complete_from_const(const char *text, int state)
 {
-	Assert(completion_charp != NULL);
 	if (state == 0)
 	{
 		if (completion_case_sensitive)
-			return pg_strdup(completion_charp);
+			return pg_strdup(COMPLETION_CHARP);
 		else
 
 			/*
 			 * If case insensitive matching was requested initially, adjust
 			 * the case according to setting.
 			 */
-			return pg_strdup_keyword_case(completion_charp, text);
+			return pg_strdup_keyword_case(COMPLETION_CHARP, text);
 	}
 	else
 		return NULL;
@@ -3415,7 +3402,7 @@ complete_from_files(const char *text, int state)
 	if (state == 0)
 	{
 		/* Initialization: stash the unquoted input. */
-		unquoted_text = strtokx(text, "", NULL, "'", *completion_charp,
+		unquoted_text = strtokx(text, "", NULL, "'", COMPLETION_CHARP[0],
 								false, true, pset.encoding);
 		/* expect a NULL return for the empty string only */
 		if (!unquoted_text)
@@ -3436,7 +3423,7 @@ complete_from_files(const char *text, int state)
 		 * bother providing a macro to simplify this.
 		 */
 		ret = quote_if_needed(unquoted_match, " \t\r\n\"`",
-							  '\'', *completion_charp, pset.encoding);
+							  '\'', COMPLETION_CHARP[0], pset.encoding);
 		if (ret)
 			free(unquoted_match);
 		else
@@ -3480,6 +3467,39 @@ pg_strdup_keyword_case(const char *s, const char *ref)
 	return ret;
 }
 
+/* Construct codelet to append given keywords  */
+static char *
+additional_kw_query(const char *ref, int n, ...)
+{
+	va_list ap;
+	static PQExpBuffer qbuf = NULL;
+	int i;
+
+	if (qbuf == NULL)
+		qbuf = createPQExpBuffer();
+	else
+		resetPQExpBuffer(qbuf);
+
+	/* Construct an additional queriy to append keywords */
+	appendPQExpBufferStr(qbuf, " UNION ALL SELECT * FROM (VALUES ");
+
+	va_start(ap, n);
+	for (i = 0 ; i < n ; i++)
+	{
+		char *item = pg_strdup_keyword_case(va_arg(ap, char *), ref);
+		if (i > 0) appendPQExpBufferChar(qbuf, ',');
+		appendPQExpBufferStr(qbuf, "('");
+		appendPQExpBufferStr(qbuf, item);
+		appendPQExpBufferStr(qbuf, "')");
+		pg_free(item);
+	}
+	va_end(ap);
+
+	appendPQExpBufferStr(qbuf, ") as x");
+
+	return qbuf->data;
+}
+
 
 /*
  * escape_string - Escape argument for use as string literal.
-- 
2.9.2

0003-Introduce-word-shift-and-removal-feature-to-psql-com.patchtext/x-patch; charset=us-asciiDownload
From 41681eb716e2e86a7c00e7f34779981b6682bea7 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 14:22:53 +0900
Subject: [PATCH 03/12] Introduce word shift and removal feature to
 psql-completion

Currently completion of psql is sensitive to noise words such like
temp/temporary, unlogged or concurrent. Addition to that, schema
elemsnts in CREATE SCHEMA syntax or some recursive syntaxes are
processed in somewhat bogus way.  To deal with such cases in simpler
way, this patch introduces two features.

1. Add a feature to ignore leading words to process.  New macros
  HEAD_SHIFT, HEAD_SET to shift or set the position of the first word
  to match using other macros. All *MatchesN macros follow
  this. SHIFT_TO_LAST1 is a macro to shift the head to the position
  where the specified word is found last.

2. Add a feature to remove intermediate words from previous_words
  list.  COLLAPSE(s, n) macro removes n words from the s'th position
  (1-based). Removing "noise" words let the succeeding operations
  simple.

This patch doesn't make any behavioral change.
---
 src/bin/psql/tab-complete-macros.h | 186 +++++++++++++++++++++++++++++--------
 src/bin/psql/tab-complete.c        |  51 ++++++++--
 2 files changed, 190 insertions(+), 47 deletions(-)

diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
index 44fd547..f000a0c 100644
--- a/src/bin/psql/tab-complete-macros.h
+++ b/src/bin/psql/tab-complete-macros.h
@@ -25,41 +25,69 @@
 #define prev8_wd  (previous_words[7])
 #define prev9_wd  (previous_words[8])
 
+/* Return the number of stored words counting head shift */
+#define WORD_COUNT() (previous_words_count - head_shift)
+
 /*
  * Return the index in previous_words for index from the beginning. n is
  * 1-based and the result is 0-based.
  */
-#define HEAD_INDEX(n) \
-	(previous_words_count - (n))
+#define HEAD_INDEX(n) (WORD_COUNT() - (n))
+
+/* Move the position of the beginning word for matching macros.  */
+#define HEAD_SHIFT(n) (head_shift += (n))
+
+/* Set the position of the beginning word for matching macros.  */
+#define HEAD_SET(n) (head_shift = (n))
+
+/*
+ * remove n words from current shifted position. This moves entire the
+ * previous_words regardless of head_shift.
+ */
+#define COLLAPSE(s, n)							\
+	do { \
+		memmove(previous_words + HEAD_INDEX((s) + (n) - 1), \
+				previous_words + HEAD_INDEX((s) - 1), \
+				sizeof(char *) * \
+				(previous_words_count - HEAD_INDEX((s) - 1)));	\
+		previous_words_count -= (n); \
+	} while (0)
+
+/*
+ * Find the position where the specified word appears last and shift to there.
+ * The words before the position will be ignored ever after.
+ */
+#define SHIFT_TO_LAST1(p1) \
+	HEAD_SHIFT(find_last_index_of(p1, previous_words, previous_words_count))
 
 /*
  * Macros for matching the last N words before point, and after head_sift,
  * case-insensitively.
  */
 #define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches(p1, prev_wd))
 
 #define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd))
 
 #define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
+	(WORD_COUNT() >= 3 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd))
 
 #define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
+	(WORD_COUNT() >= 4 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
 	 word_matches(p4, prev4_wd))
 
 #define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
+	(WORD_COUNT() >= 5 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -67,7 +95,7 @@
 	 word_matches(p5, prev5_wd))
 
 #define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
+	(WORD_COUNT() >= 6 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -76,7 +104,7 @@
 	 word_matches(p6, prev6_wd))
 
 #define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
+	(WORD_COUNT() >= 7 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -86,7 +114,7 @@
 	 word_matches(p7, prev7_wd))
 
 #define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
+	(WORD_COUNT() >= 8 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -97,7 +125,7 @@
 	 word_matches(p8, prev8_wd))
 
 #define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
+	(WORD_COUNT() >= 9 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -113,10 +141,10 @@
 	 * head_shift, case-sensitively.
 	 */
 #define TailMatchesCS1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches_cs(p1, prev_wd))
 #define TailMatchesCS2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches_cs(p1, prev_wd) && \
 	 word_matches_cs(p2, prev2_wd))
 
@@ -125,31 +153,31 @@
 	 * case-insensitively.
 	 */
 #define Matches1(p1) \
-	(previous_words_count == 1 && \
+	(WORD_COUNT() == 1 && \
 	 TailMatches1(p1))
 #define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
+	(WORD_COUNT() == 2 && \
 	 TailMatches2(p1, p2))
 #define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
+	(WORD_COUNT() == 3 && \
 	 TailMatches3(p1, p2, p3))
 #define Matches4(p1, p2, p3, p4) \
-	(previous_words_count == 4 && \
+	(WORD_COUNT() == 4 && \
 	 TailMatches4(p1, p2, p3, p4))
 #define Matches5(p1, p2, p3, p4, p5) \
-	(previous_words_count == 5 && \
+	(WORD_COUNT() == 5 && \
 	 TailMatches5(p1, p2, p3, p4, p5))
 #define Matches6(p1, p2, p3, p4, p5, p6) \
-	(previous_words_count == 6 && \
+	(WORD_COUNT() == 6 && \
 	 TailMatches6(p1, p2, p3, p4, p5, p6))
 #define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(previous_words_count == 7 && \
+	(WORD_COUNT() == 7 && \
 	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
 #define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(previous_words_count == 8 && \
+	(WORD_COUNT() == 8 && \
 	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
 #define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(previous_words_count == 9 && \
+	(WORD_COUNT() == 9 && \
 	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
 
 /*
@@ -195,16 +223,39 @@
 	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
 	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]))
 
-#define MidMatches7(s,p1, p2, p3, p4, p5, p6, p7)	\
+#define MidMatches7(s,p1, p2, p3, p4, p5, p6, p7)			\
 	(HEAD_INDEX((s) + 6) >= 0 &&							\
-	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
-	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
-	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&			\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&		\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&			\
 	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
 	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
 	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&			\
 	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]))
 
+#define MidMatches8(s,p1, p2, p3, p4, p5, p6, p7, p8)		\
+	(HEAD_INDEX((s) + 7) >= 0 &&							\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&				\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&		\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&		\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&		\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&		\
+	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]) &&		\
+	 word_matches(p8, previous_words[HEAD_INDEX((s) + 7)]))
+
+#define MidMatches9(s,p1, p2, p3, p4, p5, p6, p7, p8, p9)		\
+	(HEAD_INDEX((s) + 8) >= 0 &&							\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&				\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&		\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&		\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&		\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&		\
+	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]) &&		\
+	 word_matches(p8, previous_words[HEAD_INDEX((s) + 7)]) &&		\
+	 word_matches(p9, previous_words[HEAD_INDEX((s) + 8)]))
+
 #define HeadMatches1(p1) \
 	MidMatches1(1, p1)
 #define HeadMatches2(p1, p2) \
@@ -219,6 +270,59 @@
 	MidMatches6(1, p1, p2, p3, p4, p5, p6)
 #define HeadMatches7(p1, p2, p3, p4, p5, p6, p7) \
 	MidMatches7(1, p1, p2, p3, p4, p5, p6, p7)
+#define HeadMatches8(p1, p2, p3, p4, p5, p6, p7, p8)	\
+	MidMatches8(1, p1, p2, p3, p4, p5, p6, p7, p8)
+#define HeadMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9)	\
+	MidMatches9(1, p1, p2, p3, p4, p5, p6, p7, p8, p9)
+
+#define HeadMatchAndRemove1(s, l, p1)			\
+	do {												  \
+		if (WORD_COUNT() >= s + l - 1 && HeadMatches1(p1)) \
+			COLLAPSE(s, l);								  \
+	} while (0)
+#define HeadMatchAndRemove2(s, l, p1, p2)		\
+	do {														\
+		if (WORD_COUNT() >= s + l - 1 && HeadMatches2(p1, p2))	\
+			COLLAPSE(s, l);										\
+	} while (0)
+#define HeadMatchAndRemove3(s, l, p1, p2, p3)							\
+	do {																\
+		if (WORD_COUNT() >= s + l - 1 && HeadMatches3(p1, p2, p3))		\
+			COLLAPSE(s, l);												\
+	} while (0)
+#define HeadMatchAndRemove4(s, l, p1, p2, p3, p4)	\
+	do {															  \
+		if (WORD_COUNT() >= s + l - 1 && HeadMatches4(p1, p2, p3, p4)) \
+			COLLAPSE(s, l);											  \
+	} while (0)
+#define HeadMatchAndRemove5(s, l, p1, p2, p3, p4, p5)					\
+	do {																\
+		if (WORD_COUNT() >= s + l - 1 && HeadMatches5(p1, p2, p3, p4, p5)) \
+			COLLAPSE(s, l);												\
+	} while (0)
+#define HeadMatchAndRemove6(s, l, p1, p2, p3, p4, p5, p6)				\
+	do {																\
+		if (WORD_COUNT() >= s + l - 1 && HeadMatches6(p1, p2, p3, p4, p5, p6)) \
+			COLLAPSE(s, l);												\
+	} while (0)
+#define HeadMatchAndRemove7(s, l, p1, p2, p3, p4, p5, p6, p7)			\
+	do {																\
+		if (WORD_COUNT() >= s + l - 1 &&									\
+			HeadMatches7(p1, p2, p3, p4, p5, p6, p7))					\
+			COLLAPSE(s, l);												\
+	} while (0)
+#define HeadMatchAndRemove8(s, l, p1, p2, p3, p4, p5, p6, p7, p8)		\
+	do {																\
+		if (WORD_COUNT() >= s + l - 1 &&									\
+			HeadMatches8(p1, p2, p3, p4, p5, p6, p7, p8))				\
+			COLLAPSE(s, l);												\
+	} while (0)
+#define HeadMatchAndRemove9(s, l, p1, p2, p3, p4, p5, p6, p7, p8, p9)	\
+	do {																\
+		if (WORD_COUNT() >= s + l - 1 &&									\
+			HeadMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))			\
+			COLLAPSE(s, l);												\
+	} while (0)
 
 /*
  * A few macros to ease typing. You can use these to complete the given
@@ -240,12 +344,6 @@
 
 #define COMPLETION_CHARP (completion_charp->data)
 
-#define COMPLETE_WITH_QUERY(query)				\
-do { \
-	SET_COMP_CHARP(query);	\
-	return completion_matches(text, complete_from_query);	\
-} while (0)
-
 /*
  * COMPLETE_WITH_QUERY with additional keywords. Keywords are complete
  * case-sensitively
@@ -257,11 +355,8 @@ do { \
 	return completion_matches(text, complete_from_query);	\
 } while (0)
 
-#define COMPLETE_WITH_SCHEMA_QUERY(query) \
-do { \
-	completion_squery = &(query); \
-	return completion_matches(text, complete_from_schema_query); \
-} while (0)
+#define COMPLETE_WITH_QUERY(query) COMPLETE_WITH_QUERY_KW((query), "")
+
 
 /*
  * COMPLETE_WITH_SCHEMA_QUERY with additional keywords. Keywords are complete
@@ -274,6 +369,8 @@ do { \
 	return completion_matches(text, complete_from_schema_query); \
 } while (0)
 
+#define COMPLETE_WITH_SCHEMA_QUERY(query) COMPLETE_WITH_SCHEMA_QUERY_KW((query), "")
+
 #define COMPLETE_WITH_LIST_CS(list) \
 do { \
 	completion_charpp = list; \
@@ -324,6 +421,8 @@ do { \
 
 #define COMPLETE_WITH_ATTR(query) COMPLETE_WITH_ATTR_KW((query), "")
 
+#define COMPLETE_WITH_ATTR(query) COMPLETE_WITH_ATTR_KW((query), "")
+
 #define COMPLETE_WITH_ENUM_VALUE(type) \
 do { \
 	char   *_completion_schema; \
@@ -471,4 +570,17 @@ do { \
 	additional_kw_query(text, 12, s1, s2, s3, s4, s5, s6, s7,		\
 						s8, s9, s10, s11, s12, s13, s14, s15)
 
+#define COMPLETE_THING(p) \
+do { \
+	const pgsql_thing_t *ent = find_thing_entry(previous_words[-(p) - 1]);	\
+	if (ent) \
+	{ \
+		if (ent->query) \
+			COMPLETE_WITH_QUERY(ent->query); \
+		else if (ent->squery) \
+			COMPLETE_WITH_SCHEMA_QUERY(*ent->squery); \
+	} \
+	return NULL; \
+} while (0)
+
 #endif   /* TAB_COMPLETE_MACROS_H */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 21eb7ab..f54ad40 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -794,6 +794,7 @@ static void append_variable_names(char ***varnames, int *nvars,
 static char **complete_from_variables(const char *text,
 					const char *prefix, const char *suffix, bool need_value);
 static char *complete_from_files(const char *text, int state);
+static int find_last_index_of(char *w, char **previous_words, int len);
 
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static char *additional_kw_query( const char *ref, int n, ...);
@@ -806,6 +807,7 @@ static char *get_guctype(const char *varname);
 
 static char **psql_completion_internal(const char *text, char **previous_words,
 										   int previous_words_count);
+static const pgsql_thing_t *find_thing_entry(char *word);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -1014,6 +1016,9 @@ static char **
 psql_completion_internal(const char *text, char **previous_words,
 						 int previous_words_count)
 {
+	/* The number of prefixing words to be ignored */
+	int			head_shift = 0;
+
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
@@ -2914,18 +2919,14 @@ psql_completion_internal(const char *text, char **previous_words,
 	 */
 	else
 	{
-		int			i;
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
 
-		for (i = 0; words_after_create[i].name; i++)
+		if (ent)
 		{
-			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
-			{
-				if (words_after_create[i].query)
-					COMPLETE_WITH_QUERY(words_after_create[i].query);
-				if (words_after_create[i].squery)
-					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery);
-				break;
-			}
+			if (ent->query)
+				COMPLETE_WITH_QUERY(ent->query);
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery);
 		}
 	}
 
@@ -3436,6 +3437,18 @@ complete_from_files(const char *text, int state)
 
 /* HELPER FUNCTIONS */
 
+/*
+ * Return the index (reverse to the index of previous_words) of the tailmost
+ * (topmost in the array) appearance of w.
+ */
+static int
+find_last_index_of(char *w, char **previous_words, int len)
+{
+	int i;
+
+	for (i = 0 ; i < len && !word_matches(w, previous_words[i]) ; i++);
+	return i < len ? (len - i - 1) : 0;
+}
 
 /*
  * Make a pg_strdup copy of s and convert the case according to
@@ -3715,6 +3728,24 @@ get_guctype(const char *varname)
 	return guctype;
 }
 
+/*
+ * Finds the entry in words_after_create[] that matches the word.
+ * NULL if not found.
+ */
+static const pgsql_thing_t *
+find_thing_entry(char *word)
+{
+	int			i;
+
+	for (i = 0; words_after_create[i].name; i++)
+	{
+		if (pg_strcasecmp(word, words_after_create[i].name) == 0)
+			return words_after_create + i;
+	}
+
+	return NULL;
+}
+
 #ifdef NOT_USED
 
 /*
-- 
2.9.2

0004-Allow-complete-schema-elements-in-more-natural-way.patchtext/x-patch; charset=us-asciiDownload
From 9c72fe4b22b96890aaeb87c3e695ac4cf68fcbdc Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 15:14:35 +0900
Subject: [PATCH 04/12] Allow complete schema elements in more natural way

Some syntaxes like CREATE TABLE|INDEX, GRANT or REVOKE can be schema
elements of CREATE SCHEMA. Currently Matches/HeadMatches cannot be
used in completing schema elements since they begin midst of input
words. It leads to unnatural difference on completions that cannot use
TailMatches.

This patch allows to shift the beginning of input words so that schema
elements can be completed in just the same way with bare syntax.
---
 src/bin/psql/tab-complete.c | 103 ++++++++++++++++++++++++++------------------
 1 file changed, 60 insertions(+), 43 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index f54ad40..23c8e9b 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1069,10 +1069,27 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
+	/*
+	 * If this is in CREATE SCHEMA, seek to the last CREATE and regard it as
+	 * current command to complete.
+	 */
+	if (HeadMatches2("CREATE", "SCHEMA"))
+		SHIFT_TO_LAST1("CREATE|GRANT|REVOKE");
+
 /* CREATE */
 	/* complete with something you can create */
 	if (Matches1("CREATE"))
-		return completion_matches(text, create_command_generator);
+	{
+		/*
+		 * If this is the top level CREATE, complete with all CREATable
+		 * objects
+		 */
+		if (head_shift == 0)
+			return completion_matches(text, create_command_generator);
+
+		/* schema_element allows only some kinds of objects */
+		COMPLETE_WITH_LIST5("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER");
+	}
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
@@ -1800,33 +1817,33 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
 
-	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	/* CREATE INDEX --- is allowed inside CREATE SCHEMA */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	if (TailMatches2("CREATE", "UNIQUE"))
+	if (Matches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
 	/*
 	 * If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY", and
 	 * existing indexes
 	 */
-	if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	if (Matches2("CREATE|UNIQUE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
 									  ADDLIST2("ON", "CONCURRENTLY"));
 	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
-			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
+	if (Matches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
+		Matches2("INDEX|CONCURRENTLY", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 
 	/*
 	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
 	 * indexes
 	 */
-	if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+	if (Matches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
 									  ADDLIST1("ON"));
 	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
-			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
+	if (Matches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
+		Matches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/*
@@ -1898,25 +1915,25 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
-/* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
-			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
+/* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA */
+	if (Matches3("CREATE", "SEQUENCE", MatchAny) ||
+		Matches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
-		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
+	if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
+		Matches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
 	if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
-/* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE TABLE --- is allowed inside CREATE SCHEMA */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	if (Matches2("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	if (TailMatches2("CREATE", "UNLOGGED"))
+	if (Matches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
 /* CREATE TABLESPACE */
@@ -1932,35 +1949,35 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
-/* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE TRIGGER --- is allowed inside CREATE SCHEMA */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	if (Matches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	if (Matches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	if (Matches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
-	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
+	if (Matches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+		Matches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
 	/*
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
 	 * tables
 	 */
-	if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
+	if (Matches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
-	if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
+	if (Matches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("ON", MatchAny))
 		COMPLETE_WITH_LIST7("NOT DEFERRABLE", "DEFERRABLE", "INITIALLY",
 							"REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
 	if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches1("DEFERRABLE") || TailMatches2("INITIALLY", "IMMEDIATE|DEFERRED")))
+		(TailMatches1("DEFERRABLE") || TailMatches2("INITIALLY", "IMMEDIATE|DEFERRED")))
 		COMPLETE_WITH_LIST4("REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
 	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("REFERENCING"))
 		COMPLETE_WITH_LIST2("OLD TABLE", "NEW TABLE");
@@ -2034,12 +2051,12 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
-/* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE VIEW --- is allowed inside CREATE SCHEMA */
 	/* Complete CREATE VIEW <name> with AS */
-	if (TailMatches3("CREATE", "VIEW", MatchAny))
+	if (Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
+	if (Matches4("CREATE", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
@@ -2238,9 +2255,9 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatches2("FOREIGN", "SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
 
-/* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* GRANT && REVOKE --- is allowed inside CREATE SCHEMA */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	if (TailMatches1("GRANT|REVOKE"))
+	if (Matches1("GRANT|REVOKE"))
 		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
 			ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
 					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
@@ -2250,11 +2267,11 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	if (TailMatches2("GRANT|REVOKE", MatchAny))
+	if (Matches2("GRANT|REVOKE", MatchAny))
 	{
 		if (TailMatches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
-		if (TailMatches2("GRANT", MatchAny))
+		if (Matches2("GRANT", MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2271,7 +2288,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
 	 */
-	if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
+	if (Matches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tsvmf,
 			   ADDLIST15("ALL FUNCTIONS IN SCHEMA",
 						 "ALL SEQUENCES IN SCHEMA",
@@ -2289,11 +2306,11 @@ psql_completion_internal(const char *text, char **previous_words,
 						 "TABLESPACE",
 						 "TYPE"));
 
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
+	if (Matches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
 							"TABLES IN SCHEMA");
 
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+	if (Matches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/*
@@ -2302,7 +2319,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 *
 	 * Complete "GRANT/REVOKE * ON *" with "TO/FROM".
 	 */
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
+	if (Matches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
 		if (TailMatches1("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
@@ -2343,27 +2360,27 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("FROM");
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
+	if (Matches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
 	{
-		if (TailMatches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (Matches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (Matches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 	{
-		if (TailMatches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (Matches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
+	if (Matches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
 	{
-		if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (Matches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
-- 
2.9.2

0005-Simpilfy-ALTER-TABLE-ALTER-COLUMN-completion.patchtext/x-patch; charset=us-asciiDownload
From 6921c552d9d6259d7fc0cbaf61e48d74525eecba Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 16:05:13 +0900
Subject: [PATCH 05/12] Simpilfy ALTER TABLE ALTER COLUMN completion

Simplify ALTER TABLE ALTER COLUMN by removing COLUMN before
successive completions.
---
 src/bin/psql/tab-complete.c | 17 +++++++----------
 1 file changed, 7 insertions(+), 10 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 23c8e9b..d88bb74 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1500,25 +1500,22 @@ psql_completion_internal(const char *text, char **previous_words,
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
 	}
+	/* Remove COLUMN just after ALTER */
+	HeadMatchAndRemove5(5, 1, "ALTER", "TABLE", MatchAny, "ALTER", "COLUMN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
-			 Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
 		COMPLETE_WITH_LIST4("TYPE", "SET", "RESET", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
-			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
 		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
-		 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
-	Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
 		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
-			 Matches8("ALTER", "TABLE", MatchAny, "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("DEFAULT", "NOT NULL");
 	if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
-- 
2.9.2

0006-Simplify-completion-for-CLUSTER-VERBOSE.patchtext/x-patch; charset=us-asciiDownload
From c34b0d4d673930e1b82b3cca0a96b0f4c8634ec4 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 16:19:46 +0900
Subject: [PATCH 06/12] Simplify completion for CLUSTER VERBOSE.

Simplify completion for CLUSTER command by removing VERBOSE.
---
 src/bin/psql/tab-complete.c | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d88bb74..76010b4 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1676,17 +1676,16 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
 									  ADDLIST1("VERBOSE"));
-	if (Matches2("CLUSTER", "VERBOSE"))
+	/* Remove VERBOSE for further completion */
+	HeadMatchAndRemove2(2, 1, "CLUSTER", "VERBOSE");
+
+	if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 	/* If we have CLUSTER <sth>, then add "USING" */
-	if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
-		COMPLETE_WITH_CONST("USING");
-	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	if (Matches3("CLUSTER", "VERBOSE", MatchAny))
+	if (Matches2("CLUSTER", MatchAny))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER <sth> USING, then add the index as well */
-	if (Matches3("CLUSTER", MatchAny, "USING") ||
-			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
+	if (Matches3("CLUSTER", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
-- 
2.9.2

0007-Simplify-completion-for-COPY.patchtext/x-patch; charset=us-asciiDownload
From 5ebc210b689abc636e34d7f45f804299a7a1002e Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 17:21:29 +0900
Subject: [PATCH 07/12] Simplify completion for COPY.

Simplify completion for COPY command by removing BINARY.
---
 src/bin/psql/tab-complete.c | 21 ++++++++++-----------
 1 file changed, 10 insertions(+), 11 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 76010b4..2414f47 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1734,39 +1734,38 @@ psql_completion_internal(const char *text, char **previous_words,
 /* COPY */
 
 	/*
+	 * Just ignore obsolte styled BINARY if exists.
+	 */
+	HeadMatchAndRemove2(2, 1, "COPY", "BINARY");
+	/*
 	 * If we have COPY, offer list of tables or "(" (Also cover the analogous
 	 * backslash command).
 	 */
 	if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
 									  ADDLIST1("("));
-	/* If we have COPY BINARY, complete with list of tables */
-	if (Matches2("COPY", "BINARY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
+	/* Don't suggest BINARY just after COPY. It is obsolete. */
+
 	/* If we have COPY (, complete it with legal commands */
 	if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
 	/* If we have COPY [BINARY] <sth>, complete it with "TO" or "FROM" */
-	if (Matches2("COPY|\\copy", MatchAny) ||
-			 Matches3("COPY", "BINARY", MatchAny))
+	if (Matches2("COPY|\\copy", MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "TO");
 	/* If we have COPY [BINARY] <sth> FROM|TO, complete with filename */
-	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
-			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
+	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO"))
 	{
 		SET_COMP_CHARP("");
 		return completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename */
-	if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
-			 Matches5("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
+	if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny))
 		COMPLETE_WITH_LIST6("BINARY", "OIDS", "DELIMITER", "NULL", "CSV",
 							"ENCODING");
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename CSV */
-	if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
-			 Matches6("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny, "CSV"))
+	if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV"))
 		COMPLETE_WITH_LIST5("HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE",
 							"FORCE NOT NULL");
 
-- 
2.9.2

0008-Simplify-completion-for-CREATE-INDEX.patchtext/x-patch; charset=us-asciiDownload
From 9f3278968700129eab13884a46f1e9f2ac56a604 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 17:45:13 +0900
Subject: [PATCH 08/12] Simplify completion for CREATE INDEX

CREATE INDEX has a rather complex syntx but removing some noise words
can simplify the completions. It also enables to use Matches instead
of TailMatches forced by a difficulty of matching. That gets rid of
the possibility of false matches.
---
 src/bin/psql/tab-complete.c | 54 +++++++++++++++++++++++++--------------------
 1 file changed, 30 insertions(+), 24 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2414f47..08fe2cb 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1816,53 +1816,59 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* First off we complete CREATE UNIQUE with "INDEX" */
 	if (Matches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
+	/* Remove UNIQUE for further completion */
+	HeadMatchAndRemove3(2, 1, "CREATE", "UNIQUE", "INDEX");
 
 	/*
 	 * If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY", and
 	 * existing indexes
 	 */
-	if (Matches2("CREATE|UNIQUE", "INDEX"))
+	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
 									  ADDLIST2("ON", "CONCURRENTLY"));
-	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	if (Matches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
-		Matches2("INDEX|CONCURRENTLY", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
+	/* Remove CONCURRENTLY for further completion */
+	HeadMatchAndRemove3(3, 1, "CREATE", "INDEX", "CONCURRENTLY");
 
 	/*
-	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
+	 * Complete CREATE [UNIQUE] INDEX [CONCURRENTLY] with "ON" and existing
 	 * indexes
 	 */
-	if (Matches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
 									  ADDLIST1("ON"));
-	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	if (Matches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
-		Matches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
+
+	/* Complete CREATE [UNIQUE] INDEX [CONCURRENTLY] <sth> with "ON" */
+	if (Matches3("CREATE", "INDEX", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
+	/* Complete CREATE INDEX [<name>] ON with a list of tables  */
+	if (Matches4("CREATE", "INDEX", MatchAny, "ON") ||
+		Matches3("CREATE", "INDEX", "ON"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
+
 	/*
 	 * Complete INDEX <name> ON <table> with a list of table columns (which
-	 * should really be in parens)
+	 * should really be in parens). Index name is still needed.
 	 */
-	if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-		TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+	if (Matches5("CREATE", "INDEX", MatchAny, "ON", MatchAny) ||
+		Matches4("CREATE", "INDEX", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-		TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
+	if (Matches6("CREATE", "INDEX", MatchAny, "ON", MatchAny, "(") ||
+		Matches5("CREATE", "INDEX", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd);
-	/* same if you put in USING */
-	if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev4_wd);
+	/* index name is no longer necessary */
+	HeadMatchAndRemove4(3, 1, "CREATE", "INDEX", MatchAny, "ON");
+
 	/* Complete USING with an index method */
-	if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
-			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
-			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
+	if (Matches5("CREATE", "INDEX", "ON", MatchAny, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
-			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
-			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
+	/* Complete USING index_method with ( */
+	if (Matches6("CREATE", "INDEX", "ON", MatchAny, "USING", MatchAny))
 		COMPLETE_WITH_CONST("(");
+	/* complete using with column name */
+	if (Matches7("CREATE", "INDEX", "ON", MatchAny, "USING", MatchAny, "("))
+		COMPLETE_WITH_ATTR(prev4_wd);
+
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-- 
2.9.2

0009-Simplify-completion-for-CREATE-SEQUENCE.patchtext/x-patch; charset=us-asciiDownload
From 45e1fa4ad804d5bf0a96a56ed68afad770863c69 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 17:56:45 +0900
Subject: [PATCH 09/12] Simplify completion for CREATE SEQUENCE.

Simplify completion for CREATE SEQUENCE by removing TEMPORARY.
---
 src/bin/psql/tab-complete.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 08fe2cb..c19aac2 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1917,12 +1917,11 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 /* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA */
-	if (Matches3("CREATE", "SEQUENCE", MatchAny) ||
-		Matches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
+	HeadMatchAndRemove3(2, 1, "CREATE", "TEMP|TEMPORARY", "SEQUENCE");
+	if (Matches3("CREATE", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
-		Matches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
+	if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
-- 
2.9.2

0010-Simplify-completion-for-DROP-INDEX.patchtext/x-patch; charset=us-asciiDownload
From f02f9abf76f78045a36e5e1268657253085f744c Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 18:14:18 +0900
Subject: [PATCH 10/12] Simplify completion for DROP INDEX.

Simplify completion for DROP INDEX by removing CONCURRENTLY.
---
 src/bin/psql/tab-complete.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index c19aac2..413598e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2132,12 +2132,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
 									  ADDLIST1("CONCURRENTLY"));
-	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
+	/* Remove CONCURRENTLY */
+	HeadMatchAndRemove3(3, 1, "DROP", "INDEX", "CONCURRENTLY");
 	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
-		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
 	if (Matches2("DROP", "MATERIALIZED"))
-- 
2.9.2

0011-Allow-completing-the-body-of-EXPLAIN.patchtext/x-patch; charset=us-asciiDownload
From fd0fd3df2bcc6f36c079adeb9e70fcd7bdff58c5 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 18:30:43 +0900
Subject: [PATCH 11/12] Allow completing the body of EXPLAIN

This allows compleition for the body commands of EXPLAIN by
recursively call psql_comppletion_internal after ripping off the outer
command.
---
 src/bin/psql/tab-complete.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 413598e..7fc4965 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2217,6 +2217,14 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("EXPLAIN", "VERBOSE") ||
 			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
 		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
+	HeadMatchAndRemove2(2, 1, "EXPLAIN", "ANALZYE");
+	HeadMatchAndRemove2(2, 1, "EXPLAIN", "VERVOSE");
+	/* complete for individual syntaxes */
+	if (Matches2("EXPLAIN", "SELECT|INSERT|DELETE|UPDATE|DECLARE"))
+	{
+		COLLAPSE(1, 1);
+		return psql_completion_internal(text, previous_words, WORD_COUNT());
+	}
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-- 
2.9.2

0012-Allow-CREATE-RULE-to-use-command-completion-recursiv.patchtext/x-patch; charset=us-asciiDownload
From 7ac44be52f4432c9c1699794055ed433dc291fe5 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 18:59:02 +0900
Subject: [PATCH 12/12] Allow CREATE RULE to use command completion recursively

A trial implement of CREATE RULE completing after DO.  This allows
commands not to use TailMatches. But seems to require more
consideration.
---
 src/bin/psql/tab-complete.c | 54 ++++++++++++++++++++++++++++++++++-----------
 1 file changed, 41 insertions(+), 13 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 7fc4965..7995a23 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1916,6 +1916,34 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
+	/* Recursive completion for inner command of CREATE RULE */
+	if (HeadMatches2("CREATE", "RULE")){
+		int idx =
+			find_last_index_of("DO", previous_words, previous_words_count);
+		if (idx > 0)
+		{
+			COLLAPSE(1, idx); /* Start from "DO" */
+			if (Matches1("DO"))
+				COMPLETE_WITH_LIST8("ALSO", "INSTEAD", "NOTHING",
+									"SELECT", "INSERT", "UPDATE", "DELETE",
+									"NOTIFY");
+			HeadMatchAndRemove2(2, 1, "DO", "ALSO|INSTEAD");
+			if (Matches1("DO"))
+				COMPLETE_WITH_LIST6("NOTHING",
+									"SELECT", "INSERT", "UPDATE", "DELETE",
+									"NOTIFY");
+			idx =
+				find_last_index_of("SELECT|UPDATE|INSERT|DELETE",
+								   previous_words, previous_words_count);
+			if (idx > 0) {
+				COLLAPSE(1, idx);
+				return psql_completion_internal(text, previous_words,
+												previous_words_count);
+			}
+		}
+	}
+
+
 /* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA */
 	HeadMatchAndRemove3(2, 1, "CREATE", "TEMP|TEMPORARY", "SEQUENCE");
 	if (Matches3("CREATE", "SEQUENCE", MatchAny))
@@ -2091,10 +2119,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches1("DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
-	if (TailMatches2("DELETE", "FROM"))
+	if (Matches2("DELETE", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete DELETE FROM <table> */
-	if (TailMatches3("DELETE", "FROM", MatchAny))
+	if (Matches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
 	/* XXX: implement tab completion for DELETE ... USING */
 
@@ -2404,32 +2432,32 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* INSERT --- can be inside EXPLAIN, RULE, etc */
 	/* Complete INSERT with "INTO" */
-	if (TailMatches1("INSERT"))
+	if (Matches1("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
-	if (TailMatches2("INSERT", "INTO"))
+	if (Matches2("INSERT", "INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete "INSERT INTO <table> (" with attribute names */
-	if (TailMatches4("INSERT", "INTO", MatchAny, "("))
+	if (Matches4("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd);
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES"
 	 */
-	if (TailMatches3("INSERT", "INTO", MatchAny))
+	if (Matches3("INSERT", "INTO", MatchAny))
 		COMPLETE_WITH_LIST5("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES");
 
 	/*
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE"
 	 */
-	if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
+	if (Matches4("INSERT", "INTO", MatchAny, MatchAny) &&
 			 ends_with(prev_wd, ')'))
 		COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES");
 
 	/* Insert an open parenthesis after "VALUES" */
-	if (TailMatches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
+	if (Matches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
 		COMPLETE_WITH_CONST("(");
 
 /* LOCK */
@@ -2468,7 +2496,7 @@ psql_completion_internal(const char *text, char **previous_words,
 							"UPDATE EXCLUSIVE MODE");
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
-	if (TailMatches1("NOTIFY"))
+	if (Matches1("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
 
 /* OPTIONS */
@@ -2701,16 +2729,16 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
-	if (TailMatches1("UPDATE"))
+	if (Matches1("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete UPDATE <table> with "SET" */
-	if (TailMatches2("UPDATE", MatchAny))
+	if (Matches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
 	/* Complete UPDATE <table> SET with list of attributes */
-	if (TailMatches3("UPDATE", MatchAny, "SET"))
+	if (Matches3("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_ATTR(prev2_wd);
 	/* UPDATE <table> SET <attr> = */
-	if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
+	if (Matches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-- 
2.9.2

#58Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#57)
17 attachment(s)
Re: IF (NOT) EXISTS in psql-completion

Thanks for reviewing but I ran out of time for this CF..

I'm going to move this to the next CF.

I splitted the patch into small pieces. f3fd531 conflicted to
this so rebased onto the current master HEAD.

0001 is psql_completion refactoring.
0002-0003 are patches prividing new infrastructures.
0004 is README for the infrastructures.
0005 is letter-case correction of SET/RESET/SHOW using 0002.
0006-0008 are improvements of recursive syntaxes using 0001 and 0004.
0009-0016 are simplifying (maybe) completion code per syntax.

The last one (0017) is the IF(NOT)EXIST modifications. It
suggests if(not)exists for syntaxes already gets object
suggestion. So some kind of objects like operator, cast and so
don't get an if.. suggestion. Likewise, I intentionally didn't
modified siggestions for "TEXT SEARCH *".

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

0012-Simplify-completion-for-CREATE-INDEX.patchtext/x-patch; charset=us-asciiDownload
From 590a0b6faaeaf85f68b3905d7395496d5a058a85 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 17:45:13 +0900
Subject: [PATCH 12/17] Simplify completion for CREATE INDEX

CREATE INDEX has a rather complex syntx but removing some noise words
can simplify the completions. It also enables to use Matches instead
of TailMatches forced by a difficulty of matching. That gets rid of
the possibility of false matches.
---
 src/bin/psql/tab-complete.c | 54 +++++++++++++++++++++++++--------------------
 1 file changed, 30 insertions(+), 24 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 984efc0..456cd87 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1843,53 +1843,59 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* First off we complete CREATE UNIQUE with "INDEX" */
 	if (Matches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
+	/* Remove UNIQUE for further completion */
+	HeadMatchAndRemove3(2, 1, "CREATE", "UNIQUE", "INDEX");
 
 	/*
 	 * If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY", and
 	 * existing indexes
 	 */
-	if (Matches2("CREATE|UNIQUE", "INDEX"))
+	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
 									  ADDLIST2("ON", "CONCURRENTLY"));
-	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	if (Matches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
-		Matches2("INDEX|CONCURRENTLY", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
+	/* Remove CONCURRENTLY for further completion */
+	HeadMatchAndRemove3(3, 1, "CREATE", "INDEX", "CONCURRENTLY");
 
 	/*
-	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
+	 * Complete CREATE [UNIQUE] INDEX [CONCURRENTLY] with "ON" and existing
 	 * indexes
 	 */
-	if (Matches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
 									  ADDLIST1("ON"));
-	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	if (Matches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
-		Matches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
+
+	/* Complete CREATE [UNIQUE] INDEX [CONCURRENTLY] <sth> with "ON" */
+	if (Matches3("CREATE", "INDEX", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
+	/* Complete CREATE INDEX [<name>] ON with a list of tables  */
+	if (Matches4("CREATE", "INDEX", MatchAny, "ON") ||
+		Matches3("CREATE", "INDEX", "ON"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
+
 	/*
 	 * Complete INDEX <name> ON <table> with a list of table columns (which
-	 * should really be in parens)
+	 * should really be in parens). Index name is still needed.
 	 */
-	if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-		TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+	if (Matches5("CREATE", "INDEX", MatchAny, "ON", MatchAny) ||
+		Matches4("CREATE", "INDEX", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-		TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
+	if (Matches6("CREATE", "INDEX", MatchAny, "ON", MatchAny, "(") ||
+		Matches5("CREATE", "INDEX", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd);
-	/* same if you put in USING */
-	if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev4_wd);
+	/* index name is no longer necessary */
+	HeadMatchAndRemove4(3, 1, "CREATE", "INDEX", MatchAny, "ON");
+
 	/* Complete USING with an index method */
-	if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
-			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
-			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
+	if (Matches5("CREATE", "INDEX", "ON", MatchAny, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
-			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
-			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
+	/* Complete USING index_method with ( */
+	if (Matches6("CREATE", "INDEX", "ON", MatchAny, "USING", MatchAny))
 		COMPLETE_WITH_CONST("(");
+	/* complete using with column name */
+	if (Matches7("CREATE", "INDEX", "ON", MatchAny, "USING", MatchAny, "("))
+		COMPLETE_WITH_ATTR(prev4_wd);
+
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-- 
2.9.2

0001-Refactoring-tab-complete-to-make-psql_completion-cod.patchtext/x-patch; charset=us-asciiDownload
From a268abf54e95c8ab15c1c4563f921125c8616f14 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Wed, 14 Sep 2016 11:59:04 +0900
Subject: [PATCH 01/17] Refactoring tab-complete to make psql_completion code
 simpler.

psql_completion consists of a long-long sequence of "else-if"s and
succeeding finishing code. This structure enforces us to use a very
tricky way to put something other than matching-completion pairs in
the midst of the sequence. This patch separates the
matching-completion pairs into individual function
"psql_completion_internal". Returning at the points of completion let
us free from the else-if sequence and let us put anything we want into
the matching-completion sequence.

Addition to that the amount of the code seems to be a good reason to
move it out of tab-complete.c. This patch searates it out as
tab-complete-macros.h
---
 src/bin/psql/Makefile              |    2 +
 src/bin/psql/tab-complete-macros.h |  430 +++++++++++
 src/bin/psql/tab-complete.c        | 1452 ++++++++++++++----------------------
 3 files changed, 998 insertions(+), 886 deletions(-)
 create mode 100644 src/bin/psql/tab-complete-macros.h

diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index 1f6a289..51f88ba 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -47,6 +47,8 @@ ifeq ($(GCC),yes)
 psqlscanslash.o: CFLAGS += -Wno-error
 endif
 
+tab-complete.o: tab-complete-macros.h
+
 distprep: sql_help.h psqlscanslash.c
 
 install: all installdirs
diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
new file mode 100644
index 0000000..97ffcd1
--- /dev/null
+++ b/src/bin/psql/tab-complete-macros.h
@@ -0,0 +1,430 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2016, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/tab-complete-macros.h
+ */
+#ifndef TAB_COMPLETE_MACROS_H
+#define TAB_COMPLETE_MACROS_H
+
+/*
+ * For compactness, we use these macros to reference previous_words[].
+ * Caution: do not access a previous_words[] entry without having checked
+ * previous_words_count to be sure it's valid.  In most cases below, that
+ * check is implicit in a TailMatches() or similar macro, but in some places
+ * we have to check it explicitly.
+ */
+#define prev_wd   (previous_words[0])
+#define prev2_wd  (previous_words[1])
+#define prev3_wd  (previous_words[2])
+#define prev4_wd  (previous_words[3])
+#define prev5_wd  (previous_words[4])
+#define prev6_wd  (previous_words[5])
+#define prev7_wd  (previous_words[6])
+#define prev8_wd  (previous_words[7])
+#define prev9_wd  (previous_words[8])
+
+/*
+ * Return the index in previous_words for index from the beginning. n is
+ * 1-based and the result is 0-based.
+ */
+#define HEAD_INDEX(n) \
+	(previous_words_count - (n))
+
+/*
+ * Macros for matching the last N words before point, and after head_sift,
+ * case-insensitively.
+ */
+#define TailMatches1(p1) \
+	(previous_words_count >= 1 && \
+	 word_matches(p1, prev_wd))
+
+#define TailMatches2(p2, p1) \
+	(previous_words_count >= 2 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd))
+
+#define TailMatches3(p3, p2, p1) \
+	(previous_words_count >= 3 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd))
+
+#define TailMatches4(p4, p3, p2, p1) \
+	(previous_words_count >= 4 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd))
+
+#define TailMatches5(p5, p4, p3, p2, p1) \
+	(previous_words_count >= 5 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd))
+
+#define TailMatches6(p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 6 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd))
+
+#define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 7 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd))
+
+#define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 8 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd) && \
+	 word_matches(p8, prev8_wd))
+
+#define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
+	(previous_words_count >= 9 && \
+	 word_matches(p1, prev_wd) && \
+	 word_matches(p2, prev2_wd) && \
+	 word_matches(p3, prev3_wd) && \
+	 word_matches(p4, prev4_wd) && \
+	 word_matches(p5, prev5_wd) && \
+	 word_matches(p6, prev6_wd) && \
+	 word_matches(p7, prev7_wd) && \
+	 word_matches(p8, prev8_wd) && \
+	 word_matches(p9, prev9_wd))
+
+	/*
+	 * Macros for matching the last N words before point, and after
+	 * head_shift, case-sensitively.
+	 */
+#define TailMatchesCS1(p1) \
+	(previous_words_count >= 1 && \
+	 word_matches_cs(p1, prev_wd))
+#define TailMatchesCS2(p2, p1) \
+	(previous_words_count >= 2 && \
+	 word_matches_cs(p1, prev_wd) && \
+	 word_matches_cs(p2, prev2_wd))
+
+	/*
+	 * Macros for matching N words exactly to the line, and after head_shift,
+	 * case-insensitively.
+	 */
+#define Matches1(p1) \
+	(previous_words_count == 1 && \
+	 TailMatches1(p1))
+#define Matches2(p1, p2) \
+	(previous_words_count == 2 && \
+	 TailMatches2(p1, p2))
+#define Matches3(p1, p2, p3) \
+	(previous_words_count == 3 && \
+	 TailMatches3(p1, p2, p3))
+#define Matches4(p1, p2, p3, p4) \
+	(previous_words_count == 4 && \
+	 TailMatches4(p1, p2, p3, p4))
+#define Matches5(p1, p2, p3, p4, p5) \
+	(previous_words_count == 5 && \
+	 TailMatches5(p1, p2, p3, p4, p5))
+#define Matches6(p1, p2, p3, p4, p5, p6) \
+	(previous_words_count == 6 && \
+	 TailMatches6(p1, p2, p3, p4, p5, p6))
+#define Matches7(p1, p2, p3, p4, p5, p6, p7) \
+	(previous_words_count == 7 && \
+	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
+#define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
+	(previous_words_count == 8 && \
+	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
+#define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
+	(previous_words_count == 9 && \
+	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
+
+/*
+ * Macros for matching N words after head_shift + sth in the line, regardless
+ * of what is after them, case-insensitively.
+ */
+#define MidMatches1(s, p1) \
+	(HEAD_INDEX(s) >=0 && \
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]))
+
+#define MidMatches2(s, p1, p2) \
+	(HEAD_INDEX((s) + 1) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]))
+
+#define MidMatches3(s, p1, p2, p3) \
+	(HEAD_INDEX((s) + 2) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]))
+
+#define MidMatches4(s, p1, p2, p3, p4) \
+	(HEAD_INDEX((s) + 3) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) && \
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) && \
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) && \
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]))
+
+#define MidMatches5(s, p1, p2, p3, p4, p5) \
+	(HEAD_INDEX((s) + 4) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&	\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&		\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]))
+
+#define MidMatches6(s,p1, p2, p3, p4, p5, p6)		\
+	(HEAD_INDEX((s) + 5) >= 0 &&						\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]))
+
+#define MidMatches7(s,p1, p2, p3, p4, p5, p6, p7)	\
+	(HEAD_INDEX((s) + 6) >= 0 &&							\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&			\
+	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]))
+
+#define HeadMatches1(p1) \
+	MidMatches1(1, p1)
+#define HeadMatches2(p1, p2) \
+	MidMatches2(1, p1, p2)
+#define HeadMatches3(p1, p2, p3) \
+	MidMatches3(1, p1, p2, p3)
+#define HeadMatches4(p1, p2, p3, p4) \
+	MidMatches4(1, p1, p2, p3, p4)
+#define HeadMatches5(p1, p2, p3, p4, p5) \
+	MidMatches5(1, p1, p2, p3, p4, p5)
+#define HeadMatches6(p1, p2, p3, p4, p5, p6) \
+	MidMatches6(1, p1, p2, p3, p4, p5, p6)
+#define HeadMatches7(p1, p2, p3, p4, p5, p6, p7) \
+	MidMatches7(1, p1, p2, p3, p4, p5, p6, p7)
+
+/*
+ * A few macros to ease typing. You can use these to complete the given
+ * string with
+ * 1) The results from a query you pass it. (Perhaps one of those below?)
+ * 2) The results from a schema query you pass it.
+ * 3) The items from a null-pointer-terminated list (with or without
+ *	  case-sensitive comparison; see also COMPLETE_WITH_LISTn, below).
+ * 4) A string constant.
+ * 5) The list of attributes of the given table (possibly schema-qualified).
+ * 6) The list of arguments to the given function (possibly schema-qualified).
+ */
+#define COMPLETE_WITH_QUERY(query) \
+do { \
+	completion_charp = query;	\
+	return completion_matches(text, complete_from_query);	\
+} while (0)
+
+#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
+do { \
+	completion_squery = &(query); \
+	completion_charp = addon; \
+	return completion_matches(text, complete_from_schema_query); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS(list) \
+do { \
+	completion_charpp = list; \
+	completion_case_sensitive = true; \
+	return completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST(list) \
+do { \
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	return completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_CONST(string) \
+do { \
+	completion_charp = string;	\
+	completion_case_sensitive = false; \
+	return completion_matches(text, complete_from_const);	\
+} while (0)
+
+#define COMPLETE_WITH_ATTR(relation, addon) \
+do { \
+	char   *_completion_schema; \
+	char   *_completion_table; \
+\
+	_completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \
+								 false, false, pset.encoding); \
+	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+				   false, false, pset.encoding); \
+	_completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+								false, false, pset.encoding); \
+	if (_completion_table == NULL) \
+	{ \
+		completion_charp = Query_for_list_of_attributes addon; \
+		completion_info_charp = relation; \
+	} \
+	else \
+	{ \
+		completion_charp = Query_for_list_of_attributes_with_schema addon; \
+		completion_info_charp = _completion_table; \
+		completion_info_charp2 = _completion_schema; \
+	} \
+	return completion_matches(text, complete_from_query); \
+} while (0)
+
+#define COMPLETE_WITH_ENUM_VALUE(type) \
+do { \
+	char   *_completion_schema; \
+	char   *_completion_type; \
+\
+	_completion_schema = strtokx(type, " \t\n\r", ".", "\"", 0, \
+								 false, false, pset.encoding); \
+	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+				   false, false, pset.encoding); \
+	_completion_type = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+							   false, false, pset.encoding);  \
+	if (_completion_type == NULL)\
+	{ \
+		completion_charp = Query_for_list_of_enum_values;	\
+		completion_info_charp = type; \
+	} \
+	else \
+	{ \
+		completion_charp = Query_for_list_of_enum_values_with_schema;	\
+		completion_info_charp = _completion_type; \
+		completion_info_charp2 = _completion_schema; \
+	} \
+	return completion_matches(text, complete_from_query);	\
+} while (0)
+
+#define COMPLETE_WITH_FUNCTION_ARG(function) \
+do { \
+	char   *_completion_schema; \
+	char   *_completion_function; \
+\
+	_completion_schema = strtokx(function, " \t\n\r", ".", "\"", 0, \
+								 false, false, pset.encoding); \
+	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+				   false, false, pset.encoding); \
+	_completion_function = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+								   false, false, pset.encoding); \
+	if (_completion_function == NULL) \
+	{ \
+		completion_charp = Query_for_list_of_arguments; \
+		completion_info_charp = function; \
+	} \
+	else \
+	{ \
+		completion_charp = Query_for_list_of_arguments_with_schema;	\
+		completion_info_charp = _completion_function; \
+		completion_info_charp2 = _completion_schema; \
+	} \
+	return completion_matches(text, complete_from_query); \
+} while (0)
+
+/*
+ * These macros simplify use of COMPLETE_WITH_LIST for short, fixed lists.
+ * There is no COMPLETE_WITH_LIST1; use COMPLETE_WITH_CONST for that case.
+ */
+#define COMPLETE_WITH_LIST2(s1, s2) \
+do { \
+	static const char *const list[] = { s1, s2, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST3(s1, s2, s3) \
+do { \
+	static const char *const list[] = { s1, s2, s3, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST4(s1, s2, s3, s4) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST5(s1, s2, s3, s4, s5) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST6(s1, s2, s3, s4, s5, s6) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST7(s1, s2, s3, s4, s5, s6, s7) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST8(s1, s2, s3, s4, s5, s6, s7, s8) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST9(s1, s2, s3, s4, s5, s6, s7, s8, s9) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST10(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, NULL }; \
+	COMPLETE_WITH_LIST(list); \
+} while (0)
+
+/*
+ * Likewise for COMPLETE_WITH_LIST_CS.
+ */
+#define COMPLETE_WITH_LIST_CS2(s1, s2) \
+do { \
+	static const char *const list[] = { s1, s2, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS3(s1, s2, s3) \
+do { \
+	static const char *const list[] = { s1, s2, s3, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS4(s1, s2, s3, s4) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST_CS5(s1, s2, s3, s4, s5) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
+} while (0)
+
+
+
+#endif   /* TAB_COMPLETE_MACROS_H */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 02c8d60..95587f7 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -34,6 +34,7 @@
 
 #include "postgres_fe.h"
 #include "tab-complete.h"
+#include "tab-complete-macros.h"
 #include "input.h"
 
 /* If we don't have this, we might as well forget about the whole thing: */
@@ -133,211 +134,6 @@ static const SchemaQuery *completion_squery;	/* to pass a SchemaQuery */
 static bool completion_case_sensitive;	/* completion is case sensitive */
 
 /*
- * A few macros to ease typing. You can use these to complete the given
- * string with
- * 1) The results from a query you pass it. (Perhaps one of those below?)
- * 2) The results from a schema query you pass it.
- * 3) The items from a null-pointer-terminated list (with or without
- *	  case-sensitive comparison; see also COMPLETE_WITH_LISTn, below).
- * 4) A string constant.
- * 5) The list of attributes of the given table (possibly schema-qualified).
- * 6/ The list of arguments to the given function (possibly schema-qualified).
- */
-#define COMPLETE_WITH_QUERY(query) \
-do { \
-	completion_charp = query; \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
-do { \
-	completion_squery = &(query); \
-	completion_charp = addon; \
-	matches = completion_matches(text, complete_from_schema_query); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS(list) \
-do { \
-	completion_charpp = list; \
-	completion_case_sensitive = true; \
-	matches = completion_matches(text, complete_from_list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST(list) \
-do { \
-	completion_charpp = list; \
-	completion_case_sensitive = false; \
-	matches = completion_matches(text, complete_from_list); \
-} while (0)
-
-#define COMPLETE_WITH_CONST(string) \
-do { \
-	completion_charp = string; \
-	completion_case_sensitive = false; \
-	matches = completion_matches(text, complete_from_const); \
-} while (0)
-
-#define COMPLETE_WITH_ATTR(relation, addon) \
-do { \
-	char   *_completion_schema; \
-	char   *_completion_table; \
-\
-	_completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-								false, false, pset.encoding); \
-	if (_completion_table == NULL) \
-	{ \
-		completion_charp = Query_for_list_of_attributes  addon; \
-		completion_info_charp = relation; \
-	} \
-	else \
-	{ \
-		completion_charp = Query_for_list_of_attributes_with_schema  addon; \
-		completion_info_charp = _completion_table; \
-		completion_info_charp2 = _completion_schema; \
-	} \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-#define COMPLETE_WITH_ENUM_VALUE(type) \
-do { \
-	char   *_completion_schema; \
-	char   *_completion_type; \
-\
-	_completion_schema = strtokx(type, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_type = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-							   false, false, pset.encoding);  \
-	if (_completion_type == NULL)\
-	{ \
-		completion_charp = Query_for_list_of_enum_values; \
-		completion_info_charp = type; \
-	} \
-	else \
-	{ \
-		completion_charp = Query_for_list_of_enum_values_with_schema; \
-		completion_info_charp = _completion_type; \
-		completion_info_charp2 = _completion_schema; \
-	} \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-#define COMPLETE_WITH_FUNCTION_ARG(function) \
-do { \
-	char   *_completion_schema; \
-	char   *_completion_function; \
-\
-	_completion_schema = strtokx(function, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_function = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-								   false, false, pset.encoding); \
-	if (_completion_function == NULL) \
-	{ \
-		completion_charp = Query_for_list_of_arguments; \
-		completion_info_charp = function; \
-	} \
-	else \
-	{ \
-		completion_charp = Query_for_list_of_arguments_with_schema; \
-		completion_info_charp = _completion_function; \
-		completion_info_charp2 = _completion_schema; \
-	} \
-	matches = completion_matches(text, complete_from_query); \
-} while (0)
-
-/*
- * These macros simplify use of COMPLETE_WITH_LIST for short, fixed lists.
- * There is no COMPLETE_WITH_LIST1; use COMPLETE_WITH_CONST for that case.
- */
-#define COMPLETE_WITH_LIST2(s1, s2) \
-do { \
-	static const char *const list[] = { s1, s2, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST3(s1, s2, s3) \
-do { \
-	static const char *const list[] = { s1, s2, s3, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST4(s1, s2, s3, s4) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST5(s1, s2, s3, s4, s5) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST6(s1, s2, s3, s4, s5, s6) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST7(s1, s2, s3, s4, s5, s6, s7) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST8(s1, s2, s3, s4, s5, s6, s7, s8) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST9(s1, s2, s3, s4, s5, s6, s7, s8, s9) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST10(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-/*
- * Likewise for COMPLETE_WITH_LIST_CS.
- */
-#define COMPLETE_WITH_LIST_CS2(s1, s2) \
-do { \
-	static const char *const list[] = { s1, s2, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS3(s1, s2, s3) \
-do { \
-	static const char *const list[] = { s1, s2, s3, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS4(s1, s2, s3, s4) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS5(s1, s2, s3, s4, s5) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-/*
  * Assembly instructions for schema queries
  */
 
@@ -1007,6 +803,8 @@ static char **get_previous_words(int point, char **buffer, int *nwords);
 
 static char *get_guctype(const char *varname);
 
+static char **psql_completion_internal(const char *text, char **previous_words,
+										   int previous_words_count);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -1162,156 +960,58 @@ psql_completion(const char *text, int start, int end)
 	/* The number of words found on the input line. */
 	int			previous_words_count;
 
-	/*
-	 * For compactness, we use these macros to reference previous_words[].
-	 * Caution: do not access a previous_words[] entry without having checked
-	 * previous_words_count to be sure it's valid.  In most cases below, that
-	 * check is implicit in a TailMatches() or similar macro, but in some
-	 * places we have to check it explicitly.
-	 */
-#define prev_wd   (previous_words[0])
-#define prev2_wd  (previous_words[1])
-#define prev3_wd  (previous_words[2])
-#define prev4_wd  (previous_words[3])
-#define prev5_wd  (previous_words[4])
-#define prev6_wd  (previous_words[5])
-#define prev7_wd  (previous_words[6])
-#define prev8_wd  (previous_words[7])
-#define prev9_wd  (previous_words[8])
-
-	/* Macros for matching the last N words before point, case-insensitively. */
-#define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, prev_wd))
-
-#define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd))
-
-#define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd))
-
-#define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd))
-
-#define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd))
-
-#define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd))
-
-#define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd))
-
-#define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd) && \
-	 word_matches(p8, prev8_wd))
-
-#define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd) && \
-	 word_matches(p5, prev5_wd) && \
-	 word_matches(p6, prev6_wd) && \
-	 word_matches(p7, prev7_wd) && \
-	 word_matches(p8, prev8_wd) && \
-	 word_matches(p9, prev9_wd))
-
-	/* Macros for matching the last N words before point, case-sensitively. */
-#define TailMatchesCS1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches_cs(p1, prev_wd))
-#define TailMatchesCS2(p2, p1) \
-	(previous_words_count >= 2 && \
-	 word_matches_cs(p1, prev_wd) && \
-	 word_matches_cs(p2, prev2_wd))
+	(void) end;					/* "end" is not used */
 
-	/*
-	 * Macros for matching N words beginning at the start of the line,
-	 * case-insensitively.
-	 */
-#define Matches1(p1) \
-	(previous_words_count == 1 && \
-	 TailMatches1(p1))
-#define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
-	 TailMatches2(p1, p2))
-#define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
-	 TailMatches3(p1, p2, p3))
-#define Matches4(p1, p2, p3, p4) \
-	(previous_words_count == 4 && \
-	 TailMatches4(p1, p2, p3, p4))
-#define Matches5(p1, p2, p3, p4, p5) \
-	(previous_words_count == 5 && \
-	 TailMatches5(p1, p2, p3, p4, p5))
-#define Matches6(p1, p2, p3, p4, p5, p6) \
-	(previous_words_count == 6 && \
-	 TailMatches6(p1, p2, p3, p4, p5, p6))
-#define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(previous_words_count == 7 && \
-	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
-#define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(previous_words_count == 8 && \
-	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
-#define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(previous_words_count == 9 && \
-	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
+#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
+	rl_completion_append_character = ' ';
+#endif
+
+	/* Clear a few things. */
+	completion_charp = NULL;
+	completion_charpp = NULL;
+	completion_info_charp = NULL;
+	completion_info_charp2 = NULL;
 
 	/*
-	 * Macros for matching N words at the start of the line, regardless of
-	 * what is after them, case-insensitively.
+	 * Scan the input line to extract the words before our current position.
+	 * According to those we'll make some smart decisions on what the user is
+	 * probably intending to type.
 	 */
-#define HeadMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]))
+	previous_words = get_previous_words(start,
+										&words_buffer,
+										&previous_words_count);
+
+	matches  = psql_completion_internal(text, previous_words,
+										previous_words_count);
 
-#define HeadMatches2(p1, p2) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]))
+	/* free storage */
+	free(previous_words);
+	free(words_buffer);
 
-#define HeadMatches3(p1, p2, p3) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]) && \
-	 word_matches(p3, previous_words[previous_words_count - 3]))
+	if (matches != NULL)
+		return matches;
 
+	/*
+	 * If we still don't have anything to match we have to fabricate some sort
+	 * of default list. If we were to just return NULL, readline automatically
+	 * attempts filename completion, and that's usually no good.
+	 */
+#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
+	rl_completion_append_character = '\0';
+#endif
+	COMPLETE_WITH_CONST("");		/* This returns matches */
+}
+
+/*
+ * The completion function.
+ *
+ * Makes completion list.
+ * Note: COMPLETE_WITH_* macros immediately return to the caller.
+ */
+static char **
+psql_completion_internal(const char *text, char **previous_words,
+						 int previous_words_count)
+{
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
@@ -1343,90 +1043,80 @@ psql_completion(const char *text, int start, int end)
 		"\\timing", "\\unset", "\\x", "\\w", "\\watch", "\\z", "\\!", NULL
 	};
 
-	(void) end;					/* "end" is not used */
-
-#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
-	rl_completion_append_character = ' ';
-#endif
-
-	/* Clear a few things. */
-	completion_charp = NULL;
-	completion_charpp = NULL;
-	completion_info_charp = NULL;
-	completion_info_charp2 = NULL;
-
-	/*
-	 * Scan the input line to extract the words before our current position.
-	 * According to those we'll make some smart decisions on what the user is
-	 * probably intending to type.
-	 */
-	previous_words = get_previous_words(start,
-										&words_buffer,
-										&previous_words_count);
-
 	/* If current word is a backslash command, offer completions for that */
 	if (text[0] == '\\')
 		COMPLETE_WITH_LIST_CS(backslash_commands);
 
 	/* If current word is a variable interpolation, handle that case */
-	else if (text[0] == ':' && text[1] != ':')
+	if (text[0] == ':' && text[1] != ':')
 	{
 		if (text[1] == '\'')
-			matches = complete_from_variables(text, ":'", "'", true);
-		else if (text[1] == '"')
-			matches = complete_from_variables(text, ":\"", "\"", true);
-		else
-			matches = complete_from_variables(text, ":", "", true);
+			return complete_from_variables(text, ":'", "'", true);
+		if (text[1] == '"')
+			return complete_from_variables(text, ":\"", "\"", true);
+
+		return complete_from_variables(text, ":", "", true);
 	}
 
 	/* If no previous word, suggest one of the basic sql commands */
-	else if (previous_words_count == 0)
+	if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
 /* CREATE */
 	/* complete with something you can create */
-	else if (TailMatches1("CREATE"))
-		matches = completion_matches(text, create_command_generator);
+	if (Matches1("CREATE"))
+		return completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
-	else if (Matches1("DROP"))
-		matches = completion_matches(text, drop_command_generator);
+	if (Matches1("DROP"))
+		return completion_matches(text, drop_command_generator);
 
 /* ALTER */
 
 	/* ALTER TABLE */
-	else if (Matches2("ALTER", "TABLE"))
+	if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
 	/* ALTER something */
-	else if (Matches1("ALTER"))
+	if (Matches1("ALTER"))
 	{
 		static const char *const list_ALTER[] =
-		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
-			"EVENT TRIGGER", "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
-			"GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW", "OPERATOR",
-			"POLICY", "ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM", "TABLE",
-			"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
-		"USER", "USER MAPPING FOR", "VIEW", NULL};
+		{"AGGREGATE",
+		 "COLLATION", "CONVERSION",
+		 "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
+		 "EVENT TRIGGER", "EXTENSION",
+		 "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
+		 "GROUP",
+		 "INDEX",
+		 "LANGUAGE", "LARGE OBJECT",
+		 "MATERIALIZED VIEW", 
+		 "OPERATOR",
+		 "POLICY",
+		 "ROLE", "RULE",
+		 "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM",
+		 "TABLE", "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
+		 "USER", "USER MAPPING FOR",
+		 "VIEW",
+		 NULL};
 
 		COMPLETE_WITH_LIST(list_ALTER);
 	}
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx */
-	else if (TailMatches4("ALL", "IN", "TABLESPACE", MatchAny))
+	if (TailMatches4("ALL", "IN", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("SET TABLESPACE", "OWNED BY");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY */
-	else if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
+	if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY xxx */
-	else if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
+	if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("SET TABLESPACE");
 	/* ALTER AGGREGATE,FUNCTION <name> */
-	else if (Matches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
+	if (Matches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION <name> (...) */
-	else if (Matches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
+	if (Matches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
 	{
 		if (ends_with(prev_wd, ')'))
 			COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
@@ -1435,63 +1125,63 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER SCHEMA <name> */
-	else if (Matches3("ALTER", "SCHEMA", MatchAny))
+	if (Matches3("ALTER", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER TO", "RENAME TO");
 
 	/* ALTER COLLATION <name> */
-	else if (Matches3("ALTER", "COLLATION", MatchAny))
+	if (Matches3("ALTER", "COLLATION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER CONVERSION <name> */
-	else if (Matches3("ALTER", "CONVERSION", MatchAny))
+	if (Matches3("ALTER", "CONVERSION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER DATABASE <name> */
-	else if (Matches3("ALTER", "DATABASE", MatchAny))
+	if (Matches3("ALTER", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST7("RESET", "SET", "OWNER TO", "RENAME TO",
 							"IS_TEMPLATE", "ALLOW_CONNECTIONS",
 							"CONNECTION LIMIT");
 
 	/* ALTER EVENT TRIGGER */
-	else if (Matches3("ALTER", "EVENT", "TRIGGER"))
+	if (Matches3("ALTER", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
+	if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST4("DISABLE", "ENABLE", "OWNER TO", "RENAME TO");
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
+	if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST2("REPLICA", "ALWAYS");
 
 	/* ALTER EXTENSION <name> */
-	else if (Matches3("ALTER", "EXTENSION", MatchAny))
+	if (Matches3("ALTER", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST4("ADD", "DROP", "UPDATE", "SET SCHEMA");
 
 	/* ALTER EXTENSION <name> UPDATE */
-	else if (Matches4("ALTER", "EXTENSION", MatchAny, "UPDATE"))
+	if (Matches4("ALTER", "EXTENSION", MatchAny, "UPDATE"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions_with_TO);
 	}
 
 	/* ALTER EXTENSION <name> UPDATE TO */
-	else if (Matches5("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
+	if (Matches5("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
 	}
 
 	/* ALTER FOREIGN */
-	else if (Matches2("ALTER", "FOREIGN"))
+	if (Matches2("ALTER", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
 	/* ALTER FOREIGN TABLE <name> */
-	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
+	if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
 		{"ADD", "ALTER", "DISABLE TRIGGER", "DROP", "ENABLE", "INHERIT",
@@ -1502,42 +1192,42 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER INDEX */
-	else if (Matches2("ALTER", "INDEX"))
+	if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 	/* ALTER INDEX <name> */
-	else if (Matches3("ALTER", "INDEX", MatchAny))
+	if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
 	/* ALTER INDEX <name> SET */
-	else if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
+	if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
 		COMPLETE_WITH_LIST2("(", "TABLESPACE");
 	/* ALTER INDEX <name> RESET */
-	else if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
+	if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER INDEX <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
+	if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
 		COMPLETE_WITH_LIST3("fillfactor", "fastupdate",
 							"gin_pending_list_limit");
-	else if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
+	if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST3("fillfactor =", "fastupdate =",
 							"gin_pending_list_limit =");
 
 	/* ALTER LANGUAGE <name> */
-	else if (Matches3("ALTER", "LANGUAGE", MatchAny))
+	if (Matches3("ALTER", "LANGUAGE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER_TO", "RENAME TO");
 
 	/* ALTER LARGE OBJECT <oid> */
-	else if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
+	if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
 		COMPLETE_WITH_CONST("OWNER TO");
 
 	/* ALTER MATERIALIZED VIEW */
-	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
+	if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
 	/* ALTER USER,ROLE <name> */
-	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
-			 !TailMatches2("USER", "MAPPING"))
+	if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
+		!TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_ALTERUSER[] =
 		{"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -1551,7 +1241,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER USER,ROLE <name> WITH */
-	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
+	if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_ALTERUSER_WITH[] =
@@ -1566,61 +1256,61 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
-	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	if (Matches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* ALTER DEFAULT PRIVILEGES */
-	else if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
+	if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
 		COMPLETE_WITH_LIST2("FOR ROLE", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR */
-	else if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
+	if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
 		COMPLETE_WITH_CONST("ROLE");
 	/* ALTER DEFAULT PRIVILEGES IN */
-	else if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "IN"))
+	if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "IN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR ROLE|USER ... */
-	else if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER",
+	if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER",
 				MatchAny))
 		COMPLETE_WITH_LIST3("GRANT", "REVOKE", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES IN SCHEMA ... */
-	else if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
+	if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
 				MatchAny))
 		COMPLETE_WITH_LIST3("GRANT", "REVOKE", "FOR ROLE");
 	/* ALTER DEFAULT PRIVILEGES IN SCHEMA ... FOR */
-	else if (Matches7("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
+	if (Matches7("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
 				MatchAny, "FOR"))
 		COMPLETE_WITH_CONST("ROLE");
 	/* ALTER DEFAULT PRIVILEGES FOR ROLE|USER ... IN SCHEMA ... */
 	/* ALTER DEFAULT PRIVILEGES IN SCHEMA ... FOR ROLE|USER ... */
-	else if (Matches9("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER",
+	if (Matches9("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER",
 					MatchAny, "IN", "SCHEMA", MatchAny) ||
 		Matches9("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
 					MatchAny, "FOR", "ROLE|USER", MatchAny))
 		COMPLETE_WITH_LIST2("GRANT", "REVOKE");
 	/* ALTER DOMAIN <name> */
-	else if (Matches3("ALTER", "DOMAIN", MatchAny))
+	if (Matches3("ALTER", "DOMAIN", MatchAny))
 		COMPLETE_WITH_LIST6("ADD", "DROP", "OWNER TO", "RENAME", "SET",
 							"VALIDATE CONSTRAINT");
 	/* ALTER DOMAIN <sth> DROP */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
 	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
 	}
 	/* ALTER DOMAIN <sth> RENAME */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("CONSTRAINT", "TO");
 	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
-	else if (Matches6("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
+	if (Matches6("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER DOMAIN <sth> SET */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
+	if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	/* ALTER SEQUENCE <name> */
-	else if (Matches3("ALTER", "SEQUENCE", MatchAny))
+	if (Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1629,74 +1319,74 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
 	/* ALTER SEQUENCE <name> NO */
-	else if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
+	if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 	/* ALTER SERVER <name> */
-	else if (Matches3("ALTER", "SERVER", MatchAny))
+	if (Matches3("ALTER", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST4("VERSION", "OPTIONS", "OWNER TO", "RENAME TO");
 	/* ALTER SERVER <name> VERSION <version> */
-	else if (Matches5("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
+	if (Matches5("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
 		COMPLETE_WITH_CONST("OPTIONS");
 	/* ALTER SYSTEM SET, RESET, RESET ALL */
-	else if (Matches2("ALTER", "SYSTEM"))
+	if (Matches2("ALTER", "SYSTEM"))
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
-	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
+	if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
 	/* ALTER VIEW <name> */
-	else if (Matches3("ALTER", "VIEW", MatchAny))
+	if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
-	else if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
 	/* ALTER POLICY <name> */
-	else if (Matches2("ALTER", "POLICY"))
+	if (Matches2("ALTER", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* ALTER POLICY <name> ON */
-	else if (Matches3("ALTER", "POLICY", MatchAny))
+	if (Matches3("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
-	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
+	if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 	/* ALTER POLICY <name> ON <table> - show options */
-	else if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING (", "WITH CHECK (");
 	/* ALTER POLICY <name> ON <table> TO <role> */
-	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* ALTER POLICY <name> ON <table> USING ( */
-	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
-	else if (Matches7("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
+	if (Matches7("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
 		COMPLETE_WITH_CONST("(");
 
 	/* ALTER RULE <name>, add ON */
-	else if (Matches3("ALTER", "RULE", MatchAny))
+	if (Matches3("ALTER", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
-	else if (Matches4("ALTER", "RULE", MatchAny, "ON"))
+	if (Matches4("ALTER", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
 
 	/* ALTER RULE <name> ON <name> */
-	else if (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/* ALTER TRIGGER <name>, add ON */
-	else if (Matches3("ALTER", "TRIGGER", MatchAny))
+	if (Matches3("ALTER", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
-	else if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
+	if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
@@ -1705,17 +1395,17 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
-	else if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
+	if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER TRIGGER <name> ON <name> */
-	else if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
+	if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/*
 	 * If we detect ALTER TABLE <name>, suggest sub commands
 	 */
-	else if (Matches3("ALTER", "TABLE", MatchAny))
+	if (Matches3("ALTER", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER2[] =
 		{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
@@ -1725,114 +1415,114 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
 	/* ALTER TABLE xxx ENABLE */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST5("ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE",
 							"TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
 		COMPLETE_WITH_LIST2("RULE", "TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
 	/* ALTER TABLE xxx INHERIT */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx NO INHERIT */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx DISABLE */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
 		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
 
 	/* ALTER TABLE xxx ALTER */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
 
 	/* ALTER TABLE xxx RENAME */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/* ALTER TABLE xxx RENAME yyy */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
+	if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
-	else if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
+	if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* If we have ALTER TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
+	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
 			 Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
 		COMPLETE_WITH_LIST4("TYPE", "SET", "RESET", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
 			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
 		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
+	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
 		 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
+	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
 	Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
 		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
-			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
+		Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("DEFAULT", "NOT NULL");
-	else if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
 	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
 		COMPLETE_WITH_LIST7("(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED",
 							"WITH", "WITHOUT");
 
@@ -1840,19 +1530,19 @@ psql_completion(const char *text, int start, int end)
 	 * If we have ALTER TABLE <sth> SET TABLESPACE provide a list of
 	 * tablespaces
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	/* If we have ALTER TABLE <sth> SET WITH provide OIDS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
 		COMPLETE_WITH_CONST("OIDS");
 	/* If we have ALTER TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
 		COMPLETE_WITH_LIST2("CLUSTER", "OIDS");
 	/* ALTER TABLE <foo> RESET */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLE <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
+	if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
 	{
 		static const char *const list_TABLEOPTIONS[] =
 		{
@@ -1890,108 +1580,108 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
-	else if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
+	if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
+	if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
 		COMPLETE_WITH_LIST4("FULL", "NOTHING", "DEFAULT", "USING");
-	else if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
+	if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
 		COMPLETE_WITH_CONST("IDENTITY");
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
-	else if (Matches3("ALTER", "TABLESPACE", MatchAny))
+	if (Matches3("ALTER", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "OWNER TO", "SET", "RESET");
 	/* ALTER TABLESPACE <foo> SET|RESET */
-	else if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
+	if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
+	if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
 		COMPLETE_WITH_LIST3("seq_page_cost", "random_page_cost",
 							"effective_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
-	else if (Matches3("ALTER", "TEXT", "SEARCH"))
+	if (Matches3("ALTER", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
 		COMPLETE_WITH_LIST2("RENAME TO", "SET SCHEMA");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING",
 							"DROP MAPPING FOR",
 							"OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* complete ALTER TYPE <foo> with actions */
-	else if (Matches3("ALTER", "TYPE", MatchAny))
+	if (Matches3("ALTER", "TYPE", MatchAny))
 		COMPLETE_WITH_LIST7("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE",
 							"DROP ATTRIBUTE",
 							"OWNER TO", "RENAME", "SET SCHEMA");
 	/* complete ALTER TYPE <foo> ADD with actions */
-	else if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
+	if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
 	/* ALTER TYPE <foo> RENAME	*/
-	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
+	if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST3("ATTRIBUTE", "TO", "VALUE");
 	/* ALTER TYPE xxx RENAME (ATTRIBUTE|VALUE) yyy */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE|VALUE", MatchAny))
+	if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE|VALUE", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 	/*
 	 * If we have ALTER TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list
 	 * of attributes
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
+	if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
+	if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* complete ALTER GROUP <foo> */
-	else if (Matches3("ALTER", "GROUP", MatchAny))
+	if (Matches3("ALTER", "GROUP", MatchAny))
 		COMPLETE_WITH_LIST3("ADD USER", "DROP USER", "RENAME TO");
 	/* complete ALTER GROUP <foo> ADD|DROP with USER */
-	else if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
+	if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
 		COMPLETE_WITH_CONST("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
-	else if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
+	if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 	/*
 	 * If we have ALTER TYPE <sth> RENAME VALUE, provide list of enum values
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "RENAME", "VALUE"))
+	if (Matches5("ALTER", "TYPE", MatchAny, "RENAME", "VALUE"))
 		COMPLETE_WITH_ENUM_VALUE(prev3_wd);
 
 /* BEGIN */
-	else if (Matches1("BEGIN"))
+	if (Matches1("BEGIN"))
 		COMPLETE_WITH_LIST6("WORK", "TRANSACTION", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
 /* END, ABORT */
-	else if (Matches1("END|ABORT"))
+	if (Matches1("END|ABORT"))
 		COMPLETE_WITH_LIST2("WORK", "TRANSACTION");
 /* COMMIT */
-	else if (Matches1("COMMIT"))
+	if (Matches1("COMMIT"))
 		COMPLETE_WITH_LIST3("WORK", "TRANSACTION", "PREPARED");
 /* RELEASE SAVEPOINT */
-	else if (Matches1("RELEASE"))
+	if (Matches1("RELEASE"))
 		COMPLETE_WITH_CONST("SAVEPOINT");
 /* ROLLBACK */
-	else if (Matches1("ROLLBACK"))
+	if (Matches1("ROLLBACK"))
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
-	else if (Matches1("CLUSTER"))
+	if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
-	else if (Matches2("CLUSTER", "VERBOSE"))
+	if (Matches2("CLUSTER", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 	/* If we have CLUSTER <sth>, then add "USING" */
-	else if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
+	if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	else if (Matches3("CLUSTER", "VERBOSE", MatchAny))
+	if (Matches3("CLUSTER", "VERBOSE", MatchAny))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER <sth> USING, then add the index as well */
-	else if (Matches3("CLUSTER", MatchAny, "USING") ||
+	if (Matches3("CLUSTER", MatchAny, "USING") ||
 			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
@@ -1999,9 +1689,9 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* COMMENT */
-	else if (Matches1("COMMENT"))
+	if (Matches1("COMMENT"))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches2("COMMENT", "ON"))
+	if (Matches2("COMMENT", "ON"))
 	{
 		static const char *const list_COMMENT[] =
 		{"ACCESS METHOD", "CAST", "COLLATION", "CONVERSION", "DATABASE",
@@ -2014,26 +1704,26 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
-	else if (Matches4("COMMENT", "ON", "ACCESS", "METHOD"))
+	if (Matches4("COMMENT", "ON", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (Matches3("COMMENT", "ON", "FOREIGN"))
+	if (Matches3("COMMENT", "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
-	else if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
+	if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches3("COMMENT", "ON", "CONSTRAINT"))
+	if (Matches3("COMMENT", "ON", "CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
-	else if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
+	if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
+	if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
 	}
-	else if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
+	if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
+	if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	else if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
+	if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
 		Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
 			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
 		COMPLETE_WITH_CONST("IS");
@@ -2044,97 +1734,97 @@ psql_completion(const char *text, int start, int end)
 	 * If we have COPY, offer list of tables or "(" (Also cover the analogous
 	 * backslash command).
 	 */
-	else if (Matches1("COPY|\\copy"))
+	if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION ALL SELECT '('");
 	/* If we have COPY BINARY, complete with list of tables */
-	else if (Matches2("COPY", "BINARY"))
+	if (Matches2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* If we have COPY (, complete it with legal commands */
-	else if (Matches2("COPY|\\copy", "("))
+	if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
 	/* If we have COPY [BINARY] <sth>, complete it with "TO" or "FROM" */
-	else if (Matches2("COPY|\\copy", MatchAny) ||
+	if (Matches2("COPY|\\copy", MatchAny) ||
 			 Matches3("COPY", "BINARY", MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "TO");
 	/* If we have COPY [BINARY] <sth> FROM|TO, complete with filename */
-	else if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
+	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
 			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
 	{
 		completion_charp = "";
-		matches = completion_matches(text, complete_from_files);
+		return completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename */
-	else if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
+	if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
 			 Matches5("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
 		COMPLETE_WITH_LIST6("BINARY", "OIDS", "DELIMITER", "NULL", "CSV",
 							"ENCODING");
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename CSV */
-	else if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
+	if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
 			 Matches6("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny, "CSV"))
 		COMPLETE_WITH_LIST5("HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE",
 							"FORCE NOT NULL");
 
 	/* CREATE ACCESS METHOD */
 	/* Complete "CREATE ACCESS METHOD <name>" */
-	else if (Matches4("CREATE", "ACCESS", "METHOD", MatchAny))
+	if (Matches4("CREATE", "ACCESS", "METHOD", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE" */
-	else if (Matches5("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
+	if (Matches5("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
 		COMPLETE_WITH_CONST("INDEX");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE <type>" */
-	else if (Matches6("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
+	if (Matches6("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
 		COMPLETE_WITH_CONST("HANDLER");
 
 	/* CREATE DATABASE */
-	else if (Matches3("CREATE", "DATABASE", MatchAny))
+	if (Matches3("CREATE", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST9("OWNER", "TEMPLATE", "ENCODING", "TABLESPACE",
 							"IS_TEMPLATE",
 							"ALLOW_CONNECTIONS", "CONNECTION LIMIT",
 							"LC_COLLATE", "LC_CTYPE");
 
-	else if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
+	if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
-	else if (Matches2("CREATE", "EXTENSION"))
+	if (Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
-	else if (Matches3("CREATE", "EXTENSION", MatchAny))
+	if (Matches3("CREATE", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
 	/* CREATE EXTENSION <name> VERSION */
-	else if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
+	if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
 	}
 
 	/* CREATE FOREIGN */
-	else if (Matches2("CREATE", "FOREIGN"))
+	if (Matches2("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* CREATE FOREIGN DATA WRAPPER */
-	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	else if (TailMatches2("CREATE", "UNIQUE"))
+	if (TailMatches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
 	/*
 	 * If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY", and
 	 * existing indexes
 	 */
-	else if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	if (TailMatches2("CREATE|UNIQUE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
 								   " UNION SELECT 'CONCURRENTLY'");
 	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	else if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
+	if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
 			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
@@ -2142,11 +1832,11 @@ psql_completion(const char *text, int start, int end)
 	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
 	 * indexes
 	 */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+	if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'");
 	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
+	if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
 			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
@@ -2154,142 +1844,142 @@ psql_completion(const char *text, int start, int end)
 	 * Complete INDEX <name> ON <table> with a list of table columns (which
 	 * should really be in parens)
 	 */
-	else if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-			 TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+	if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
+		TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-			 TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
+	if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
+		TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* same if you put in USING */
-	else if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
+	if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	else if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
+	if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
+	if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
 			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
 			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-	else if (Matches3("CREATE", "POLICY", MatchAny))
+	if (Matches3("CREATE", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
-	else if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
+	if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* Complete "CREATE POLICY <name> ON <table> AS|FOR|TO|USING|WITH CHECK" */
-	else if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
+	if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST5("AS", "FOR", "TO", "USING (", "WITH CHECK (");
 	/* CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS"))
 		COMPLETE_WITH_LIST2("PERMISSIVE", "RESTRICTIVE");
 	/* CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE FOR|TO|USING|WITH CHECK */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING", "WITH CHECK");
 	/* CREATE POLICY <name> ON <table> FOR ALL|SELECT|INSERT|UPDATE|DELETE */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
 		COMPLETE_WITH_LIST5("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
 	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
 		COMPLETE_WITH_LIST2("TO", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> FOR SELECT|DELETE TO|USING" */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
 		COMPLETE_WITH_LIST2("TO", "USING (");
 	/* CREATE POLICY <name> ON <table> FOR ALL|UPDATE TO|USING|WITH CHECK */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
+	if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
 		COMPLETE_WITH_LIST3("TO", "USING (", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 	/* CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE FOR ALL|SELECT|INSERT|UPDATE|DELETE */
-	else if (Matches8("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR"))
+	if (Matches8("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR"))
 		COMPLETE_WITH_LIST5("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
 	/* Complete "CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE FOR INSERT TO|WITH CHECK" */
-	else if (Matches9("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "INSERT"))
+	if (Matches9("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "INSERT"))
 		COMPLETE_WITH_LIST2("TO", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE FOR SELECT|DELETE TO|USING" */
-	else if (Matches9("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "SELECT|DELETE"))
+	if (Matches9("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "SELECT|DELETE"))
 		COMPLETE_WITH_LIST2("TO", "USING (");
 	/* CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE FOR ALL|UPDATE TO|USING|WITH CHECK */
-	else if (Matches9("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "ALL|UPDATE"))
+	if (Matches9("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "ALL|UPDATE"))
 		COMPLETE_WITH_LIST3("TO", "USING (", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE TO <role>" */
-	else if (Matches8("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "TO"))
+	if (Matches8("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE USING (" */
-	else if (Matches8("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "USING"))
+	if (Matches8("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS ON" */
-	else if (Matches3("CREATE", "RULE", MatchAny))
+	if (Matches3("CREATE", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("AS ON");
 	/* Complete "CREATE RULE <sth> AS" with "ON" */
-	else if (Matches4("CREATE", "RULE", MatchAny, "AS"))
+	if (Matches4("CREATE", "RULE", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE RULE <sth> AS ON" with SELECT|UPDATE|INSERT|DELETE */
-	else if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
+	if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
 	/* Complete "AS ON SELECT|UPDATE|INSERT|DELETE" with a "TO" */
-	else if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
+	if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
-	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
+	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	else if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
+	if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
 			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	else if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
+	if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
 		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
-	else if (Matches3("CREATE", "SERVER", MatchAny))
+	if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
 /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	else if (TailMatches2("CREATE", "UNLOGGED"))
+	if (TailMatches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
 /* CREATE TABLESPACE */
-	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
+	if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
 	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
-	else if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
+	if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
 		COMPLETE_WITH_CONST("LOCATION");
 
 /* CREATE TEXT SEARCH */
-	else if (Matches3("CREATE", "TEXT", "SEARCH"))
+	if (Matches3("CREATE", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	else if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	if (TailMatches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+	if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
 	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
@@ -2297,51 +1987,51 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
 	 * tables
 	 */
-	else if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
+	if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
-	else if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
+	if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("ON", MatchAny))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("ON", MatchAny))
 		COMPLETE_WITH_LIST7("NOT DEFERRABLE", "DEFERRABLE", "INITIALLY",
 							"REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
+	if (HeadMatches2("CREATE", "TRIGGER") &&
 			 (TailMatches1("DEFERRABLE") || TailMatches2("INITIALLY", "IMMEDIATE|DEFERRED")))
 		COMPLETE_WITH_LIST4("REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("REFERENCING"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("REFERENCING"))
 		COMPLETE_WITH_LIST2("OLD TABLE", "NEW TABLE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("OLD|NEW", "TABLE"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("OLD|NEW", "TABLE"))
 		COMPLETE_WITH_CONST("AS");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches5("REFERENCING", "OLD", "TABLE", "AS", MatchAny) ||
-			  TailMatches4("REFERENCING", "OLD", "TABLE", MatchAny)))
+	if (HeadMatches2("CREATE", "TRIGGER") &&
+		(TailMatches5("REFERENCING", "OLD", "TABLE", "AS", MatchAny) ||
+		 TailMatches4("REFERENCING", "OLD", "TABLE", MatchAny)))
 		COMPLETE_WITH_LIST4("NEW TABLE", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches5("REFERENCING", "NEW", "TABLE", "AS", MatchAny) ||
-			  TailMatches4("REFERENCING", "NEW", "TABLE", MatchAny)))
+	if (HeadMatches2("CREATE", "TRIGGER") &&
+		(TailMatches5("REFERENCING", "NEW", "TABLE", "AS", MatchAny) ||
+		 TailMatches4("REFERENCING", "NEW", "TABLE", MatchAny)))
 		COMPLETE_WITH_LIST4("OLD TABLE", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches9("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
-			  TailMatches8("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
-			  TailMatches8("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", MatchAny) ||
-			  TailMatches7("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", MatchAny)))
+	if (HeadMatches2("CREATE", "TRIGGER") &&
+		(TailMatches9("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
+		 TailMatches8("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
+		 TailMatches8("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", MatchAny) ||
+		 TailMatches7("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", MatchAny)))
 		COMPLETE_WITH_LIST3("FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("FOR"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("FOR"))
 		COMPLETE_WITH_LIST3("EACH", "ROW", "STATEMENT");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("FOR", "EACH"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("FOR", "EACH"))
 		COMPLETE_WITH_LIST2("ROW", "STATEMENT");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches3("FOR", "EACH", "ROW|STATEMENT") ||
-			  TailMatches2("FOR", "ROW|STATEMENT")))
+	if (HeadMatches2("CREATE", "TRIGGER") &&
+		(TailMatches3("FOR", "EACH", "ROW|STATEMENT") ||
+		 TailMatches2("FOR", "ROW|STATEMENT")))
 		COMPLETE_WITH_LIST2("WHEN (", "EXECUTE PROCEDURE");
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("EXECUTE", "PROCEDURE"))
+	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("EXECUTE", "PROCEDURE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 
 /* CREATE ROLE,USER,GROUP <name> */
-	else if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
+	if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_CREATEROLE[] =
@@ -2356,7 +2046,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE ROLE,USER,GROUP <name> WITH */
-	else if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
+	if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_CREATEROLE_WITH[] =
@@ -2374,70 +2064,70 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
 	 * PASSWORD
 	 */
-	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
-	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
+	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE VIEW <name> with AS */
-	else if (TailMatches3("CREATE", "VIEW", MatchAny))
+	if (TailMatches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
+	if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches2("CREATE", "MATERIALIZED"))
+	if (Matches2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE EVENT TRIGGER */
-	else if (Matches2("CREATE", "EVENT"))
+	if (Matches2("CREATE", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	/* Complete CREATE EVENT TRIGGER <name> with ON */
-	else if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
+	if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
-	else if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
+	if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_LIST3("ddl_command_start", "ddl_command_end", "sql_drop");
 
 /* DECLARE */
-	else if (Matches2("DECLARE", MatchAny))
+	if (Matches2("DECLARE", MatchAny))
 		COMPLETE_WITH_LIST5("BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL",
 							"CURSOR");
-	else if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
+	if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
 		COMPLETE_WITH_LIST3("WITH HOLD", "WITHOUT HOLD", "FOR");
 
 /* DELETE --- can be inside EXPLAIN, RULE, etc */
 	/* ... despite which, only complete DELETE with FROM at start of line */
-	else if (Matches1("DELETE"))
+	if (Matches1("DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
-	else if (TailMatches2("DELETE", "FROM"))
+	if (TailMatches2("DELETE", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete DELETE FROM <table> */
-	else if (TailMatches3("DELETE", "FROM", MatchAny))
+	if (TailMatches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
 	/* XXX: implement tab completion for DELETE ... USING */
 
 /* DISCARD */
-	else if (Matches1("DISCARD"))
+	if (Matches1("DISCARD"))
 		COMPLETE_WITH_LIST4("ALL", "PLANS", "SEQUENCES", "TEMP");
 
 /* DO */
-	else if (Matches1("DO"))
+	if (Matches1("DO"))
 		COMPLETE_WITH_CONST("LANGUAGE");
 
 /* DROP */
 	/* Complete DROP object with CASCADE / RESTRICT */
-	else if (Matches3("DROP",
+	if (Matches3("DROP",
 					  "COLLATION|CONVERSION|DOMAIN|EXTENSION|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW",
 					  MatchAny) ||
 			 Matches4("DROP", "ACCESS", "METHOD", MatchAny) ||
@@ -2450,88 +2140,88 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
+	if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
-	else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
+	if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
-	else if (Matches2("DROP", "FOREIGN"))
+	if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* DROP INDEX */
-	else if (Matches2("DROP", "INDEX"))
+	if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
+	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches3("DROP", "INDEX", MatchAny))
+	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	else if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
+	if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
-	else if (Matches2("DROP", "MATERIALIZED"))
+	if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
 	/* DROP OWNED BY */
-	else if (Matches2("DROP", "OWNED"))
+	if (Matches2("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (Matches3("DROP", "OWNED", "BY"))
+	if (Matches3("DROP", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
-	else if (Matches3("DROP", "TEXT", "SEARCH"))
+	if (Matches3("DROP", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (Matches3("DROP", "TRIGGER", MatchAny))
+	if (Matches3("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
+	if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
 	}
-	else if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
+	if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP ACCESS METHOD */
-	else if (Matches2("DROP", "ACCESS"))
+	if (Matches2("DROP", "ACCESS"))
 		COMPLETE_WITH_CONST("METHOD");
-	else if (Matches3("DROP", "ACCESS", "METHOD"))
+	if (Matches3("DROP", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 
 	/* DROP EVENT TRIGGER */
-	else if (Matches2("DROP", "EVENT"))
+	if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
-	else if (Matches3("DROP", "EVENT", "TRIGGER"))
+	if (Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* DROP POLICY <name>  */
-	else if (Matches2("DROP", "POLICY"))
+	if (Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* DROP POLICY <name> ON */
-	else if (Matches3("DROP", "POLICY", MatchAny))
+	if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* DROP POLICY <name> ON <table> */
-	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
+	if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 
 	/* DROP RULE */
-	else if (Matches3("DROP", "RULE", MatchAny))
+	if (Matches3("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
+	if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
-	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
+	if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 /* EXECUTE */
-	else if (Matches1("EXECUTE"))
+	if (Matches1("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
 
 /* EXPLAIN */
@@ -2539,22 +2229,22 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
-	else if (Matches1("EXPLAIN"))
+	if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"ANALYZE", "VERBOSE");
-	else if (Matches2("EXPLAIN", "ANALYZE"))
+	if (Matches2("EXPLAIN", "ANALYZE"))
 		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"VERBOSE");
-	else if (Matches2("EXPLAIN", "VERBOSE") ||
+	if (Matches2("EXPLAIN", "VERBOSE") ||
 			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
 		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-	else if (Matches1("FETCH|MOVE"))
+	if (Matches1("FETCH|MOVE"))
 		COMPLETE_WITH_LIST4("ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE");
 	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
-	else if (Matches2("FETCH|MOVE", MatchAny))
+	if (Matches2("FETCH|MOVE", MatchAny))
 		COMPLETE_WITH_LIST3("ALL", "NEXT", "PRIOR");
 
 	/*
@@ -2562,26 +2252,26 @@ psql_completion(const char *text, int start, int end)
 	 * but we may as well tab-complete both: perhaps some users prefer one
 	 * variant or the other.
 	 */
-	else if (Matches3("FETCH|MOVE", MatchAny, MatchAny))
+	if (Matches3("FETCH|MOVE", MatchAny, MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "IN");
 
 /* FOREIGN DATA WRAPPER */
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
-	else if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
+	if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
 			 !TailMatches4("CREATE", MatchAny, MatchAny, MatchAny))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 	/* applies in CREATE SERVER */
-	else if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
+	if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
 			 HeadMatches2("CREATE", "SERVER"))
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /* FOREIGN TABLE */
-	else if (TailMatches2("FOREIGN", "TABLE") &&
+	if (TailMatches2("FOREIGN", "TABLE") &&
 			 !TailMatches3("CREATE", MatchAny, MatchAny))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* FOREIGN SERVER */
-	else if (TailMatches2("FOREIGN", "SERVER"))
+	if (TailMatches2("FOREIGN", "SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
 
 /*
@@ -2589,7 +2279,8 @@ psql_completion(const char *text, int start, int end)
  * ALTER DEFAULT PRIVILEGES, so use TailMatches
  */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	else if (TailMatches1("GRANT|REVOKE"))
+	if (TailMatches1("GRANT|REVOKE"))
+	{
 		/*
 		 * With ALTER DEFAULT PRIVILEGES, restrict completion
 		 * to grantable privileges (can't grant roles)
@@ -2613,16 +2304,16 @@ psql_completion(const char *text, int start, int end)
 							" UNION SELECT 'EXECUTE'"
 							" UNION SELECT 'USAGE'"
 							" UNION SELECT 'ALL'");
-
+	}
 	/*
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	else if (TailMatches2("GRANT|REVOKE", MatchAny))
+	if (TailMatches2("GRANT|REVOKE", MatchAny))
 	{
 		if (TailMatches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
-		else if (TailMatches2("GRANT", MatchAny))
+		if (TailMatches2("GRANT", MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2639,7 +2330,8 @@ psql_completion(const char *text, int start, int end)
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
 	 */
-	else if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
+	if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
+	{
 		/*
 		 * With ALTER DEFAULT PRIVILEGES, restrict completion
 		 * to the kinds of objects supported.
@@ -2663,12 +2355,12 @@ psql_completion(const char *text, int start, int end)
 								   " UNION SELECT 'TABLE'"
 								   " UNION SELECT 'TABLESPACE'"
 								   " UNION SELECT 'TYPE'");
-
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
+	}
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
 							"TABLES IN SCHEMA");
 
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/*
@@ -2677,27 +2369,27 @@ psql_completion(const char *text, int start, int end)
 	 *
 	 * Complete "GRANT/REVOKE * ON *" with "TO/FROM".
 	 */
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
+	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
 		if (TailMatches1("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-		else if (TailMatches1("DOMAIN"))
+		if (TailMatches1("DOMAIN"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-		else if (TailMatches1("FUNCTION"))
+		if (TailMatches1("FUNCTION"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-		else if (TailMatches1("LANGUAGE"))
+		if (TailMatches1("LANGUAGE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-		else if (TailMatches1("SCHEMA"))
+		if (TailMatches1("SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-		else if (TailMatches1("SEQUENCE"))
+		if (TailMatches1("SEQUENCE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-		else if (TailMatches1("TABLE"))
+		if (TailMatches1("TABLE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-		else if (TailMatches1("TABLESPACE"))
+		if (TailMatches1("TABLESPACE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-		else if (TailMatches1("TYPE"))
+		if (TailMatches1("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-		else if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
+		if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2707,20 +2399,20 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "GRANT/REVOKE ... TO/FROM" with username, PUBLIC,
 	 * CURRENT_USER, or SESSION_USER.
 	 */
-	else if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
+	if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
 			 (HeadMatches1("REVOKE") && TailMatches1("FROM")))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "ALTER DEFAULT PRIVILEGES ... GRANT/REVOKE ... TO/FROM */
 	else if (HeadMatches3("ALTER","DEFAULT", "PRIVILEGES") && TailMatches1("TO|FROM"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */
-	else if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
+	if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (HeadMatches1("REVOKE") && TailMatches3("ON", MatchAny, MatchAny))
+	if (HeadMatches1("REVOKE") && TailMatches3("ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("FROM");
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	else if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
+	if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
 	{
 		if (TailMatches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2729,7 +2421,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	else if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 	{
 		if (TailMatches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2738,7 +2430,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	else if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
+	if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
 	{
 		if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
@@ -2747,62 +2439,62 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* GROUP BY */
-	else if (TailMatches3("FROM", MatchAny, "GROUP"))
+	if (TailMatches3("FROM", MatchAny, "GROUP"))
 		COMPLETE_WITH_CONST("BY");
 
 /* IMPORT FOREIGN SCHEMA */
-	else if (Matches1("IMPORT"))
+	if (Matches1("IMPORT"))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
-	else if (Matches2("IMPORT", "FOREIGN"))
+	if (Matches2("IMPORT", "FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT --- can be inside EXPLAIN, RULE, etc */
 	/* Complete INSERT with "INTO" */
-	else if (TailMatches1("INSERT"))
+	if (TailMatches1("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
-	else if (TailMatches2("INSERT", "INTO"))
+	if (TailMatches2("INSERT", "INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete "INSERT INTO <table> (" with attribute names */
-	else if (TailMatches4("INSERT", "INTO", MatchAny, "("))
+	if (TailMatches4("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES"
 	 */
-	else if (TailMatches3("INSERT", "INTO", MatchAny))
+	if (TailMatches3("INSERT", "INTO", MatchAny))
 		COMPLETE_WITH_LIST5("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES");
 
 	/*
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE"
 	 */
-	else if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
+	if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
 			 ends_with(prev_wd, ')'))
 		COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES");
 
 	/* Insert an open parenthesis after "VALUES" */
-	else if (TailMatches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
+	if (TailMatches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
 		COMPLETE_WITH_CONST("(");
 
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
-	else if (Matches1("LOCK"))
+	if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION SELECT 'TABLE'");
-	else if (Matches2("LOCK", "TABLE"))
+	if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* For the following, handle the case of a single table only for now */
 
 	/* Complete LOCK [TABLE] <table> with "IN" */
-	else if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
+	if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
 			 Matches3("LOCK", "TABLE", MatchAny))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	else if (Matches3("LOCK", MatchAny, "IN") ||
+	if (Matches3("LOCK", MatchAny, "IN") ||
 			 Matches4("LOCK", "TABLE", MatchAny, "IN"))
 		COMPLETE_WITH_LIST8("ACCESS SHARE MODE",
 							"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
@@ -2811,36 +2503,36 @@ psql_completion(const char *text, int start, int end)
 							"EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE");
 
 	/* Complete LOCK [TABLE] <table> IN ACCESS|ROW with rest of lock mode */
-	else if (Matches4("LOCK", MatchAny, "IN", "ACCESS|ROW") ||
+	if (Matches4("LOCK", MatchAny, "IN", "ACCESS|ROW") ||
 			 Matches5("LOCK", "TABLE", MatchAny, "IN", "ACCESS|ROW"))
 		COMPLETE_WITH_LIST2("EXCLUSIVE MODE", "SHARE MODE");
 
 	/* Complete LOCK [TABLE] <table> IN SHARE with rest of lock mode */
-	else if (Matches4("LOCK", MatchAny, "IN", "SHARE") ||
+	if (Matches4("LOCK", MatchAny, "IN", "SHARE") ||
 			 Matches5("LOCK", "TABLE", MatchAny, "IN", "SHARE"))
 		COMPLETE_WITH_LIST3("MODE", "ROW EXCLUSIVE MODE",
 							"UPDATE EXCLUSIVE MODE");
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
-	else if (TailMatches1("NOTIFY"))
+	if (TailMatches1("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
 
 /* OPTIONS */
-	else if (TailMatches1("OPTIONS"))
+	if (TailMatches1("OPTIONS"))
 		COMPLETE_WITH_CONST("(");
 
 /* OWNER TO  - complete with available roles */
-	else if (TailMatches2("OWNER", "TO"))
+	if (TailMatches2("OWNER", "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* ORDER BY */
-	else if (TailMatches3("FROM", MatchAny, "ORDER"))
+	if (TailMatches3("FROM", MatchAny, "ORDER"))
 		COMPLETE_WITH_CONST("BY");
-	else if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
+	if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 /* PREPARE xx AS */
-	else if (Matches3("PREPARE", MatchAny, "AS"))
+	if (Matches3("PREPARE", MatchAny, "AS"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
 
 /*
@@ -2849,60 +2541,60 @@ psql_completion(const char *text, int start, int end)
  */
 
 /* REASSIGN OWNED BY xxx TO yyy */
-	else if (Matches1("REASSIGN"))
+	if (Matches1("REASSIGN"))
 		COMPLETE_WITH_CONST("OWNED BY");
-	else if (Matches2("REASSIGN", "OWNED"))
+	if (Matches2("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (Matches3("REASSIGN", "OWNED", "BY"))
+	if (Matches3("REASSIGN", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
+	if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
+	if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* REFRESH MATERIALIZED VIEW */
-	else if (Matches1("REFRESH"))
+	if (Matches1("REFRESH"))
 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
-	else if (Matches2("REFRESH", "MATERIALIZED"))
+	if (Matches2("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
+	if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
+	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
+	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
+	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
+	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
-	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
+	if (Matches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
-	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
+	if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
-	else if (Matches7("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
+	if (Matches7("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
 
 /* REINDEX */
-	else if (Matches1("REINDEX"))
+	if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
-	else if (Matches2("REINDEX", "TABLE"))
+	if (Matches2("REINDEX", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-	else if (Matches2("REINDEX", "INDEX"))
+	if (Matches2("REINDEX", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches2("REINDEX", "SCHEMA"))
+	if (Matches2("REINDEX", "SCHEMA"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-	else if (Matches2("REINDEX", "SYSTEM|DATABASE"))
+	if (Matches2("REINDEX", "SYSTEM|DATABASE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 
 /* SECURITY LABEL */
-	else if (Matches1("SECURITY"))
+	if (Matches1("SECURITY"))
 		COMPLETE_WITH_CONST("LABEL");
-	else if (Matches2("SECURITY", "LABEL"))
+	if (Matches2("SECURITY", "LABEL"))
 		COMPLETE_WITH_LIST2("ON", "FOR");
-	else if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
+	if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches3("SECURITY", "LABEL", "ON") ||
+	if (Matches3("SECURITY", "LABEL", "ON") ||
 			 Matches5("SECURITY", "LABEL", "FOR", MatchAny, "ON"))
 	{
 		static const char *const list_SECURITY_LABEL[] =
@@ -2913,7 +2605,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_SECURITY_LABEL);
 	}
-	else if (Matches5("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
+	if (Matches5("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("IS");
 
 /* SELECT */
@@ -2921,69 +2613,69 @@ psql_completion(const char *text, int start, int end)
 
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
-	else if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
+	if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
-	else if (Matches1("SHOW"))
+	if (Matches1("SHOW"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
 	/* Complete "SET TRANSACTION" */
-	else if (Matches2("SET", "TRANSACTION"))
+	if (Matches2("SET", "TRANSACTION"))
 		COMPLETE_WITH_LIST5("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	else if (Matches2("BEGIN|START", "TRANSACTION") ||
-			 Matches2("BEGIN", "WORK") ||
-			 Matches1("BEGIN") ||
-		  Matches5("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
+	if (Matches2("BEGIN|START", "TRANSACTION") ||
+		Matches2("BEGIN", "WORK") ||
+		Matches1("BEGIN") ||
+		Matches5("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
 		COMPLETE_WITH_LIST4("ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
-			 Matches2("BEGIN", "NOT") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
+		Matches2("BEGIN", "NOT") ||
+		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
 		COMPLETE_WITH_CONST("DEFERRABLE");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
-			 Matches2("BEGIN", "ISOLATION") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
+		Matches2("BEGIN", "ISOLATION") ||
+		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
 		COMPLETE_WITH_CONST("LEVEL");
-	else if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
-			 Matches3("BEGIN", "ISOLATION", "LEVEL") ||
-			 Matches7("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
+	if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
+		Matches3("BEGIN", "ISOLATION", "LEVEL") ||
+		Matches7("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
 		COMPLETE_WITH_LIST3("READ", "REPEATABLE READ", "SERIALIZABLE");
-	else if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
-			 Matches4("BEGIN", "ISOLATION", "LEVEL", "READ") ||
-			 Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
+	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
+		Matches4("BEGIN", "ISOLATION", "LEVEL", "READ") ||
+		Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
 		COMPLETE_WITH_LIST2("UNCOMMITTED", "COMMITTED");
-	else if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
-			 Matches4("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
-			 Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
+	if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
+		Matches4("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
+		Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
 		COMPLETE_WITH_CONST("READ");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
-			 Matches2("BEGIN", "READ") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
+	if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
+		Matches2("BEGIN", "READ") ||
+		Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
-	else if (Matches2("SET", "CONSTRAINTS"))
+	if (Matches2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
-	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
+	if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
-	else if (Matches2("SET", "ROLE"))
+	if (Matches2("SET", "ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
-	else if (Matches2("SET", "SESSION"))
+	if (Matches2("SET", "SESSION"))
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
-	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
+	if (Matches3("SET", "SESSION", "AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
 	/* Complete RESET SESSION with AUTHORIZATION */
-	else if (Matches2("RESET", "SESSION"))
+	if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
 	/* Complete SET <var> with "TO" */
-	else if (Matches2("SET", MatchAny))
+	if (Matches2("SET", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete ALTER DATABASE|FUNCTION|ROLE|USER ... SET <name> */
-	else if (HeadMatches2("ALTER", "DATABASE|FUNCTION|ROLE|USER") &&
+	if (HeadMatches2("ALTER", "DATABASE|FUNCTION|ROLE|USER") &&
 			 TailMatches2("SET", MatchAny))
 		COMPLETE_WITH_LIST2("FROM CURRENT", "TO");
 	/* Suggest possible variable values */
-	else if (TailMatches3("SET", MatchAny, "TO|="))
+	if (TailMatches3("SET", MatchAny, "TO|="))
 	{
 		/* special cased code for individual GUCs */
 		if (TailMatches2("DateStyle", "TO|="))
@@ -2996,112 +2688,117 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST(my_list);
 		}
-		else if (TailMatches2("search_path", "TO|="))
+		if (TailMatches2("search_path", "TO|="))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
 								" AND nspname not like 'pg\\_toast%%' "
 								" AND nspname not like 'pg\\_temp%%' "
 								" UNION SELECT 'DEFAULT' ");
 		else
 		{
-			/* generic, type based, GUC support */
+			/* generic, type based, GUC support, guctype is malloc'ed */
 			char	   *guctype = get_guctype(prev2_wd);
 
 			if (guctype && strcmp(guctype, "enum") == 0)
 			{
 				char		querybuf[1024];
 
+				free(guctype);
 				snprintf(querybuf, sizeof(querybuf), Query_for_enum, prev2_wd);
 				COMPLETE_WITH_QUERY(querybuf);
 			}
-			else if (guctype && strcmp(guctype, "bool") == 0)
+
+			if (guctype && strcmp(guctype, "bool") == 0)
+			{
+				free(guctype);
 				COMPLETE_WITH_LIST9("on", "off", "true", "false", "yes", "no",
 									"1", "0", "DEFAULT");
-			else
-				COMPLETE_WITH_CONST("DEFAULT");
+			}
 
 			if (guctype)
 				free(guctype);
+
+			COMPLETE_WITH_CONST("DEFAULT");
 		}
 	}
 
 /* START TRANSACTION */
-	else if (Matches1("START"))
+	if (Matches1("START"))
 		COMPLETE_WITH_CONST("TRANSACTION");
 
 /* TABLE, but not TABLE embedded in other commands */
-	else if (Matches1("TABLE"))
+	if (Matches1("TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
 /* TABLESAMPLE */
-	else if (TailMatches1("TABLESAMPLE"))
+	if (TailMatches1("TABLESAMPLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods);
-	else if (TailMatches2("TABLESAMPLE", MatchAny))
+	if (TailMatches2("TABLESAMPLE", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
-	else if (Matches1("TRUNCATE"))
+	if (Matches1("TRUNCATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* UNLISTEN */
-	else if (Matches1("UNLISTEN"))
+	if (Matches1("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
 
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
-	else if (TailMatches1("UPDATE"))
+	if (TailMatches1("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete UPDATE <table> with "SET" */
-	else if (TailMatches2("UPDATE", MatchAny))
+	if (TailMatches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
 	/* Complete UPDATE <table> SET with list of attributes */
-	else if (TailMatches3("UPDATE", MatchAny, "SET"))
+	if (TailMatches3("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* UPDATE <table> SET <attr> = */
-	else if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
+	if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
+	if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
-	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
+	if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
 							" UNION SELECT 'PUBLIC'"
 							" UNION SELECT 'USER'");
-	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
+	if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
+	if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("SERVER");
-	else if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
+	if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /*
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
-	else if (Matches1("VACUUM"))
+	if (Matches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'FULL'"
 								   " UNION SELECT 'FREEZE'"
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches2("VACUUM", "FULL|FREEZE"))
+	if (Matches2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
+	if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
+	if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (Matches2("VACUUM", "VERBOSE"))
+	if (Matches2("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (Matches2("VACUUM", "ANALYZE"))
+	if (Matches2("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (HeadMatches1("VACUUM"))
+	if (HeadMatches1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 /* WITH [RECURSIVE] */
@@ -3110,114 +2807,114 @@ psql_completion(const char *text, int start, int end)
 	 * Only match when WITH is the first word, as WITH may appear in many
 	 * other contexts.
 	 */
-	else if (Matches1("WITH"))
+	if (Matches1("WITH"))
 		COMPLETE_WITH_CONST("RECURSIVE");
 
 /* ANALYZE */
 	/* Complete with list of tables */
-	else if (Matches1("ANALYZE"))
+	if (Matches1("ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
-	else if (TailMatches2(MatchAny, "WHERE"))
+	if (TailMatches2(MatchAny, "WHERE"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
-	else if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
+	if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* ... JOIN ... */
-	else if (TailMatches1("JOIN"))
+	if (TailMatches1("JOIN"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
-	else if (TailMatchesCS1("\\?"))
+	if (TailMatchesCS1("\\?"))
 		COMPLETE_WITH_LIST_CS3("commands", "options", "variables");
-	else if (TailMatchesCS1("\\connect|\\c"))
+	if (TailMatchesCS1("\\connect|\\c"))
 	{
 		if (!recognized_connection_string(text))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 	}
-	else if (TailMatchesCS2("\\connect|\\c", MatchAny))
+	if (TailMatchesCS2("\\connect|\\c", MatchAny))
 	{
 		if (!recognized_connection_string(prev_wd))
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	}
-	else if (TailMatchesCS1("\\da*"))
+	if (TailMatchesCS1("\\da*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
-	else if (TailMatchesCS1("\\dA*"))
+	if (TailMatchesCS1("\\dA*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (TailMatchesCS1("\\db*"))
+	if (TailMatchesCS1("\\db*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-	else if (TailMatchesCS1("\\dD*"))
+	if (TailMatchesCS1("\\dD*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-	else if (TailMatchesCS1("\\des*"))
+	if (TailMatchesCS1("\\des*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
-	else if (TailMatchesCS1("\\deu*"))
+	if (TailMatchesCS1("\\deu*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (TailMatchesCS1("\\dew*"))
+	if (TailMatchesCS1("\\dew*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
-	else if (TailMatchesCS1("\\df*"))
+	if (TailMatchesCS1("\\df*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 
-	else if (TailMatchesCS1("\\dFd*"))
+	if (TailMatchesCS1("\\dFd*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
-	else if (TailMatchesCS1("\\dFp*"))
+	if (TailMatchesCS1("\\dFp*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
-	else if (TailMatchesCS1("\\dFt*"))
+	if (TailMatchesCS1("\\dFt*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
 	/* must be at end of \dF alternatives: */
-	else if (TailMatchesCS1("\\dF*"))
+	if (TailMatchesCS1("\\dF*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
 
-	else if (TailMatchesCS1("\\di*"))
+	if (TailMatchesCS1("\\di*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (TailMatchesCS1("\\dL*"))
+	if (TailMatchesCS1("\\dL*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-	else if (TailMatchesCS1("\\dn*"))
+	if (TailMatchesCS1("\\dn*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-	else if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
+	if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-	else if (TailMatchesCS1("\\ds*"))
+	if (TailMatchesCS1("\\ds*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-	else if (TailMatchesCS1("\\dt*"))
+	if (TailMatchesCS1("\\dt*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-	else if (TailMatchesCS1("\\dT*"))
+	if (TailMatchesCS1("\\dT*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-	else if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
+	if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (TailMatchesCS1("\\dv*"))
+	if (TailMatchesCS1("\\dv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (TailMatchesCS1("\\dx*"))
+	if (TailMatchesCS1("\\dx*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
-	else if (TailMatchesCS1("\\dm*"))
+	if (TailMatchesCS1("\\dm*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (TailMatchesCS1("\\dE*"))
+	if (TailMatchesCS1("\\dE*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
-	else if (TailMatchesCS1("\\dy*"))
+	if (TailMatchesCS1("\\dy*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* must be at end of \d alternatives: */
-	else if (TailMatchesCS1("\\d*"))
+	if (TailMatchesCS1("\\d*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
-	else if (TailMatchesCS1("\\ef"))
+	if (TailMatchesCS1("\\ef"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (TailMatchesCS1("\\ev"))
+	if (TailMatchesCS1("\\ev"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
-	else if (TailMatchesCS1("\\encoding"))
+	if (TailMatchesCS1("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
-	else if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
+	if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
 		COMPLETE_WITH_LIST(sql_commands);
-	else if (TailMatchesCS1("\\l*") && !TailMatchesCS1("\\lo*"))
+	if (TailMatchesCS1("\\l*") && !TailMatchesCS1("\\lo*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-	else if (TailMatchesCS1("\\password"))
+	if (TailMatchesCS1("\\password"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (TailMatchesCS1("\\pset"))
+	if (TailMatchesCS1("\\pset"))
 	{
 		static const char *const my_list[] =
 		{"border", "columns", "expanded", "fieldsep", "fieldsep_zero",
@@ -3228,7 +2925,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (TailMatchesCS2("\\pset", MatchAny))
+	if (TailMatchesCS2("\\pset", MatchAny))
 	{
 		if (TailMatchesCS1("format"))
 		{
@@ -3238,53 +2935,53 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (TailMatchesCS1("linestyle"))
+		if (TailMatchesCS1("linestyle"))
 			COMPLETE_WITH_LIST_CS3("ascii", "old-ascii", "unicode");
-		else if (TailMatchesCS1("unicode_border_linestyle|"
+		if (TailMatchesCS1("unicode_border_linestyle|"
 								"unicode_column_linestyle|"
 								"unicode_header_linestyle"))
 			COMPLETE_WITH_LIST_CS2("single", "double");
 	}
-	else if (TailMatchesCS1("\\unset"))
+	if (TailMatchesCS1("\\unset"))
 	{
-		matches = complete_from_variables(text, "", "", true);
+		return complete_from_variables(text, "", "", true);
 	}
-	else if (TailMatchesCS1("\\set"))
+	if (TailMatchesCS1("\\set"))
 	{
-		matches = complete_from_variables(text, "", "", false);
+		return complete_from_variables(text, "", "", false);
 	}
-	else if (TailMatchesCS2("\\set", MatchAny))
+	if (TailMatchesCS2("\\set", MatchAny))
 	{
 		if (TailMatchesCS1("AUTOCOMMIT|ON_ERROR_STOP|QUIET|"
 						   "SINGLELINE|SINGLESTEP"))
 			COMPLETE_WITH_LIST_CS2("on", "off");
-		else if (TailMatchesCS1("COMP_KEYWORD_CASE"))
+		if (TailMatchesCS1("COMP_KEYWORD_CASE"))
 			COMPLETE_WITH_LIST_CS4("lower", "upper",
 								   "preserve-lower", "preserve-upper");
-		else if (TailMatchesCS1("ECHO"))
+		if (TailMatchesCS1("ECHO"))
 			COMPLETE_WITH_LIST_CS4("errors", "queries", "all", "none");
-		else if (TailMatchesCS1("ECHO_HIDDEN"))
+		if (TailMatchesCS1("ECHO_HIDDEN"))
 			COMPLETE_WITH_LIST_CS3("noexec", "off", "on");
-		else if (TailMatchesCS1("HISTCONTROL"))
+		if (TailMatchesCS1("HISTCONTROL"))
 			COMPLETE_WITH_LIST_CS4("ignorespace", "ignoredups",
 								   "ignoreboth", "none");
-		else if (TailMatchesCS1("ON_ERROR_ROLLBACK"))
+		if (TailMatchesCS1("ON_ERROR_ROLLBACK"))
 			COMPLETE_WITH_LIST_CS3("on", "off", "interactive");
-		else if (TailMatchesCS1("SHOW_CONTEXT"))
+		if (TailMatchesCS1("SHOW_CONTEXT"))
 			COMPLETE_WITH_LIST_CS3("never", "errors", "always");
-		else if (TailMatchesCS1("VERBOSITY"))
+		if (TailMatchesCS1("VERBOSITY"))
 			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
 	}
-	else if (TailMatchesCS1("\\sf*"))
+	if (TailMatchesCS1("\\sf*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (TailMatchesCS1("\\sv*"))
+	if (TailMatchesCS1("\\sv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
+	if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
 							"\\ir|\\include_relative|\\o|\\out|"
 							"\\s|\\w|\\write|\\lo_import"))
 	{
 		completion_charp = "\\";
-		matches = completion_matches(text, complete_from_files);
+		return completion_matches(text, complete_from_files);
 	}
 
 	/*
@@ -3302,7 +2999,7 @@ psql_completion(const char *text, int start, int end)
 			{
 				if (words_after_create[i].query)
 					COMPLETE_WITH_QUERY(words_after_create[i].query);
-				else if (words_after_create[i].squery)
+				if (words_after_create[i].squery)
 					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
 											   NULL);
 				break;
@@ -3310,25 +3007,8 @@ psql_completion(const char *text, int start, int end)
 		}
 	}
 
-	/*
-	 * If we still don't have anything to match we have to fabricate some sort
-	 * of default list. If we were to just return NULL, readline automatically
-	 * attempts filename completion, and that's usually no good.
-	 */
-	if (matches == NULL)
-	{
-		COMPLETE_WITH_CONST("");
-#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
-		rl_completion_append_character = '\0';
-#endif
-	}
-
-	/* free storage */
-	free(previous_words);
-	free(words_buffer);
-
-	/* Return our Grand List O' Matches */
-	return matches;
+	/* We found no match */
+	return NULL;
 }
 
 
-- 
2.9.2

0002-Make-keywords-case-follow-to-input.patchtext/x-patch; charset=us-asciiDownload
From e150c357a8454f5b38e1e62666b224dc4d0d31fc Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Wed, 14 Sep 2016 12:48:16 +0900
Subject: [PATCH 02/17] Make keywords' case follow to input

Currently some keywords suggested along with database objects are
always in upper case. This patch changes the behavior so that the case
of the additional keywords follow the setting of COMP_KEYWORD_CASE.

COMPLETE_WITH_ATTR needs completion_charp to be appendable, so this
patch changes it to a PQExpBuffer and adjust COMPLET_WITH_* macros to
that change.

Since COMPLETE_WITH_(QUERY|SCHEMA_QUERY|ATTR) are not given addons in
most cases so each of them are split into two macros that requires
addons (suffixed by _KW) and that don't.

This leaves keywords contained in Query_for_list_of_grant_roles and
Query_for_enum, but it is another problem.
---
 src/bin/psql/tab-complete-macros.h |  75 ++++++--
 src/bin/psql/tab-complete.c        | 354 +++++++++++++++++++++----------------
 2 files changed, 259 insertions(+), 170 deletions(-)

diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
index 97ffcd1..e0cbf49 100644
--- a/src/bin/psql/tab-complete-macros.h
+++ b/src/bin/psql/tab-complete-macros.h
@@ -231,16 +231,46 @@
  * 5) The list of attributes of the given table (possibly schema-qualified).
  * 6) The list of arguments to the given function (possibly schema-qualified).
  */
-#define COMPLETE_WITH_QUERY(query) \
+#define APPEND_COMP_CHARP(charp) \
+	appendPQExpBufferStr(completion_charp, charp);
+
+#define SET_COMP_CHARP(charp) \
+	resetPQExpBuffer(completion_charp);	\
+	APPEND_COMP_CHARP(charp);
+
+#define COMPLETION_CHARP (completion_charp->data)
+
+#define COMPLETE_WITH_QUERY(query)				\
+do { \
+	SET_COMP_CHARP(query);	\
+	return completion_matches(text, complete_from_query);	\
+} while (0)
+
+/*
+ * COMPLETE_WITH_QUERY with additional keywords. Keywords are complete
+ * case-sensitively
+ */
+#define COMPLETE_WITH_QUERY_KW(query, addon)				\
 do { \
-	completion_charp = query;	\
+	SET_COMP_CHARP(query);	\
+	APPEND_COMP_CHARP(addon); \
 	return completion_matches(text, complete_from_query);	\
 } while (0)
 
-#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
+#define COMPLETE_WITH_SCHEMA_QUERY(query) \
 do { \
 	completion_squery = &(query); \
-	completion_charp = addon; \
+	return completion_matches(text, complete_from_schema_query); \
+} while (0)
+
+/*
+ * COMPLETE_WITH_SCHEMA_QUERY with additional keywords. Keywords are complete
+ * case-sensitively
+ */
+#define COMPLETE_WITH_SCHEMA_QUERY_KW(query, addon)	\
+do { \
+	completion_squery = &(query); \
+	SET_COMP_CHARP(addon); \
 	return completion_matches(text, complete_from_schema_query); \
 } while (0)
 
@@ -260,12 +290,12 @@ do { \
 
 #define COMPLETE_WITH_CONST(string) \
 do { \
-	completion_charp = string;	\
+	SET_COMP_CHARP(string);	\
 	completion_case_sensitive = false; \
 	return completion_matches(text, complete_from_const);	\
 } while (0)
 
-#define COMPLETE_WITH_ATTR(relation, addon) \
+#define COMPLETE_WITH_ATTR_KW(relation, addon) \
 do { \
 	char   *_completion_schema; \
 	char   *_completion_table; \
@@ -278,18 +308,22 @@ do { \
 								false, false, pset.encoding); \
 	if (_completion_table == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_attributes addon; \
+		SET_COMP_CHARP(Query_for_list_of_attributes); \
+		APPEND_COMP_CHARP(addon);					  \
 		completion_info_charp = relation; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_attributes_with_schema addon; \
+		SET_COMP_CHARP(Query_for_list_of_attributes_with_schema); \
+		APPEND_COMP_CHARP(addon); \
 		completion_info_charp = _completion_table; \
 		completion_info_charp2 = _completion_schema; \
 	} \
 	return completion_matches(text, complete_from_query); \
 } while (0)
 
+#define COMPLETE_WITH_ATTR(query) COMPLETE_WITH_ATTR_KW((query), "")
+
 #define COMPLETE_WITH_ENUM_VALUE(type) \
 do { \
 	char   *_completion_schema; \
@@ -303,12 +337,12 @@ do { \
 							   false, false, pset.encoding);  \
 	if (_completion_type == NULL)\
 	{ \
-		completion_charp = Query_for_list_of_enum_values;	\
+		SET_COMP_CHARP(Query_for_list_of_enum_values);	\
 		completion_info_charp = type; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_enum_values_with_schema;	\
+		SET_COMP_CHARP(Query_for_list_of_enum_values_with_schema);	\
 		completion_info_charp = _completion_type; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -328,12 +362,12 @@ do { \
 								   false, false, pset.encoding); \
 	if (_completion_function == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_arguments; \
+		SET_COMP_CHARP(Query_for_list_of_arguments); \
 		completion_info_charp = function; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_arguments_with_schema;	\
+		SET_COMP_CHARP(Query_for_list_of_arguments_with_schema); \
 		completion_info_charp = _completion_function; \
 		completion_info_charp2 = _completion_schema; \
 	} \
@@ -425,6 +459,21 @@ do { \
 	COMPLETE_WITH_LIST_CS(list); \
 } while (0)
 
-
+#define ADDLIST1(s1) additional_kw_query(text, 1, s1)
+#define ADDLIST2(s1, s2) additional_kw_query(text, 2, s1, s2)
+#define ADDLIST3(s1, s2, s3) additional_kw_query(text, 3, s1, s2, s3)
+#define ADDLIST4(s1, s2, s3, s4) \
+	additional_kw_query(text, 4, s1, s2, s3, s4)
+#define ADDLIST6(s1, s2, s3, s4, s5, s6)				\
+	additional_kw_query(text, 6, s1, s2, s3, s4, s5, s6)
+#define ADDLIST13(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13) \
+	additional_kw_query(text, 13, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13)
+#define ADDLIST15(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) \
+	additional_kw_query(text, 15, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13, s14, s15)
+#define ADDLIST16(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16) \
+	additional_kw_query(text, 16, s1, s2, s3, s4, s5, s6, s7,		\
+						s8, s9, s10, s11, s12, s13, s14, s15, s16)
 
 #endif   /* TAB_COMPLETE_MACROS_H */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 95587f7..8842dae 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -126,7 +126,7 @@ static int	completion_max_records;
  * Communication variables set by COMPLETE_WITH_FOO macros and then used by
  * the completion callback functions.  Ugly but there is no better way.
  */
-static const char *completion_charp;	/* to pass a string */
+static PQExpBuffer completion_charp = NULL;		/* to pass a string */
 static const char *const * completion_charpp;	/* to pass a list of strings */
 static const char *completion_info_charp;		/* to pass a second string */
 static const char *completion_info_charp2;		/* to pass a third string */
@@ -500,12 +500,11 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"
 
 #define Query_for_list_of_grant_roles \
-" SELECT pg_catalog.quote_ident(rolname) "\
-"   FROM pg_catalog.pg_roles "\
-"  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"\
-" UNION ALL SELECT 'PUBLIC'"\
-" UNION ALL SELECT 'CURRENT_USER'"\
-" UNION ALL SELECT 'SESSION_USER'"
+	concatenate_strings( \
+		" SELECT pg_catalog.quote_ident(rolname) "	\
+		"   FROM pg_catalog.pg_roles "							\
+		"  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'",	\
+		ADDLIST3("PUBLIC", "CURRENT_USER", "SESSION_USER"))
 
 /* the silly-looking length condition is just to eat up the current word */
 #define Query_for_table_owning_index \
@@ -796,6 +795,8 @@ static char **complete_from_variables(const char *text,
 static char *complete_from_files(const char *text, int state);
 
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
+static char *concatenate_strings(const char *s1, const char *s2);
+static char *additional_kw_query( const char *ref, int n, ...);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
 
@@ -967,7 +968,8 @@ psql_completion(const char *text, int start, int end)
 #endif
 
 	/* Clear a few things. */
-	completion_charp = NULL;
+	if (completion_charp == NULL)
+		completion_charp = createPQExpBuffer();
 	completion_charpp = NULL;
 	completion_info_charp = NULL;
 	completion_info_charp2 = NULL;
@@ -1076,8 +1078,8 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER TABLE */
 	if (Matches2("ALTER", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
+									  ADDLIST1("ALL IN TABLESPACE"));
 
 	/* ALTER something */
 	if (Matches1("ALTER"))
@@ -1193,8 +1195,8 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER INDEX */
 	if (Matches2("ALTER", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+									  ADDLIST1("ALL IN TABLESPACE"));
 	/* ALTER INDEX <name> */
 	if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
@@ -1222,8 +1224,8 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER MATERIALIZED VIEW */
 	if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_matviews,
+									  ADDLIST1("ALL IN TABLESPACE"));
 
 	/* ALTER USER,ROLE <name> */
 	if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
@@ -1396,7 +1398,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
 	if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 	/* ALTER TRIGGER <name> ON <name> */
 	if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
@@ -1442,10 +1444,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	}
 	/* ALTER TABLE xxx INHERIT */
 	if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* ALTER TABLE xxx NO INHERIT */
 	if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* ALTER TABLE xxx DISABLE */
 	if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
 		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
@@ -1462,13 +1464,13 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* ALTER TABLE xxx ALTER */
 	if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
+		COMPLETE_WITH_ATTR_KW(prev2_wd, ADDLIST2("COLUMN", "CONSTRAINT"));
 
 	/* ALTER TABLE xxx RENAME */
 	if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
+		COMPLETE_WITH_ATTR_KW(prev2_wd, ADDLIST3("COLUMN", "CONSTRAINT", "TO"));
 	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 
 	/* ALTER TABLE xxx RENAME yyy */
 	if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
@@ -1483,7 +1485,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
@@ -1634,7 +1636,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * of attributes
 	 */
 	if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
 	if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
@@ -1671,9 +1673,10 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
 	if (Matches1("CLUSTER"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("VERBOSE"));
 	if (Matches2("CLUSTER", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 	/* If we have CLUSTER <sth>, then add "USING" */
 	if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
 		COMPLETE_WITH_CONST("USING");
@@ -1720,7 +1723,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
 	}
 	if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 	if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 	if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
@@ -1735,11 +1738,11 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * backslash command).
 	 */
 	if (Matches1("COPY|\\copy"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION ALL SELECT '('");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
+									  ADDLIST1("("));
 	/* If we have COPY BINARY, complete with list of tables */
 	if (Matches2("COPY", "BINARY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* If we have COPY (, complete it with legal commands */
 	if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
@@ -1751,7 +1754,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
 			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
 	{
-		completion_charp = "";
+		SET_COMP_CHARP("");
 		return completion_matches(text, complete_from_files);
 	}
 
@@ -1820,21 +1823,20 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * existing indexes
 	 */
 	if (TailMatches2("CREATE|UNIQUE", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'ON'"
-								   " UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+									  ADDLIST2("ON", "CONCURRENTLY"));
 	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
 	if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
 			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 
 	/*
 	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
 	 * indexes
 	 */
 	if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'ON'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+									  ADDLIST1("ON"));
 	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
 	if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
 			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
@@ -1849,10 +1851,10 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("(", "USING");
 	if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
 		TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+		COMPLETE_WITH_ATTR(prev2_wd);
 	/* same if you put in USING */
 	if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev4_wd, "");
+		COMPLETE_WITH_ATTR(prev4_wd);
 	/* Complete USING with an index method */
 	if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
@@ -1869,8 +1871,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
 	if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-	/* Complete "CREATE POLICY <name> ON <table> AS|FOR|TO|USING|WITH CHECK" */
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
+	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
 	if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST5("AS", "FOR", "TO", "USING (", "WITH CHECK (");
 	/* CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE */
@@ -1932,7 +1934,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
 	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 /* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
@@ -1988,10 +1990,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * tables
 	 */
 	if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
 	if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("ON", MatchAny))
 		COMPLETE_WITH_LIST7("NOT DEFERRABLE", "DEFERRABLE", "INITIALLY",
 							"REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
@@ -2028,7 +2030,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
 	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("EXECUTE", "PROCEDURE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 
 /* CREATE ROLE,USER,GROUP <name> */
 	if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
@@ -2111,7 +2113,7 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
 	if (TailMatches2("DELETE", "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete DELETE FROM <table> */
 	if (TailMatches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
@@ -2149,10 +2151,10 @@ psql_completion_internal(const char *text, char **previous_words,
 
 	/* DROP INDEX */
 	if (Matches2("DROP", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+									  ADDLIST1("CONCURRENTLY"));
 	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 	if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
@@ -2162,7 +2164,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 
 	/* DROP OWNED BY */
 	if (Matches2("DROP", "OWNED"))
@@ -2268,7 +2270,7 @@ psql_completion_internal(const char *text, char **previous_words,
 /* FOREIGN TABLE */
 	if (TailMatches2("FOREIGN", "TABLE") &&
 			 !TailMatches3("CREATE", MatchAny, MatchAny))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables);
 
 /* FOREIGN SERVER */
 	if (TailMatches2("FOREIGN", "SERVER"))
@@ -2290,20 +2292,10 @@ psql_completion_internal(const char *text, char **previous_words,
 				"DELETE", "TRUNCATE", "REFERENCES", "TRIGGER",
 						"EXECUTE", "USAGE", "ALL");
 		else
-			COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'SELECT'"
-							" UNION SELECT 'INSERT'"
-							" UNION SELECT 'UPDATE'"
-							" UNION SELECT 'DELETE'"
-							" UNION SELECT 'TRUNCATE'"
-							" UNION SELECT 'REFERENCES'"
-							" UNION SELECT 'TRIGGER'"
-							" UNION SELECT 'CREATE'"
-							" UNION SELECT 'CONNECT'"
-							" UNION SELECT 'TEMPORARY'"
-							" UNION SELECT 'EXECUTE'"
-							" UNION SELECT 'USAGE'"
-							" UNION SELECT 'ALL'");
+			COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+				   ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
+							 "REFERENCES", "TRIGGER", "CREATE", "CONNECT",
+							 "TEMPORARY", "EXECUTE", "USAGE", "ALL"));
 	}
 	/*
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
@@ -2339,22 +2331,22 @@ psql_completion_internal(const char *text, char **previous_words,
 		if (HeadMatches3("ALTER","DEFAULT","PRIVILEGES"))
 			COMPLETE_WITH_LIST4("TABLES", "SEQUENCES", "FUNCTIONS", "TYPES");
 		else
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
-								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
-								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
-								   " UNION SELECT 'ALL TABLES IN SCHEMA'"
-								   " UNION SELECT 'DATABASE'"
-								   " UNION SELECT 'DOMAIN'"
-								   " UNION SELECT 'FOREIGN DATA WRAPPER'"
-								   " UNION SELECT 'FOREIGN SERVER'"
-								   " UNION SELECT 'FUNCTION'"
-								   " UNION SELECT 'LANGUAGE'"
-								   " UNION SELECT 'LARGE OBJECT'"
-								   " UNION SELECT 'SCHEMA'"
-								   " UNION SELECT 'SEQUENCE'"
-								   " UNION SELECT 'TABLE'"
-								   " UNION SELECT 'TABLESPACE'"
-								   " UNION SELECT 'TYPE'");
+			COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tsvmf,
+							ADDLIST15("ALL FUNCTIONS IN SCHEMA",
+									  "ALL SEQUENCES IN SCHEMA",
+									  "ALL TABLES IN SCHEMA",
+									  "DATABASE",
+									  "DOMAIN",
+									  "FOREIGN DATA WRAPPER",
+									  "FOREIGN SERVER",
+									  "FUNCTION",
+									  "LANGUAGE",
+									  "LARGE OBJECT",
+									  "SCHEMA",
+									  "SEQUENCE",
+									  "TABLE",
+									  "TABLESPACE",
+									  "TYPE"));
 	}
 	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
@@ -2374,21 +2366,21 @@ psql_completion_internal(const char *text, char **previous_words,
 		if (TailMatches1("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 		if (TailMatches1("DOMAIN"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains);
 		if (TailMatches1("FUNCTION"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 		if (TailMatches1("LANGUAGE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
 		if (TailMatches1("SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
 		if (TailMatches1("SEQUENCE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
 		if (TailMatches1("TABLE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf);
 		if (TailMatches1("TABLESPACE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 		if (TailMatches1("TYPE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 		if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
@@ -2454,10 +2446,10 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
 	if (TailMatches2("INSERT", "INTO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete "INSERT INTO <table> (" with attribute names */
 	if (TailMatches4("INSERT", "INTO", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+		COMPLETE_WITH_ATTR(prev2_wd);
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
@@ -2481,10 +2473,10 @@ psql_completion_internal(const char *text, char **previous_words,
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
 	if (Matches1("LOCK"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION SELECT 'TABLE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
+									  ADDLIST1("TABLE"));
 	if (Matches2("LOCK", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 	/* For the following, handle the case of a single table only for now */
 
@@ -2529,7 +2521,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatches3("FROM", MatchAny, "ORDER"))
 		COMPLETE_WITH_CONST("BY");
 	if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 
 /* PREPARE xx AS */
 	if (Matches3("PREPARE", MatchAny, "AS"))
@@ -2558,10 +2550,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_matviews,
+									  ADDLIST1("CONCURRENTLY"));
 	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 	if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
 	if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
@@ -2579,9 +2571,9 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
 	if (Matches2("REINDEX", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 	if (Matches2("REINDEX", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	if (Matches2("REINDEX", "SCHEMA"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
 	if (Matches2("REINDEX", "SYSTEM|DATABASE"))
@@ -2651,7 +2643,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
 	if (Matches2("SET", "CONSTRAINTS"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_constraints_with_schema,
+									  ADDLIST1("ALL"));
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
 	if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
@@ -2663,7 +2656,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
 	if (Matches3("SET", "SESSION", "AUTHORIZATION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+							   ADDLIST1("DEFAULT"));
 	/* Complete RESET SESSION with AUTHORIZATION */
 	if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
@@ -2689,10 +2683,10 @@ psql_completion_internal(const char *text, char **previous_words,
 			COMPLETE_WITH_LIST(my_list);
 		}
 		if (TailMatches2("search_path", "TO|="))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
-								" AND nspname not like 'pg\\_toast%%' "
-								" AND nspname not like 'pg\\_temp%%' "
-								" UNION SELECT 'DEFAULT' ");
+			COMPLETE_WITH_QUERY_KW(Query_for_list_of_schemas
+								   " AND nspname not like 'pg\\_toast%%' "
+								   " AND nspname not like 'pg\\_temp%%' ",
+								   ADDLIST1("DEFAULT"));
 		else
 		{
 			/* generic, type based, GUC support, guctype is malloc'ed */
@@ -2727,7 +2721,7 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* TABLE, but not TABLE embedded in other commands */
 	if (Matches1("TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations);
 
 /* TABLESAMPLE */
 	if (TailMatches1("TABLESAMPLE"))
@@ -2737,7 +2731,7 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* TRUNCATE */
 	if (Matches1("TRUNCATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 /* UNLISTEN */
 	if (Matches1("UNLISTEN"))
@@ -2746,13 +2740,13 @@ psql_completion_internal(const char *text, char **previous_words,
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
 	if (TailMatches1("UPDATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete UPDATE <table> with "SET" */
 	if (TailMatches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
 	/* Complete UPDATE <table> SET with list of attributes */
 	if (TailMatches3("UPDATE", MatchAny, "SET"))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+		COMPLETE_WITH_ATTR(prev2_wd);
 	/* UPDATE <table> SET <attr> = */
 	if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
@@ -2761,10 +2755,8 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
 	if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'CURRENT_USER'"
-							" UNION SELECT 'PUBLIC'"
-							" UNION SELECT 'USER'");
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+			ADDLIST3("CURRENT_USER", "PUBLIC", "USER"));
 	if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
 	if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
@@ -2777,29 +2769,26 @@ psql_completion_internal(const char *text, char **previous_words,
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
 	if (Matches1("VACUUM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'FULL'"
-								   " UNION SELECT 'FREEZE'"
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST4("FULL", "FREEZE",
+											   "ANALYZE", "VERBOSE"));
 	if (Matches2("VACUUM", "FULL|FREEZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST2("ANALYZE", "VERBOSE"));
 	if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("VERBOSE"));
 	if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("ANALYZE"));
 	if (Matches2("VACUUM", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("ANALYZE"));
 	if (Matches2("VACUUM", "ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
+									  ADDLIST1("VERBOSE"));
 	if (HeadMatches1("VACUUM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 
 /* WITH [RECURSIVE] */
 
@@ -2813,21 +2802,21 @@ psql_completion_internal(const char *text, char **previous_words,
 /* ANALYZE */
 	/* Complete with list of tables */
 	if (Matches1("ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf);
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
 	if (TailMatches2(MatchAny, "WHERE"))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+		COMPLETE_WITH_ATTR(prev2_wd);
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
 	if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf);
 
 /* ... JOIN ... */
 	if (TailMatches1("JOIN"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf);
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
@@ -2844,13 +2833,13 @@ psql_completion_internal(const char *text, char **previous_words,
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	}
 	if (TailMatchesCS1("\\da*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates);
 	if (TailMatchesCS1("\\dA*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 	if (TailMatchesCS1("\\db*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	if (TailMatchesCS1("\\dD*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains);
 	if (TailMatchesCS1("\\des*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
 	if (TailMatchesCS1("\\deu*"))
@@ -2858,7 +2847,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatchesCS1("\\dew*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 	if (TailMatchesCS1("\\df*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 
 	if (TailMatchesCS1("\\dFd*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
@@ -2871,40 +2860,40 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
 
 	if (TailMatchesCS1("\\di*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	if (TailMatchesCS1("\\dL*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
 	if (TailMatchesCS1("\\dn*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
 	if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf);
 	if (TailMatchesCS1("\\ds*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
 	if (TailMatchesCS1("\\dt*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	if (TailMatchesCS1("\\dT*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 	if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	if (TailMatchesCS1("\\dv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	if (TailMatchesCS1("\\dx*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
 	if (TailMatchesCS1("\\dm*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 	if (TailMatchesCS1("\\dE*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables);
 	if (TailMatchesCS1("\\dy*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* must be at end of \d alternatives: */
 	if (TailMatchesCS1("\\d*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations);
 
 	if (TailMatchesCS1("\\ef"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 	if (TailMatchesCS1("\\ev"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 
 	if (TailMatchesCS1("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
@@ -2973,14 +2962,14 @@ psql_completion_internal(const char *text, char **previous_words,
 			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
 	}
 	if (TailMatchesCS1("\\sf*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
 	if (TailMatchesCS1("\\sv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
 							"\\ir|\\include_relative|\\o|\\out|"
 							"\\s|\\w|\\write|\\lo_import"))
 	{
-		completion_charp = "\\";
+		SET_COMP_CHARP("\\");
 		return completion_matches(text, complete_from_files);
 	}
 
@@ -3000,8 +2989,7 @@ psql_completion_internal(const char *text, char **previous_words,
 				if (words_after_create[i].query)
 					COMPLETE_WITH_QUERY(words_after_create[i].query);
 				if (words_after_create[i].squery)
-					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
-											   NULL);
+					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery);
 				break;
 			}
 		}
@@ -3248,13 +3236,13 @@ _complete_from_query(int is_schema_query, const char *text, int state)
 							  char_length, e_text);
 
 			/* If an addon query was provided, use it */
-			if (completion_charp)
-				appendPQExpBuffer(&query_buffer, "\n%s", completion_charp);
+			if (COMPLETION_CHARP[0])
+				appendPQExpBuffer(&query_buffer, "\n%s", COMPLETION_CHARP);
 		}
 		else
 		{
 			/* completion_charp is an sprintf-style format string */
-			appendPQExpBuffer(&query_buffer, completion_charp,
+			appendPQExpBuffer(&query_buffer, COMPLETION_CHARP,
 							  char_length, e_text,
 							  e_info_charp, e_info_charp,
 							  e_info_charp2, e_info_charp2);
@@ -3369,18 +3357,17 @@ complete_from_list(const char *text, int state)
 static char *
 complete_from_const(const char *text, int state)
 {
-	Assert(completion_charp != NULL);
 	if (state == 0)
 	{
 		if (completion_case_sensitive)
-			return pg_strdup(completion_charp);
+			return pg_strdup(COMPLETION_CHARP);
 		else
 
 			/*
 			 * If case insensitive matching was requested initially, adjust
 			 * the case according to setting.
 			 */
-			return pg_strdup_keyword_case(completion_charp, text);
+			return pg_strdup_keyword_case(COMPLETION_CHARP, text);
 	}
 	else
 		return NULL;
@@ -3481,7 +3468,7 @@ complete_from_files(const char *text, int state)
 	if (state == 0)
 	{
 		/* Initialization: stash the unquoted input. */
-		unquoted_text = strtokx(text, "", NULL, "'", *completion_charp,
+		unquoted_text = strtokx(text, "", NULL, "'", COMPLETION_CHARP[0],
 								false, true, pset.encoding);
 		/* expect a NULL return for the empty string only */
 		if (!unquoted_text)
@@ -3502,7 +3489,7 @@ complete_from_files(const char *text, int state)
 		 * bother providing a macro to simplify this.
 		 */
 		ret = quote_if_needed(unquoted_match, " \t\r\n\"`",
-							  '\'', *completion_charp, pset.encoding);
+							  '\'', COMPLETION_CHARP[0], pset.encoding);
 		if (ret)
 			free(unquoted_match);
 		else
@@ -3546,6 +3533,59 @@ pg_strdup_keyword_case(const char *s, const char *ref)
 	return ret;
 }
 
+/*
+ * Returns concatenated string.
+ * Note: the internal buffer is static so this cannot be called recursively.
+ */
+static char *
+concatenate_strings(const char *s1, const char *s2)
+{
+	static PQExpBuffer qbuf = NULL;
+
+	if (qbuf == NULL)
+		qbuf = createPQExpBuffer();
+	else
+		resetPQExpBuffer(qbuf);
+
+	appendPQExpBufferStr(qbuf, s1);
+	appendPQExpBufferStr(qbuf, s2);
+
+	return qbuf->data;
+}
+
+/* Construct codelet to append given keywords  */
+static char *
+additional_kw_query(const char *ref, int n, ...)
+{
+	va_list ap;
+	static PQExpBuffer qbuf = NULL;
+	int i;
+
+	if (qbuf == NULL)
+		qbuf = createPQExpBuffer();
+	else
+		resetPQExpBuffer(qbuf);
+
+	/* Construct an additional queriy to append keywords */
+	appendPQExpBufferStr(qbuf, " UNION ALL SELECT * FROM (VALUES ");
+
+	va_start(ap, n);
+	for (i = 0 ; i < n ; i++)
+	{
+		char *item = pg_strdup_keyword_case(va_arg(ap, char *), ref);
+		if (i > 0) appendPQExpBufferChar(qbuf, ',');
+		appendPQExpBufferStr(qbuf, "('");
+		appendPQExpBufferStr(qbuf, item);
+		appendPQExpBufferStr(qbuf, "')");
+		pg_free(item);
+	}
+	va_end(ap);
+
+	appendPQExpBufferStr(qbuf, ") as x");
+
+	return qbuf->data;
+}
+
 
 /*
  * escape_string - Escape argument for use as string literal.
-- 
2.9.2

0003-Introduce-word-shift-and-removal-feature-to-psql-com.patchtext/x-patch; charset=us-asciiDownload
From c9ae5408c8e5dd64b02200f70efe2d2e14a14ef0 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 14:22:53 +0900
Subject: [PATCH 03/17] Introduce word shift and removal feature to
 psql-completion

Currently completion of psql is sensitive to noise words such like
temp/temporary, unlogged or concurrent. Addition to that, schema
elemsnts in CREATE SCHEMA syntax or some recursive syntaxes are
processed in somewhat bogus way.  To deal with such cases in simpler
way, this patch introduces two features.

1. Add a feature to ignore leading words to process.  New macros
  HEAD_SHIFT, HEAD_SET to shift or set the position of the first word
  to match using other macros. All *MatchesN macros follow
  this. SHIFT_TO_LAST1 is a macro to shift the head to the position
  where the specified word is found last.

2. Add a feature to remove intermediate words from previous_words
  list.  COLLAPSE(s, n) macro removes n words from the s'th position
  (1-based). Removing "noise" words let the succeeding operations
  simple.

This patch doesn't make any behavioral change.
---
 src/bin/psql/tab-complete-macros.h | 165 ++++++++++++++++++++++++++++---------
 src/bin/psql/tab-complete.c        |  51 +++++++++---
 2 files changed, 169 insertions(+), 47 deletions(-)

diff --git a/src/bin/psql/tab-complete-macros.h b/src/bin/psql/tab-complete-macros.h
index e0cbf49..48c9327 100644
--- a/src/bin/psql/tab-complete-macros.h
+++ b/src/bin/psql/tab-complete-macros.h
@@ -25,41 +25,67 @@
 #define prev8_wd  (previous_words[7])
 #define prev9_wd  (previous_words[8])
 
+/* Return the number of stored words counting head shift */
+#define WORD_COUNT() (previous_words_count - head_shift)
+
 /*
  * Return the index in previous_words for index from the beginning. n is
  * 1-based and the result is 0-based.
  */
-#define HEAD_INDEX(n) \
-	(previous_words_count - (n))
+#define HEAD_INDEX(n) (WORD_COUNT() - (n))
+
+/* Move the position of the beginning word for matching macros.  */
+#define HEAD_SHIFT(n) (head_shift += (n))
+
+/* Set the position of the beginning word for matching macros.  */
+#define HEAD_SET(n) (head_shift = (n))
+
+/*
+ * remove n words from current shifted position. This moves entire the
+ * previous_words regardless of head_shift.
+ */
+#define COLLAPSE(s, n)							\
+	(memmove(previous_words + HEAD_INDEX((s) + (n) - 1), \
+			 previous_words + HEAD_INDEX((s) - 1),		 \
+			 sizeof(char *) *								\
+			 (previous_words_count - HEAD_INDEX((s) - 1))),	\
+	 previous_words_count -= (n))
+
+/*
+ * Find the position where the specified word appears last and shift to there.
+ * The words before the position will be ignored ever after.
+ */
+#define SHIFT_TO_LAST1(p1) \
+	HEAD_SHIFT(find_last_index_of(p1, previous_words, previous_words_count))
 
 /*
  * Macros for matching the last N words before point, and after head_sift,
  * case-insensitively.
  */
 #define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches(p1, prev_wd))
 
 #define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd))
 
 #define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
+	(WORD_COUNT() >= 3 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd))
 
 #define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
+	(WORD_COUNT() >= 4 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
 	 word_matches(p4, prev4_wd))
 
 #define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
+	(WORD_COUNT() >= 5 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -67,7 +93,7 @@
 	 word_matches(p5, prev5_wd))
 
 #define TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
+	(WORD_COUNT() >= 6 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -76,7 +102,7 @@
 	 word_matches(p6, prev6_wd))
 
 #define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
+	(WORD_COUNT() >= 7 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -86,7 +112,7 @@
 	 word_matches(p7, prev7_wd))
 
 #define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
+	(WORD_COUNT() >= 8 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -97,7 +123,7 @@
 	 word_matches(p8, prev8_wd))
 
 #define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
+	(WORD_COUNT() >= 9 && \
 	 word_matches(p1, prev_wd) && \
 	 word_matches(p2, prev2_wd) && \
 	 word_matches(p3, prev3_wd) && \
@@ -113,10 +139,10 @@
 	 * head_shift, case-sensitively.
 	 */
 #define TailMatchesCS1(p1) \
-	(previous_words_count >= 1 && \
+	(WORD_COUNT() >= 1 && \
 	 word_matches_cs(p1, prev_wd))
 #define TailMatchesCS2(p2, p1) \
-	(previous_words_count >= 2 && \
+	(WORD_COUNT() >= 2 && \
 	 word_matches_cs(p1, prev_wd) && \
 	 word_matches_cs(p2, prev2_wd))
 
@@ -125,31 +151,31 @@
 	 * case-insensitively.
 	 */
 #define Matches1(p1) \
-	(previous_words_count == 1 && \
+	(WORD_COUNT() == 1 && \
 	 TailMatches1(p1))
 #define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
+	(WORD_COUNT() == 2 && \
 	 TailMatches2(p1, p2))
 #define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
+	(WORD_COUNT() == 3 && \
 	 TailMatches3(p1, p2, p3))
 #define Matches4(p1, p2, p3, p4) \
-	(previous_words_count == 4 && \
+	(WORD_COUNT() == 4 && \
 	 TailMatches4(p1, p2, p3, p4))
 #define Matches5(p1, p2, p3, p4, p5) \
-	(previous_words_count == 5 && \
+	(WORD_COUNT() == 5 && \
 	 TailMatches5(p1, p2, p3, p4, p5))
 #define Matches6(p1, p2, p3, p4, p5, p6) \
-	(previous_words_count == 6 && \
+	(WORD_COUNT() == 6 && \
 	 TailMatches6(p1, p2, p3, p4, p5, p6))
 #define Matches7(p1, p2, p3, p4, p5, p6, p7) \
-	(previous_words_count == 7 && \
+	(WORD_COUNT() == 7 && \
 	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
 #define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
-	(previous_words_count == 8 && \
+	(WORD_COUNT() == 8 && \
 	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
 #define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
-	(previous_words_count == 9 && \
+	(WORD_COUNT() == 9 && \
 	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
 
 /*
@@ -195,16 +221,39 @@
 	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
 	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]))
 
-#define MidMatches7(s,p1, p2, p3, p4, p5, p6, p7)	\
+#define MidMatches7(s,p1, p2, p3, p4, p5, p6, p7)			\
 	(HEAD_INDEX((s) + 6) >= 0 &&							\
-	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&	\
-	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&	\
-	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&			\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&		\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&			\
 	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&			\
 	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&			\
 	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&			\
 	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]))
 
+#define MidMatches8(s,p1, p2, p3, p4, p5, p6, p7, p8)		\
+	(HEAD_INDEX((s) + 7) >= 0 &&							\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&				\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&		\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&		\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&		\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&		\
+	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]) &&		\
+	 word_matches(p8, previous_words[HEAD_INDEX((s) + 7)]))
+
+#define MidMatches9(s,p1, p2, p3, p4, p5, p6, p7, p8, p9)		\
+	(HEAD_INDEX((s) + 8) >= 0 &&							\
+	 word_matches(p1, previous_words[HEAD_INDEX(s)]) &&				\
+	 word_matches(p2, previous_words[HEAD_INDEX((s) + 1)]) &&		\
+	 word_matches(p3, previous_words[HEAD_INDEX((s) + 2)]) &&		\
+	 word_matches(p4, previous_words[HEAD_INDEX((s) + 3)]) &&		\
+	 word_matches(p5, previous_words[HEAD_INDEX((s) + 4)]) &&		\
+	 word_matches(p6, previous_words[HEAD_INDEX((s) + 5)]) &&		\
+	 word_matches(p7, previous_words[HEAD_INDEX((s) + 6)]) &&		\
+	 word_matches(p8, previous_words[HEAD_INDEX((s) + 7)]) &&		\
+	 word_matches(p9, previous_words[HEAD_INDEX((s) + 8)]))
+
 #define HeadMatches1(p1) \
 	MidMatches1(1, p1)
 #define HeadMatches2(p1, p2) \
@@ -219,6 +268,41 @@
 	MidMatches6(1, p1, p2, p3, p4, p5, p6)
 #define HeadMatches7(p1, p2, p3, p4, p5, p6, p7) \
 	MidMatches7(1, p1, p2, p3, p4, p5, p6, p7)
+#define HeadMatches8(p1, p2, p3, p4, p5, p6, p7, p8)	\
+	MidMatches8(1, p1, p2, p3, p4, p5, p6, p7, p8)
+#define HeadMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9)	\
+	MidMatches9(1, p1, p2, p3, p4, p5, p6, p7, p8, p9)
+
+#define HeadMatchAndRemove1(s, l, p1)			\
+	((WORD_COUNT() >= s + l - 1 && HeadMatches1(p1))? \
+	 COLLAPSE(s, l), true : false)
+#define HeadMatchAndRemove2(s, l, p1, p2)		\
+	((WORD_COUNT() >= s + l - 1 && HeadMatches2(p1, p2)) ?	\
+	 COLLAPSE(s, l), true : false)
+#define HeadMatchAndRemove3(s, l, p1, p2, p3)							\
+	((WORD_COUNT() >= s + l - 1 && HeadMatches3(p1, p2, p3)) ?		\
+	 COLLAPSE(s, l), true : false)
+#define HeadMatchAndRemove4(s, l, p1, p2, p3, p4)	\
+	((WORD_COUNT() >= s + l - 1 && HeadMatches4(p1, p2, p3, p4)) ? \
+	 COLLAPSE(s, l), true : false)
+#define HeadMatchAndRemove5(s, l, p1, p2, p3, p4, p5)					\
+	((WORD_COUNT() >= s + l - 1 && HeadMatches5(p1, p2, p3, p4, p5))? \
+	 COLLAPSE(s, l), true : false)
+#define HeadMatchAndRemove6(s, l, p1, p2, p3, p4, p5, p6)				\
+	((WORD_COUNT() >= s + l - 1 && HeadMatches6(p1, p2, p3, p4, p5, p6))? \
+	 COLLAPSE(s, l), true : false)
+#define HeadMatchAndRemove7(s, l, p1, p2, p3, p4, p5, p6, p7)			\
+	((WORD_COUNT() >= s + l - 1 &&									\
+			HeadMatches7(p1, p2, p3, p4, p5, p6, p7)) ?					\
+	 COLLAPSE(s, l), true : false)
+#define HeadMatchAndRemove8(s, l, p1, p2, p3, p4, p5, p6, p7, p8)		\
+	((WORD_COUNT() >= s + l - 1 &&									\
+			HeadMatches8(p1, p2, p3, p4, p5, p6, p7, p8)) ?				\
+	 COLLAPSE(s, l), true : false)
+#define HeadMatchAndRemove9(s, l, p1, p2, p3, p4, p5, p6, p7, p8, p9)	\
+	((WORD_COUNT() >= s + l - 1 &&									\
+			HeadMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9)) ?			\
+	 COLLAPSE(s, l), true : false)
 
 /*
  * A few macros to ease typing. You can use these to complete the given
@@ -240,12 +324,6 @@
 
 #define COMPLETION_CHARP (completion_charp->data)
 
-#define COMPLETE_WITH_QUERY(query)				\
-do { \
-	SET_COMP_CHARP(query);	\
-	return completion_matches(text, complete_from_query);	\
-} while (0)
-
 /*
  * COMPLETE_WITH_QUERY with additional keywords. Keywords are complete
  * case-sensitively
@@ -257,11 +335,7 @@ do { \
 	return completion_matches(text, complete_from_query);	\
 } while (0)
 
-#define COMPLETE_WITH_SCHEMA_QUERY(query) \
-do { \
-	completion_squery = &(query); \
-	return completion_matches(text, complete_from_schema_query); \
-} while (0)
+#define COMPLETE_WITH_QUERY(query) COMPLETE_WITH_QUERY_KW((query), "")
 
 /*
  * COMPLETE_WITH_SCHEMA_QUERY with additional keywords. Keywords are complete
@@ -274,6 +348,8 @@ do { \
 	return completion_matches(text, complete_from_schema_query); \
 } while (0)
 
+#define COMPLETE_WITH_SCHEMA_QUERY(query) COMPLETE_WITH_SCHEMA_QUERY_KW((query), "")
+
 #define COMPLETE_WITH_LIST_CS(list) \
 do { \
 	completion_charpp = list; \
@@ -476,4 +552,19 @@ do { \
 	additional_kw_query(text, 16, s1, s2, s3, s4, s5, s6, s7,		\
 						s8, s9, s10, s11, s12, s13, s14, s15, s16)
 
+#define COMPLETE_THING_KW(p, addon)					\
+do { \
+	const pgsql_thing_t *ent = find_thing_entry(previous_words[-(p) - 1]);	\
+	if (ent) \
+	{ \
+		if (ent->query) \
+			COMPLETE_WITH_QUERY_KW(ent->query, (addon));	\
+		else if (ent->squery) \
+			COMPLETE_WITH_SCHEMA_QUERY_KW(*ent->squery, (addon));	\
+	} \
+	return NULL; \
+} while (0)
+
+#define COMPLETE_THING(p) COMPLETE_THING_KW(p, "")
+
 #endif   /* TAB_COMPLETE_MACROS_H */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8842dae..c14619a 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -793,6 +793,7 @@ static void append_variable_names(char ***varnames, int *nvars,
 static char **complete_from_variables(const char *text,
 					const char *prefix, const char *suffix, bool need_value);
 static char *complete_from_files(const char *text, int state);
+static int find_last_index_of(char *w, char **previous_words, int len);
 
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static char *concatenate_strings(const char *s1, const char *s2);
@@ -806,6 +807,7 @@ static char *get_guctype(const char *varname);
 
 static char **psql_completion_internal(const char *text, char **previous_words,
 										   int previous_words_count);
+static const pgsql_thing_t *find_thing_entry(char *word);
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -1014,6 +1016,9 @@ static char **
 psql_completion_internal(const char *text, char **previous_words,
 						 int previous_words_count)
 {
+	/* The number of prefixing words to be ignored */
+	int			head_shift = 0;
+
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
@@ -2980,18 +2985,14 @@ psql_completion_internal(const char *text, char **previous_words,
 	 */
 	else
 	{
-		int			i;
+		const pgsql_thing_t *ent = find_thing_entry(prev_wd);
 
-		for (i = 0; words_after_create[i].name; i++)
+		if (ent)
 		{
-			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
-			{
-				if (words_after_create[i].query)
-					COMPLETE_WITH_QUERY(words_after_create[i].query);
-				if (words_after_create[i].squery)
-					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery);
-				break;
-			}
+			if (ent->query)
+				COMPLETE_WITH_QUERY(ent->query);
+			else if (ent->squery)
+				COMPLETE_WITH_SCHEMA_QUERY(*ent->squery);
 		}
 	}
 
@@ -3502,6 +3503,18 @@ complete_from_files(const char *text, int state)
 
 /* HELPER FUNCTIONS */
 
+/*
+ * Return the index (reverse to the index of previous_words) of the tailmost
+ * (topmost in the array) appearance of w.
+ */
+static int
+find_last_index_of(char *w, char **previous_words, int len)
+{
+	int i;
+
+	for (i = 0 ; i < len && !word_matches(w, previous_words[i]) ; i++);
+	return i < len ? (len - i - 1) : 0;
+}
 
 /*
  * Make a pg_strdup copy of s and convert the case according to
@@ -3801,6 +3814,24 @@ get_guctype(const char *varname)
 	return guctype;
 }
 
+/*
+ * Finds the entry in words_after_create[] that matches the word.
+ * NULL if not found.
+ */
+static const pgsql_thing_t *
+find_thing_entry(char *word)
+{
+	int			i;
+
+	for (i = 0; words_after_create[i].name; i++)
+	{
+		if (pg_strcasecmp(word, words_after_create[i].name) == 0)
+			return words_after_create + i;
+	}
+
+	return NULL;
+}
+
 #ifdef NOT_USED
 
 /*
-- 
2.9.2

0004-Add-README-for-tab-completion.patchtext/x-patch; charset=us-asciiDownload
From f670fa5394206175d1ab04f9feec28af423d9f81 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Wed, 14 Dec 2016 14:52:27 +0900
Subject: [PATCH 04/17] Add README for tab-completion

---
 src/bin/psql/README.completion | 169 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 169 insertions(+)
 create mode 100644 src/bin/psql/README.completion

diff --git a/src/bin/psql/README.completion b/src/bin/psql/README.completion
new file mode 100644
index 0000000..f3cefd0
--- /dev/null
+++ b/src/bin/psql/README.completion
@@ -0,0 +1,169 @@
+Word completion of interactive psql
+===================================
+
+psql supports word completion on interactive input. The core function
+of the feature is psql_completion_internal in tab-complete.c. A bunch
+of macros are provided in order to make it easier to read and maintain
+the completion code. psql_completion is called with reference console
+input stored in char ** previous_words in reverse order but developers
+don't need to be aware of the detail. Most of the operations can be
+described using the provided macros.
+
+Basic structure of the completion code
+--------------------------------------
+
+The main part of the function is just a series of completion
+definitions, where the first match wins. Each definition basically is
+in the following shape.
+
+   if (*matching expression*)
+      *enumeration of words for completion, then return*
+
+The matching expression is examined against previous_words which
+contains the whole command line before the last space. The completion
+code enumerates the expected words. For example, for "CREATE <tab>"
+the word list to be matched is ["CREATE"] and the prefix for
+completion is nothing. For "CREATE INDEX i", the list is ["CREATE",
+"INDEX"] and the prefix for enumeration is "i".
+
+
+Matching expression macros
+--------------------------
+There are four types of matching expression macros.
+
+- MatchesN(word1, word2 .. , wordN)
+
+ true iff the word list is exactly the same as the paremeter.
+
+- HeadMatchesN(word1, word2 .., wordN)
+
+ true iff the first N words in the word list matches the parameter.
+
+- TailMatchesN(word1, word2 .., wordN)
+
+ true iff the last N words in the word list matches the parameter.
+
+- MidMatchesN(pos, word1, word2 .., wordN)
+
+ true iff N successive words starts from pos in the word list matches
+ the parameter. The position is 1-based.
+
+Special matching words
+----------------------
+
+A defined symbol MatchAny matches any word. If you want to match any
+of several words, multiple words concatenated by '|' can be
+used. "CREATE|UPDATE" matches any of "CREATE" and "UPDATE".
+
+
+Enumeration macros
+-----------------
+There are N types of word enumeration macros.
+
+- COMPLETE_WITH_QUERY(query), COMPLETE_WITH_QUERY_KW(query, addon)
+
+  Suggests completion words acquired by using the given query. The
+  query details are seen in the comment for _complete_from_query().
+  Word matching is case-sensitive.
+
+  The latter form takes an additional parameter, which should be a
+  fragment of query starts with " UNION " followed by a query string
+  which gives some additional words. For case-insensitive suggestion,
+  this could be as simple as a static query string but for
+  case-sensitive cases of the letter form where the suggested word
+  case follows input, ADDLISTN() macro can be used.
+
+- COMPLETE_WITH_SCHEMA_QUERY(squery),
+  COMPLETE_WITH_SCHEMA_QUERY_KW(squery, addon)
+
+  Suggests words based on a "schema query", which is a struct that
+  containing parameters. You will see the details in the comment for
+  _complete_from_query(). Word maching is case-sensitive.
+
+  Just same as COMPLETE_WITH_QUERY_KW, the latter form takes a
+  fragment query same to that for COMPLETE_WITH_QUERY_KW.
+
+- COMPLETE_WITH_LIST_CS(list)
+
+  Suggests completion words given as a string array. Word matching is
+  case-sensitive.
+
+- COMPLETE_WITH_LIST_CSN(s1, s2.. ,sN)
+
+  Shortcut for COMPLETE_WITH_LIST_CS.
+
+- COMPLETE_WITH_LIST(list)
+
+  Same as COMPLETE_WITH_LIST_CS except that word matching is
+  case-insensitive and the letter case of suggested words is
+  determined according to COMP_KEYWORD_CASE.
+
+- COMPLETE_WITH_LISTN(s1, s2.. ,sN)
+
+  Shortcut for COMPLETE_WITH_LIST.
+
+- COMPLETE_WITH_CONST(string)
+
+  Same as COMPLETE_WITH_LIST but with just one suggestion.
+
+- COMPLETE_WITH_ATTR(relation), COMPLETE_WITH_ATTR_KW(relation, addon)
+
+  Suggests attribute names for the given relation. Word matching is
+  case-sensitve.
+
+- COMPLETE_WITH_FUNCTION_ARG(function)
+
+  Suggests function name for the given SQL function. Word matching is
+  case-sensitve.
+
+- COMPLETE_THING(relpos), COMPLETE_THING_KW(relpos, addon)
+
+  Suggests any object name designated by the word at relpos from the
+  current word. COMPLETE_THING(-1) for "... TABLE " enumerates names
+  of all available tables.
+
+Additional keywords for COMPLETE_WITH(_SCHEMA)_QUERY
+----------------------------------------------------
+
+Some syntaxes need suggestion by mixture of object names and
+keywords. Object names are enumarated with
+COMPLETE_WITH(_SCHEMA)_QUERY but keywords should be added manually
+onto them. COMPLETE_WITH(_SCHEMA)_QUERY_KW takes a fragment query that
+gives keywords to be suggested with the object names. Since the
+framgent queriy is just appended to the main query so it is in the
+form of ' UNION <any sql>' that adds arbitrary number of tuples
+contain a text value. For example,
+
+" UNION ALL SELECT 'CURRENT_USER' UNION ALL SELECT 'PUBLIC'"
+
+If you want the case of the keywords to follow input, ADDLISTN() macro
+provides a fragment query containing such keywords.
+
+ADDLIST3("CURRENT_USER", "PUBLIC", "USER") for input 'c' returns a
+fragment query equivalent to the following,
+
+" UNION ALL SELECT 'current_user' UNION ALL SELECT 'public'"
+
+
+Removing "NOISE" words
+----------------------------------------------------
+
+Many syntaxes has "NOISE" words, that is, words that has no effect on
+the following completion. For example, the existence of the second
+word in "CREATE UNIQUE INDEX" makes no difference for further
+completion behavior. Removing such words makes further completion code
+simpler.
+
+COLLAPSE(s, l) removes l words starts from s from previous_words.
+
+if (Matches("CREATE", "UNIQUE", "INDEX"))
+   COLLAPSE(2, 1);
+
+After the above code, the previous_words is ["CREATE", "INDEX"] so the
+following completion definitions need not care about the removed words.
+
+COLLAPSE is used after HeadMatches for most cases. HeadMatchAndRemoveN()
+macros are the composite macros. They are equivalents to the following
+and returns true if the condition meets.
+
+HeadMatchAndRemoveN() === if (HeadMatchesN(s, l, p1, ...)) COLLAPSE(s, l);
-- 
2.9.2

0005-Make-SET-RESET-SHOW-varialble-follow-input-letter-ca.patchtext/x-patch; charset=us-asciiDownload
From f9017090b37bf0387164986d3c155d0ca9ee6ecd Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Wed, 14 Dec 2016 15:22:31 +0900
Subject: [PATCH 05/17] Make SET|RESET|SHOW varialble follow input letter case.

Query_for_list_of_set_vars and _show_vars gives inherent keywords in
upper case. Separate the queries and keywords and make the keywords
follow the input.
---
 src/bin/psql/tab-complete.c | 32 ++++++++++++--------------------
 1 file changed, 12 insertions(+), 20 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index c14619a..619f8ee 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -472,27 +472,16 @@ static const SchemaQuery Query_for_list_of_matviews = {
 "SELECT name FROM "\
 " (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
 "  WHERE context != 'internal') ss "\
-" WHERE substring(name,1,%d)='%s'"\
-" UNION ALL SELECT 'all' ss"
-
-#define Query_for_list_of_set_vars \
-"SELECT name FROM "\
-" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
-"  WHERE context IN ('user', 'superuser') "\
-"  UNION ALL SELECT 'constraints' "\
-"  UNION ALL SELECT 'transaction' "\
-"  UNION ALL SELECT 'session' "\
-"  UNION ALL SELECT 'role' "\
-"  UNION ALL SELECT 'tablespace' "\
-"  UNION ALL SELECT 'all') ss "\
 " WHERE substring(name,1,%d)='%s'"
 
+#define Query_for_list_of_set_vars									 \
+"SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings " \
+"  WHERE context IN ('user', 'superuser') "							\
+"   AND substring(name,1,%d)='%s'"
+
 #define Query_for_list_of_show_vars \
-"SELECT name FROM "\
-" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
-"  UNION ALL SELECT 'session authorization' "\
-"  UNION ALL SELECT 'all') ss "\
-" WHERE substring(name,1,%d)='%s'"
+"SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
+"  WHERE substring(name,1,%d)='%s'"
 
 #define Query_for_list_of_roles \
 " SELECT pg_catalog.quote_ident(rolname) "\
@@ -2611,9 +2600,12 @@ psql_completion_internal(const char *text, char **previous_words,
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
 	if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_set_vars,
+							   ADDLIST6("CONSTRAINTS", "TRANSACTION", "SESSION",
+										"ROLE", "TABLESPACE", "ALL"));
 	if (Matches1("SHOW"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_show_vars,
+							   ADDLIST2("SESSION AUTHORIZATION", "ALL"));
 	/* Complete "SET TRANSACTION" */
 	if (Matches2("SET", "TRANSACTION"))
 		COMPLETE_WITH_LIST5("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-- 
2.9.2

0006-Allow-complete-schema-elements-in-more-natural-way.patchtext/x-patch; charset=us-asciiDownload
From 2e79531af6a378df60a585bc9b23b0b200518dd8 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 15:14:35 +0900
Subject: [PATCH 06/17] Allow complete schema elements in more natural way

Some syntaxes like CREATE TABLE|INDEX, GRANT or REVOKE can be schema
elements of CREATE SCHEMA. Currently Matches/HeadMatches cannot be
used in completing schema elements since they begin midst of input
words. It leads to unnatural difference on completions that cannot use
TailMatches.

This patch allows to shift the beginning of input words so that schema
elements can be completed in just the same way with bare syntax.
---
 src/bin/psql/tab-complete.c | 127 ++++++++++++++++++++++++++++----------------
 1 file changed, 82 insertions(+), 45 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 619f8ee..d67f7a8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -995,6 +995,14 @@ psql_completion(const char *text, int start, int end)
 	COMPLETE_WITH_CONST("");		/* This returns matches */
 }
 
+typedef enum
+{
+	CMPL_CTX_NONE,
+	CMPL_CTX_CREATE_SCHEMA,
+	CMPL_CTX_ALTER_DEFAULT_PRIV
+} completion_context;
+
+
 /*
  * The completion function.
  *
@@ -1007,6 +1015,7 @@ psql_completion_internal(const char *text, char **previous_words,
 {
 	/* The number of prefixing words to be ignored */
 	int			head_shift = 0;
+	completion_context cmp_ctx = CMPL_CTX_NONE;
 
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
@@ -1058,10 +1067,39 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
+	/*
+	 * If this is in CREATE SCHEMA, seek to the last CREATE and regard it as
+	 * current command to complete.
+	 */
+	if (HeadMatches2("CREATE", "SCHEMA"))
+	{
+		SHIFT_TO_LAST1("CREATE|GRANT|REVOKE");
+		cmp_ctx = CMPL_CTX_CREATE_SCHEMA;
+	}
+
+	/*
+	 * If this is ALTER DEFAULT PRIVILEGES, seek to the last GRANT/REVOKE
+	 */
+	if (HeadMatches3("ALTER", "DEFAULT", "PRIVILEGES"))
+	{
+		SHIFT_TO_LAST1("GRANT|REVOKE");
+		cmp_ctx = CMPL_CTX_ALTER_DEFAULT_PRIV;
+	}
+
 /* CREATE */
 	/* complete with something you can create */
 	if (Matches1("CREATE"))
-		return completion_matches(text, create_command_generator);
+	{
+		/*
+		 * If this is the top level CREATE, complete with all CREATable
+		 * objects
+		 */
+		if (head_shift == 0)
+			return completion_matches(text, create_command_generator);
+
+		/* schema_element allows only some kinds of objects */
+		COMPLETE_WITH_LIST5("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER");
+	}
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
@@ -1807,33 +1845,32 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
 
-	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	if (TailMatches2("CREATE", "UNIQUE"))
+	if (Matches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
 	/*
 	 * If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY", and
 	 * existing indexes
 	 */
-	if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	if (Matches2("CREATE|UNIQUE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
 									  ADDLIST2("ON", "CONCURRENTLY"));
 	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
-			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
+	if (Matches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
+		Matches2("INDEX|CONCURRENTLY", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 
 	/*
 	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
 	 * indexes
 	 */
-	if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+	if (Matches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
 									  ADDLIST1("ON"));
 	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
-			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
+	if (Matches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
+		Matches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/*
@@ -1930,25 +1967,25 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
-/* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
-			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
+/* CREATE SEQUENCE */
+	if (Matches3("CREATE", "SEQUENCE", MatchAny) ||
+		Matches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
-		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
+	if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
+		Matches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
 	if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
-/* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE TABLE */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	if (Matches2("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	if (TailMatches2("CREATE", "UNLOGGED"))
+	if (Matches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
 /* CREATE TABLESPACE */
@@ -1964,35 +2001,35 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
-/* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	if (Matches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	if (Matches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	if (Matches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
-	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
+	if (Matches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+		Matches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
 	/*
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
 	 * tables
 	 */
-	if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
+	if (Matches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
-	if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
+	if (Matches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("ON", MatchAny))
 		COMPLETE_WITH_LIST7("NOT DEFERRABLE", "DEFERRABLE", "INITIALLY",
 							"REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
 	if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches1("DEFERRABLE") || TailMatches2("INITIALLY", "IMMEDIATE|DEFERRED")))
+		(TailMatches1("DEFERRABLE") || TailMatches2("INITIALLY", "IMMEDIATE|DEFERRED")))
 		COMPLETE_WITH_LIST4("REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
 	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("REFERENCING"))
 		COMPLETE_WITH_LIST2("OLD TABLE", "NEW TABLE");
@@ -2066,12 +2103,12 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
-/* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE VIEW */
 	/* Complete CREATE VIEW <name> with AS */
-	if (TailMatches3("CREATE", "VIEW", MatchAny))
+	if (Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
+	if (Matches4("CREATE", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
@@ -2272,16 +2309,16 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /*
  * GRANT and REVOKE are allowed inside CREATE SCHEMA and
- * ALTER DEFAULT PRIVILEGES, so use TailMatches
+ * ALTER DEFAULT PRIVILEGES
  */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	if (TailMatches1("GRANT|REVOKE"))
+	if (Matches1("GRANT|REVOKE"))
 	{
 		/*
 		 * With ALTER DEFAULT PRIVILEGES, restrict completion
 		 * to grantable privileges (can't grant roles)
 		 */
-		if (HeadMatches3("ALTER","DEFAULT","PRIVILEGES"))
+		if (cmp_ctx == CMPL_CTX_ALTER_DEFAULT_PRIV)
 			COMPLETE_WITH_LIST10("SELECT", "INSERT", "UPDATE",
 				"DELETE", "TRUNCATE", "REFERENCES", "TRIGGER",
 						"EXECUTE", "USAGE", "ALL");
@@ -2295,11 +2332,11 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	if (TailMatches2("GRANT|REVOKE", MatchAny))
+	if (Matches2("GRANT|REVOKE", MatchAny))
 	{
 		if (TailMatches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
-		if (TailMatches2("GRANT", MatchAny))
+		if (Matches2("GRANT", MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2316,13 +2353,13 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
 	 */
-	if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
+	if (Matches3("GRANT|REVOKE", MatchAny, "ON"))
 	{
 		/*
 		 * With ALTER DEFAULT PRIVILEGES, restrict completion
 		 * to the kinds of objects supported.
 		 */
-		if (HeadMatches3("ALTER","DEFAULT","PRIVILEGES"))
+		if (cmp_ctx == CMPL_CTX_ALTER_DEFAULT_PRIV)
 			COMPLETE_WITH_LIST4("TABLES", "SEQUENCES", "FUNCTIONS", "TYPES");
 		else
 			COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tsvmf,
@@ -2342,11 +2379,11 @@ psql_completion_internal(const char *text, char **previous_words,
 									  "TABLESPACE",
 									  "TYPE"));
 	}
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
+	if (Matches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
 							"TABLES IN SCHEMA");
 
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+	if (Matches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/*
@@ -2355,7 +2392,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 *
 	 * Complete "GRANT/REVOKE * ON *" with "TO/FROM".
 	 */
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
+	if (Matches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
 		if (TailMatches1("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
@@ -2398,27 +2435,27 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("FROM");
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
+	if (Matches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
 	{
-		if (TailMatches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (Matches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (Matches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 	{
-		if (TailMatches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (Matches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
+	if (Matches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
 	{
-		if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (Matches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
-- 
2.9.2

0007-Allow-CREATE-RULE-to-use-command-completion-recursiv.patchtext/x-patch; charset=us-asciiDownload
From 482c39871edad19a839556f6a62fd135d1a507ca Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 18:59:02 +0900
Subject: [PATCH 07/17] Allow CREATE RULE to use command completion recursively

A trial implement of CREATE RULE completing after DO.  This allows
commands not to use TailMatches. But seems to require more
consideration.
---
 src/bin/psql/tab-complete.c | 53 ++++++++++++++++++++++++++++++++++-----------
 1 file changed, 40 insertions(+), 13 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d67f7a8..bd21091 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1967,6 +1967,33 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
+	/* Recursive completion for inner command of CREATE RULE */
+	if (HeadMatches2("CREATE", "RULE")){
+		int idx =
+			find_last_index_of("DO", previous_words, previous_words_count);
+		if (idx > 0)
+		{
+			COLLAPSE(1, idx); /* Start from "DO" */
+			if (Matches1("DO"))
+				COMPLETE_WITH_LIST8("ALSO", "INSTEAD", "NOTHING",
+									"SELECT", "INSERT", "UPDATE", "DELETE",
+									"NOTIFY");
+			HeadMatchAndRemove2(2, 1, "DO", "ALSO|INSTEAD");
+			if (Matches1("DO"))
+				COMPLETE_WITH_LIST6("NOTHING",
+									"SELECT", "INSERT", "UPDATE", "DELETE",
+									"NOTIFY");
+			idx =
+				find_last_index_of("SELECT|UPDATE|INSERT|DELETE",
+								   previous_words, previous_words_count);
+			if (idx > 0) {
+				COLLAPSE(1, idx);
+				return psql_completion_internal(text, previous_words,
+												previous_words_count);
+			}
+		}
+	}
+
 /* CREATE SEQUENCE */
 	if (Matches3("CREATE", "SEQUENCE", MatchAny) ||
 		Matches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
@@ -2143,10 +2170,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches1("DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
-	if (TailMatches2("DELETE", "FROM"))
+	if (Matches2("DELETE", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete DELETE FROM <table> */
-	if (TailMatches3("DELETE", "FROM", MatchAny))
+	if (Matches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
 	/* XXX: implement tab completion for DELETE ... USING */
 
@@ -2473,32 +2500,32 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* INSERT --- can be inside EXPLAIN, RULE, etc */
 	/* Complete INSERT with "INTO" */
-	if (TailMatches1("INSERT"))
+	if (Matches1("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
-	if (TailMatches2("INSERT", "INTO"))
+	if (Matches2("INSERT", "INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete "INSERT INTO <table> (" with attribute names */
-	if (TailMatches4("INSERT", "INTO", MatchAny, "("))
+	if (Matches4("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd);
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES"
 	 */
-	if (TailMatches3("INSERT", "INTO", MatchAny))
+	if (Matches3("INSERT", "INTO", MatchAny))
 		COMPLETE_WITH_LIST5("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES");
 
 	/*
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE"
 	 */
-	if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
+	if (Matches4("INSERT", "INTO", MatchAny, MatchAny) &&
 			 ends_with(prev_wd, ')'))
 		COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES");
 
 	/* Insert an open parenthesis after "VALUES" */
-	if (TailMatches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
+	if (Matches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
 		COMPLETE_WITH_CONST("(");
 
 /* LOCK */
@@ -2537,7 +2564,7 @@ psql_completion_internal(const char *text, char **previous_words,
 							"UPDATE EXCLUSIVE MODE");
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
-	if (TailMatches1("NOTIFY"))
+	if (Matches1("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
 
 /* OPTIONS */
@@ -2773,16 +2800,16 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
-	if (TailMatches1("UPDATE"))
+	if (Matches1("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete UPDATE <table> with "SET" */
-	if (TailMatches2("UPDATE", MatchAny))
+	if (Matches2("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
 	/* Complete UPDATE <table> SET with list of attributes */
-	if (TailMatches3("UPDATE", MatchAny, "SET"))
+	if (Matches3("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_ATTR(prev2_wd);
 	/* UPDATE <table> SET <attr> = */
-	if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
+	if (Matches4("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-- 
2.9.2

0008-Allow-completing-the-body-of-EXPLAIN.patchtext/x-patch; charset=us-asciiDownload
From 24d3a7918e61dc34b9bc6891627af5c308f72ee6 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 18:30:43 +0900
Subject: [PATCH 08/17] Allow completing the body of EXPLAIN

This allows compleition for the body commands of EXPLAIN by
recursively call psql_comppletion_internal after ripping off the outer
command.
---
 src/bin/psql/tab-complete.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index bd21091..5d421aa 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2298,6 +2298,14 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("EXPLAIN", "VERBOSE") ||
 			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
 		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
+	HeadMatchAndRemove2(2, 1, "EXPLAIN", "ANALZYE");
+	HeadMatchAndRemove2(2, 1, "EXPLAIN", "VERVOSE");
+	/* complete for individual syntaxes */
+	if (Matches2("EXPLAIN", "SELECT|INSERT|DELETE|UPDATE|DECLARE"))
+	{
+		COLLAPSE(1, 1);
+		return psql_completion_internal(text, previous_words, WORD_COUNT());
+	}
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-- 
2.9.2

0009-Simpilfy-ALTER-TABLE-ALTER-COLUMN-completion.patchtext/x-patch; charset=us-asciiDownload
From 1382a502943f9f6a5393235423a1cfd9c0bd199b Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 16:05:13 +0900
Subject: [PATCH 09/17] Simpilfy ALTER TABLE ALTER COLUMN completion

Simplify ALTER TABLE ALTER COLUMN by removing COLUMN before
successive completions.
---
 src/bin/psql/tab-complete.c | 17 +++++++----------
 1 file changed, 7 insertions(+), 10 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5d421aa..44d4eb6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1528,25 +1528,22 @@ psql_completion_internal(const char *text, char **previous_words,
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
 	}
+	/* Remove COLUMN just after ALTER */
+	HeadMatchAndRemove5(5, 1, "ALTER", "TABLE", MatchAny, "ALTER", "COLUMN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
-			 Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
+	if (Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
 		COMPLETE_WITH_LIST4("TYPE", "SET", "RESET", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
-			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
 		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
-		 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
-	Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
+	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
 		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
-		Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
+	if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("DEFAULT", "NOT NULL");
 	if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
-- 
2.9.2

0010-Simplify-completion-for-CLUSTER-VERBOSE.patchtext/x-patch; charset=us-asciiDownload
From 70ec64e76200bcc311c1671bff3f89672f8842ec Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 16:19:46 +0900
Subject: [PATCH 10/17] Simplify completion for CLUSTER VERBOSE.

Simplify completion for CLUSTER command by removing VERBOSE.
---
 src/bin/psql/tab-complete.c | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 44d4eb6..8c6687c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1704,17 +1704,16 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tm,
 									  ADDLIST1("VERBOSE"));
-	if (Matches2("CLUSTER", "VERBOSE"))
+	/* Remove VERBOSE for further completion */
+	HeadMatchAndRemove2(2, 1, "CLUSTER", "VERBOSE");
+
+	if (Matches1("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 	/* If we have CLUSTER <sth>, then add "USING" */
-	if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
-		COMPLETE_WITH_CONST("USING");
-	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	if (Matches3("CLUSTER", "VERBOSE", MatchAny))
+	if (Matches2("CLUSTER", MatchAny))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER <sth> USING, then add the index as well */
-	if (Matches3("CLUSTER", MatchAny, "USING") ||
-			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
+	if (Matches3("CLUSTER", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
-- 
2.9.2

0011-Simplify-completion-for-COPY.patchtext/x-patch; charset=us-asciiDownload
From 87e8f5237b9eccf58533430198a71987a56b7228 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 17:21:29 +0900
Subject: [PATCH 11/17] Simplify completion for COPY.

Simplify completion for COPY command by removing BINARY.
---
 src/bin/psql/tab-complete.c | 21 ++++++++++-----------
 1 file changed, 10 insertions(+), 11 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8c6687c..984efc0 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1762,39 +1762,38 @@ psql_completion_internal(const char *text, char **previous_words,
 /* COPY */
 
 	/*
+	 * Just ignore obsolte styled BINARY if exists.
+	 */
+	HeadMatchAndRemove2(2, 1, "COPY", "BINARY");
+	/*
 	 * If we have COPY, offer list of tables or "(" (Also cover the analogous
 	 * backslash command).
 	 */
 	if (Matches1("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
 									  ADDLIST1("("));
-	/* If we have COPY BINARY, complete with list of tables */
-	if (Matches2("COPY", "BINARY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
+	/* Don't suggest BINARY just after COPY. It is obsolete. */
+
 	/* If we have COPY (, complete it with legal commands */
 	if (Matches2("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
 	/* If we have COPY [BINARY] <sth>, complete it with "TO" or "FROM" */
-	if (Matches2("COPY|\\copy", MatchAny) ||
-			 Matches3("COPY", "BINARY", MatchAny))
+	if (Matches2("COPY|\\copy", MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "TO");
 	/* If we have COPY [BINARY] <sth> FROM|TO, complete with filename */
-	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
-			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
+	if (Matches3("COPY|\\copy", MatchAny, "FROM|TO"))
 	{
 		SET_COMP_CHARP("");
 		return completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename */
-	if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
-			 Matches5("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
+	if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny))
 		COMPLETE_WITH_LIST6("BINARY", "OIDS", "DELIMITER", "NULL", "CSV",
 							"ENCODING");
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename CSV */
-	if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
-			 Matches6("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny, "CSV"))
+	if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV"))
 		COMPLETE_WITH_LIST5("HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE",
 							"FORCE NOT NULL");
 
-- 
2.9.2

0013-Simplify-completion-for-CREATE-SEQUENCE.patchtext/x-patch; charset=us-asciiDownload
From eecc2236f6a5042f8e1e4891a1a44dc43cf1b879 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 17:56:45 +0900
Subject: [PATCH 13/17] Simplify completion for CREATE SEQUENCE.

Simplify completion for CREATE SEQUENCE by removing TEMPORARY.
---
 src/bin/psql/tab-complete.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 456cd87..c94ce20 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1996,12 +1996,11 @@ psql_completion_internal(const char *text, char **previous_words,
 	}
 
 /* CREATE SEQUENCE */
-	if (Matches3("CREATE", "SEQUENCE", MatchAny) ||
-		Matches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
+	HeadMatchAndRemove3(2, 1, "CREATE", "TEMP|TEMPORARY", "SEQUENCE");
+	if (Matches3("CREATE", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
-		Matches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
+	if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
-- 
2.9.2

0014-Simplify-completion-for-DROP-INDEX.patchtext/x-patch; charset=us-asciiDownload
From 148486fe1496dee668c64b60b0e1864940a2283c Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 18:14:18 +0900
Subject: [PATCH 14/17] Simplify completion for DROP INDEX.

Simplify completion for DROP INDEX by removing CONCURRENTLY.
---
 src/bin/psql/tab-complete.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index c94ce20..2d22825 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2211,12 +2211,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
 									  ADDLIST1("CONCURRENTLY"));
-	if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
+	/* Remove CONCURRENTLY */
+	HeadMatchAndRemove3(3, 1, "DROP", "INDEX", "CONCURRENTLY");
 	if (Matches3("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
-		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
 	if (Matches2("DROP", "MATERIALIZED"))
-- 
2.9.2

0015-Add-CURRENT_USER-to-some-completions-of-role.patchtext/x-patch; charset=us-asciiDownload
From ed2bf9a43c906edd291ae8808da8530b9411e5bb Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Wed, 14 Dec 2016 16:06:28 +0900
Subject: [PATCH 15/17] Add CURRENT_USER to some completions of role

This is not a part of refactoring, but some completions for role
misses usable keywords such like CURRENT_USER. This patch adds it.
---
 src/bin/psql/tab-complete.c | 28 +++++++++++++++++-----------
 1 file changed, 17 insertions(+), 11 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2d22825..71bfcc9 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1142,7 +1142,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("SET TABLESPACE", "OWNED BY");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY */
 	if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+							   ADDLIST2("CURRENT_USER", "SESSION_USER"));
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY xxx */
 	if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("SET TABLESPACE");
@@ -1677,7 +1678,8 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
 	if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+							   ADDLIST2("CURRENT_USER", "SESSION_USER"));
 
 	/*
 	 * If we have ALTER TYPE <sth> RENAME VALUE, provide list of enum values
@@ -2226,7 +2228,8 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
 	if (Matches3("DROP", "OWNED", "BY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+							   ADDLIST2("CURRENT_USER", "SESSION_USER"));
 
 	if (Matches3("DROP", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
@@ -2357,9 +2360,10 @@ psql_completion_internal(const char *text, char **previous_words,
 						"EXECUTE", "USAGE", "ALL");
 		else
 			COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
-				   ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
+				   ADDLIST16("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
 							 "REFERENCES", "TRIGGER", "CREATE", "CONNECT",
-							 "TEMPORARY", "EXECUTE", "USAGE", "ALL"));
+							 "TEMPORARY", "EXECUTE", "USAGE", "ALL",
+							 "PUBLIC", "CURRENT_USER", "SESSION_USER"));
 	}
 	/*
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
@@ -2579,7 +2583,8 @@ psql_completion_internal(const char *text, char **previous_words,
 
 /* OWNER TO  - complete with available roles */
 	if (TailMatches2("OWNER", "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+							   ADDLIST2("CURRENT_USER", "SESSION_USER"));
 
 /* ORDER BY */
 	if (TailMatches3("FROM", MatchAny, "ORDER"))
@@ -2602,11 +2607,13 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
 	if (Matches3("REASSIGN", "OWNED", "BY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+							   ADDLIST2("CURRENT_USER", "SESSION_USER"));
 	if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 	if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+							   ADDLIST2("CURRENT_USER", "SESSION_USER"));
 
 /* REFRESH MATERIALIZED VIEW */
 	if (Matches1("REFRESH"))
@@ -2717,14 +2724,13 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
 	if (Matches2("SET", "ROLE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles, ADDLIST1("NONE"));
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
 	if (Matches2("SET", "SESSION"))
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
 	if (Matches3("SET", "SESSION", "AUTHORIZATION"))
-		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
-							   ADDLIST1("DEFAULT"));
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles, ADDLIST1("DEFAULT"));
 	/* Complete RESET SESSION with AUTHORIZATION */
 	if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
-- 
2.9.2

0016-Simplify-completion-for-ALTER-DEFAULT-PRIVILEGES.patchtext/x-patch; charset=us-asciiDownload
From 824f0e9352f9876ee807993e850e1eecb8a75556 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 26 Dec 2016 10:56:30 +0900
Subject: [PATCH 16/17] Simplify completion for ALTER DEFAULT PRIVILEGES.

Simplify completion for ALTER DEFAULT PRIVILEGES. This patch
additionally allows to appear IN SCHEMA and FOR ROLE in arbitrary
order.
---
 src/bin/psql/tab-complete.c | 63 ++++++++++++++++++++++++++++++---------------
 1 file changed, 42 insertions(+), 21 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 71bfcc9..b69e278 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -999,7 +999,10 @@ typedef enum
 {
 	CMPL_CTX_NONE,
 	CMPL_CTX_CREATE_SCHEMA,
-	CMPL_CTX_ALTER_DEFAULT_PRIV
+	CMPL_CTX_ALTER_DEFAULT_PRIV,
+	CMPL_CTX_ALT_DEF_PRIV_FORROLE,
+	CMPL_CTX_ALT_DEF_PRIV_INSCHEMA,
+	CMPL_CTX_ALT_DEF_PRIV_BOTH
 } completion_context;
 
 
@@ -1294,33 +1297,51 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* ALTER DEFAULT PRIVILEGES */
+
+	if (Matches2("ALTER", "DEFAULT"))
+		COMPLETE_WITH_CONST("PRIVILEGES");
+
+	/* Remove intermediate words. These two phrases can appear in arbitrary
+	 * order, but assuming that each one appears once.
+	 */
+	if(HeadMatchAndRemove6(4, 3, "ALTER", "DEFAULT", "PRIVILEGES",
+						   "FOR", "ROLE|USER", MatchAny))
+		cmp_ctx = CMPL_CTX_ALT_DEF_PRIV_FORROLE;
+	if(HeadMatchAndRemove6(4, 3, "ALTER", "DEFAULT", "PRIVILEGES",
+						   "IN", "SCHEMA", MatchAny))
+		cmp_ctx = (cmp_ctx == CMPL_CTX_ALT_DEF_PRIV_FORROLE ?
+				   CMPL_CTX_ALT_DEF_PRIV_BOTH : CMPL_CTX_ALT_DEF_PRIV_INSCHEMA);
+	if(cmp_ctx == CMPL_CTX_ALT_DEF_PRIV_INSCHEMA &&
+	   HeadMatchAndRemove6(4, 3, "ALTER", "DEFAULT", "PRIVILEGES",
+						   "FOR", "ROLE|USER", MatchAny))
+		cmp_ctx = CMPL_CTX_ALT_DEF_PRIV_BOTH;
+
+	/* ALTER DEFAULT PRIVILEGES ... IN SCHEMA/FOR ROLE/GRANT/REVOKE */
 	if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
-		COMPLETE_WITH_LIST2("FOR ROLE", "IN SCHEMA");
+	{
+		switch (cmp_ctx)
+		{
+		case CMPL_CTX_ALT_DEF_PRIV_INSCHEMA:
+			COMPLETE_WITH_LIST3("FOR ROLE", "GRANT", "REVOKE");
+			break;
+		case CMPL_CTX_ALT_DEF_PRIV_FORROLE:
+			COMPLETE_WITH_LIST3("IN SCHEMA", "GRANT", "REVOKE");
+			break;
+		case CMPL_CTX_ALT_DEF_PRIV_BOTH:
+			COMPLETE_WITH_LIST2("GRANT", "REVOKE");
+			break;
+		default:
+			COMPLETE_WITH_LIST4("FOR ROLE", "IN SCHEMA", "GRANT", "REVOKE");
+			break;
+		}
+	}
 	/* ALTER DEFAULT PRIVILEGES FOR */
 	if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
 		COMPLETE_WITH_CONST("ROLE");
 	/* ALTER DEFAULT PRIVILEGES IN */
 	if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "IN"))
 		COMPLETE_WITH_CONST("SCHEMA");
-	/* ALTER DEFAULT PRIVILEGES FOR ROLE|USER ... */
-	if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER",
-				MatchAny))
-		COMPLETE_WITH_LIST3("GRANT", "REVOKE", "IN SCHEMA");
-	/* ALTER DEFAULT PRIVILEGES IN SCHEMA ... */
-	if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
-				MatchAny))
-		COMPLETE_WITH_LIST3("GRANT", "REVOKE", "FOR ROLE");
-	/* ALTER DEFAULT PRIVILEGES IN SCHEMA ... FOR */
-	if (Matches7("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
-				MatchAny, "FOR"))
-		COMPLETE_WITH_CONST("ROLE");
-	/* ALTER DEFAULT PRIVILEGES FOR ROLE|USER ... IN SCHEMA ... */
-	/* ALTER DEFAULT PRIVILEGES IN SCHEMA ... FOR ROLE|USER ... */
-	if (Matches9("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER",
-					MatchAny, "IN", "SCHEMA", MatchAny) ||
-		Matches9("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
-					MatchAny, "FOR", "ROLE|USER", MatchAny))
-		COMPLETE_WITH_LIST2("GRANT", "REVOKE");
+
 	/* ALTER DOMAIN <name> */
 	if (Matches3("ALTER", "DOMAIN", MatchAny))
 		COMPLETE_WITH_LIST6("ADD", "DROP", "OWNER TO", "RENAME", "SET",
-- 
2.9.2

0017-Add-suggestions-of-IF-NOT-EXISTS.patchtext/x-patch; charset=us-asciiDownload
From 427bd0b2793350122d8ba203cdcdbafe0c31a2df Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 26 Dec 2016 17:00:23 +0900
Subject: [PATCH 17/17] Add suggestions of IF (NOT) EXISTS

Add suggestions of IF EXISTS and IF NOT EXISTS to all applicable
existing completions. This changes several existing behaviors related
USER MAPPING, PROCEDUAL LANGUAGE.
---
 src/bin/psql/tab-complete.c | 229 +++++++++++++++++++++++++++++++++++---------
 1 file changed, 183 insertions(+), 46 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b69e278..6147cc1 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -743,7 +743,8 @@ static const pgsql_thing_t words_after_create[] = {
 								 * good idea. */
 	{"OWNED", NULL, NULL, THING_NO_CREATE},		/* for DROP OWNED BY ... */
 	{"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
-	{"POLICY", NULL, NULL},
+	{"POLICY", Query_for_list_of_policies, NULL},
+	{"PROCEDUAL LANGUAGE", NULL, NULL},
 	{"ROLE", Query_for_list_of_roles},
 	{"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"},
 	{"SCHEMA", Query_for_list_of_schemas},
@@ -760,7 +761,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"UNLOGGED", NULL, NULL, THING_NO_DROP},	/* for CREATE UNLOGGED TABLE
 												 * ... */
 	{"USER", Query_for_list_of_roles},
-	{"USER MAPPING FOR", NULL, NULL},
+	{"USER MAPPING", NULL, NULL},
 	{"VIEW", NULL, &Query_for_list_of_views},
 	{NULL}						/* end of list */
 };
@@ -1114,7 +1115,9 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* ALTER TABLE */
 	if (Matches2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
-									  ADDLIST1("ALL IN TABLESPACE"));
+							  ADDLIST2("ALL IN TABLESPACE","IF EXISTS"));
+	/* Remove IF EXISTS */
+	HeadMatchAndRemove4(3, 2, "ALTER", "TABLE", "IF", "EXISTS");
 
 	/* ALTER something */
 	if (Matches1("ALTER"))
@@ -1218,6 +1221,13 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
+	/* ALTER FOREIGN TABLE  */
+	if (Matches3("ALTER", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_foreign_tables,
+									  ADDLIST1("IF EXISTS"));
+	/* Remove IF EXISTS  */
+	HeadMatchAndRemove5(4, 2, "ALTER", "FOREIGN", "TABLE", "IF", "EXISTS");
+
 	/* ALTER FOREIGN TABLE <name> */
 	if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
@@ -1232,7 +1242,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* ALTER INDEX */
 	if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
-									  ADDLIST1("ALL IN TABLESPACE"));
+						  ADDLIST2("ALL IN TABLESPACE", "IF EXISTS"));
+	/* Remove IF EXISTS */
+	HeadMatchAndRemove4(3, 2, "ALTER", "INDEX", "IF", "EXISTS");
+
 	/* ALTER INDEX <name> */
 	if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
@@ -1261,7 +1274,10 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* ALTER MATERIALIZED VIEW */
 	if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_matviews,
-									  ADDLIST1("ALL IN TABLESPACE"));
+						  ADDLIST2("ALL IN TABLESPACE", "IF EXISTS"));
+	/* Remove IF EXISTS */
+	HeadMatchAndRemove5(4, 2, "ALTER", "MATERIALIZED", "VIEW", "IF", "EXISTS");
+
 
 	/* ALTER USER,ROLE <name> */
 	if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
@@ -1366,14 +1382,21 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	/* ALTER SEQUENCE <name> */
+	if (Matches2("ALTER", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_sequences,
+									  ADDLIST1("IF EXISTS"));
+	/* Remove IF EXISTS */
+	HeadMatchAndRemove4(3, 2, "ALTER", "SEQUENCE", "IF", "EXISTS");
 	if (Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
-		"SET SCHEMA", "OWNED BY", "OWNER TO", "RENAME TO", NULL};
+		 "SET SCHEMA", "OWNED BY", "OWNER TO", "RENAME TO", "IF EXISTS", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
+	/* Remove IF EXISTS */
+	HeadMatchAndRemove4(3, 2, "ALTER", "SEQUENCE", "IF", "EXISTS");
 	/* ALTER SEQUENCE <name> NO */
 	if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
@@ -1390,6 +1413,12 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
 	/* ALTER VIEW <name> */
+	if (Matches2("ALTER", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_views,
+									  ADDLIST1("IF EXISTS"));
+	/* Remove IF EXISTS */
+	HeadMatchAndRemove4(3, 2, "ALTER", "VIEW", "IF", "EXISTS");
+
 	if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
@@ -1534,12 +1563,26 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
 		COMPLETE_WITH_CONST("TO");
 
-	/* If we have ALTER TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
+	/*
+	 * If we have ALTER TABLE <sth> DROP, provide COLUMN or CONSTRAINT and IF
+	 * EXISTS
+	 */
 	if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
-		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
+		COMPLETE_WITH_LIST3("COLUMN", "CONSTRAINT", "IF EXISTS");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
-	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd);
+	if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN|CONSTRAINT"))
+		COMPLETE_WITH_ATTR_KW(prev3_wd, ADDLIST1("IF EXISTS"));
+	/* Remove IF EXISTS and COLUMN*/
+	HeadMatchAndRemove7(6, 2,
+						"ALTER", "TABLE", MatchAny, "DROP", "COLUMN|CONSTRAINT",
+						"IF", "EXISTS");
+	HeadMatchAndRemove6(5, 2,
+						"ALTER", "TABLE", MatchAny, "DROP", "IF", "EXISTS");
+	HeadMatchAndRemove5(5, 1,
+						"ALTER", "TABLE", MatchAny, "DROP", "COLUMN");
+	/* This doesn't match DROP CONSTRAINT */
+	if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
+		COMPLETE_WITH_ATTR(prev2_wd);
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
@@ -1686,6 +1729,12 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * If we have ALTER TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list
 	 * of attributes
 	 */
+	/* DROP can have IF EXISTS */
+	if (Matches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE"))
+		COMPLETE_WITH_ATTR_KW(prev3_wd, ADDLIST1("IF EXISTS"));
+	/* Remove IF EXISTS */
+	HeadMatchAndRemove7(6, 2, "ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE",
+						"IF", "EXISTS");
 	if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd);
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
@@ -1844,7 +1893,14 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	if (Matches2("CREATE", "EXTENSION"))
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_available_extensions,
+							   ADDLIST1("IF NOT EXISTS"));
+	/* Remove IF NOT EXISTS */
+	HeadMatchAndRemove5(3, 3, "CREATE", "EXTENSION", "IF", "NOT", "EXISTS");
+	/* Complete with available extensions rather than installed ones. */
+	if (Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
+
 	/* CREATE EXTENSION <name> */
 	if (Matches3("CREATE", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
@@ -1859,7 +1915,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches2("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
-	/* CREATE FOREIGN DATA WRAPPER */
+	/* CREATE FOREIGN DATA WRAPPER - doesn't accept IF NOT EXISTS */
 	if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
 
@@ -1875,7 +1931,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 */
 	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
-									  ADDLIST2("ON", "CONCURRENTLY"));
+						  ADDLIST3("ON", "CONCURRENTLY", "IF NOT EXISTS"));
 	/* Remove CONCURRENTLY for further completion */
 	HeadMatchAndRemove3(3, 1, "CREATE", "INDEX", "CONCURRENTLY");
 
@@ -1885,7 +1941,13 @@ psql_completion_internal(const char *text, char **previous_words,
 	 */
 	if (Matches2("CREATE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
-									  ADDLIST1("ON"));
+									  ADDLIST2("ON", "IF NOT EXISTS"));
+	/* IF NOT EXISTS must be followed by a table name */
+	if (Matches5("CREATE", "INDEX", "IF", "NOT", "EXISTS"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
+	/* Remove IF NOT EXISTS */
+	HeadMatchAndRemove6(3, 3,
+						"CREATE", "INDEX", "IF", "NOT", "EXISTS", MatchAny);
 
 	/* Complete CREATE [UNIQUE] INDEX [CONCURRENTLY] <sth> with "ON" */
 	if (Matches3("CREATE", "INDEX", MatchAny))
@@ -2018,8 +2080,21 @@ psql_completion_internal(const char *text, char **previous_words,
 		}
 	}
 
+/* CREATE SCHEMA */
+	if (Matches2("CREATE", "SCHEMA"))
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_schemas,
+							   ADDLIST1("IF NOT EXISTS"));
+	/* Remove IF NOT EXISTS */
+	HeadMatchAndRemove5(3, 3, "CREATE", "SCHEMA", "IF", "NOT", "EXISTS");
+
 /* CREATE SEQUENCE */
 	HeadMatchAndRemove3(2, 1, "CREATE", "TEMP|TEMPORARY", "SEQUENCE");
+	if (Matches2("CREATE", "SEQUENCE"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_sequences,
+									  ADDLIST1("IF NOT EXISTS"));
+	/* Remove IF NOT EXISTS */
+	HeadMatchAndRemove5(3, 3, "CREATE", "SEQUENCE", "IF", "NOT", "EXISTS");
+
 	if (Matches3("CREATE", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
@@ -2034,9 +2109,16 @@ psql_completion_internal(const char *text, char **previous_words,
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	if (Matches2("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
+	HeadMatchAndRemove3(2, 1, "CREATE", "TEMP|TEMPORARY", "TABLE");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	if (Matches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
+	HeadMatchAndRemove3(2, 1, "CREATE", "UNLOGGED", MatchAny);
+	/* Complete CREATE TABLE with table names and IF NOT EXISTS */
+	if (Matches2("CREATE", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tables,
+									  ADDLIST1("IF NOT EXISTS"));
+	HeadMatchAndRemove5(3, 3, "CREATE", "TABLE", "IF", "NOT", "EXISTS");
 
 /* CREATE TABLESPACE */
 	if (Matches3("CREATE", "TABLESPACE", MatchAny))
@@ -2164,6 +2246,13 @@ psql_completion_internal(const char *text, char **previous_words,
 /* CREATE MATERIALIZED VIEW */
 	if (Matches2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+	/* Complete CREATE MATERIALIZED VIEW with name or IF NOT EXISTS */
+	if (Matches3("CREATE", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_matviews,
+									  ADDLIST1("IF NOT EXISTS"));
+	/* Remove IF NOT EXISTS */
+	HeadMatchAndRemove6(4, 3,
+				"CREATE", "MATERIALIZED", "VIEW", "IF", "NOT", "EXISTS");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
 	if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
@@ -2209,9 +2298,86 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("LANGUAGE");
 
 /* DROP */
+	/* DROP INDEX */
+	if (Matches2("DROP", "INDEX"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
+									  ADDLIST2("CONCURRENTLY", "IF EXISTS"));
+	/* Remove CONCURRENTLY */
+	HeadMatchAndRemove3(3, 1, "DROP", "INDEX", "CONCURRENTLY");
+
+	if (Matches2("DROP", "AGGREGATE|COLLATION|CONVERSION|DOMAIN|EXTENSION|FUNCTION|GROUP|INDEX|LANGUAGE|POLICY|ROLE|SCHEMA|SEQUENCE|SERVER|TABLE|TABLESPACE|TRIGGER|TYPE|VIEW"))
+		COMPLETE_THING_KW(-1, ADDLIST1("IF EXISTS"));
+	/* Remove IF EXISTS */
+	HeadMatchAndRemove4(3, 2, "DROP", MatchAny, "IF", "EXISTS");
+
+	/* Complete more than two-words object names  */
+	/* DROP ACCESS METHOD */
+	if (Matches2("DROP", "ACCESS"))
+		COMPLETE_WITH_CONST("METHOD");
+	if (Matches3("DROP", "ACCESS", "METHOD"))
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_access_methods,
+							   ADDLIST1("IF EXISTS"));
+	/* DROP EVENT TRIGGER */
+	if (Matches2("DROP", "EVENT"))
+		COMPLETE_WITH_CONST("TRIGGER");
+	if (Matches3("DROP", "EVENT", "TRIGGER"))
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_event_triggers,
+							   ADDLIST1("IF EXISTS"));
+	/* DROP FOREIGN TABLE */
+	if (Matches2("DROP", "FOREIGN"))
+		COMPLETE_WITH_LIST2("TABLE", "DATA WRAPPER");
+	if (Matches3("DROP", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_foreign_tables,
+									  ADDLIST1("IF EXISTS"));
+	/* DROP FOREIGN DATA WRAPPER */
+	if (Matches3("DROP", "FOREIGN", "DATA"))
+		COMPLETE_WITH_CONST("WRAPPER");
+	if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_fdws, ADDLIST1("IF EXISTS"));
+	/* DROP MATERIALIZED VIEW */
+	if (Matches2("DROP", "MATERIALIZED"))
+		COMPLETE_WITH_CONST("VIEW");
+	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_matviews,
+									  ADDLIST1("IF EXISTS"));
+	/* DROP PROCEDUAL LANGUAGE */
+	if (Matches2("DROP", "PROCEDUAL"))
+		COMPLETE_WITH_CONST("LANGUAGE");
+	if (Matches3("DROP", "PROCEDUAL", "LANGUAGE"))
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_languages,
+							   ADDLIST1("IF EXISTS"));
+	/* DROP USER, and USER MAPPING */
+	if (Matches2("DROP", "USER"))
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+							   ADDLIST2("MAPPING", "IF EXISTS"));
+	if (Matches3("DROP", "USER", "MAPPING"))
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_user_mappings,
+							   ADDLIST2("IF EXISTS", "FOR"));
+
+	/* Remove IF EXISTS for two or three-words objects */
+	HeadMatchAndRemove5(4, 2, "DROP", MatchAny, MatchAny, "IF", "EXISTS");
+	HeadMatchAndRemove6(5, 2,
+						"DROP", MatchAny, MatchAny, MatchAny, "IF", "EXISTS");
+
+	if (Matches3("DROP", "ACCESS", "METHOD"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
+	if (Matches3("DROP", "EVENT", "TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+	if (Matches3("DROP", "FOREIGN", "TABLE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables);
+	if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
+	if (Matches3("DROP", "PROCEDUAL", "LANGUAGE"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
+	if (Matches4("DROP", "USER", "MAPPING", "FOR"))
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+							   ADDLIST3("USER", "CURRENT_USER", "PUBLIC"));
+
 	/* Complete DROP object with CASCADE / RESTRICT */
 	if (Matches3("DROP",
-					  "COLLATION|CONVERSION|DOMAIN|EXTENSION|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW",
+					  "COLLATION|CONVERSION|DOMAIN|EXTENSION|INDEX|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW",
 					  MatchAny) ||
 			 Matches4("DROP", "ACCESS", "METHOD", MatchAny) ||
 			 (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, MatchAny) &&
@@ -2227,23 +2393,6 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("(");
 	if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
-	if (Matches2("DROP", "FOREIGN"))
-		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
-
-	/* DROP INDEX */
-	if (Matches2("DROP", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
-									  ADDLIST1("CONCURRENTLY"));
-	/* Remove CONCURRENTLY */
-	HeadMatchAndRemove3(3, 1, "DROP", "INDEX", "CONCURRENTLY");
-	if (Matches3("DROP", "INDEX", MatchAny))
-		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-
-	/* DROP MATERIALIZED VIEW */
-	if (Matches2("DROP", "MATERIALIZED"))
-		COMPLETE_WITH_CONST("VIEW");
-	if (Matches3("DROP", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 
 	/* DROP OWNED BY */
 	if (Matches2("DROP", "OWNED"))
@@ -2266,21 +2415,6 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
-	/* DROP ACCESS METHOD */
-	if (Matches2("DROP", "ACCESS"))
-		COMPLETE_WITH_CONST("METHOD");
-	if (Matches3("DROP", "ACCESS", "METHOD"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-
-	/* DROP EVENT TRIGGER */
-	if (Matches2("DROP", "EVENT"))
-		COMPLETE_WITH_CONST("TRIGGER");
-	if (Matches3("DROP", "EVENT", "TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-
-	/* DROP POLICY <name>  */
-	if (Matches2("DROP", "POLICY"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* DROP POLICY <name> ON */
 	if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
@@ -2848,6 +2982,9 @@ psql_completion_internal(const char *text, char **previous_words,
 /* USER MAPPING */
 	if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
+	if (Matches2("CREATE", "USER"))
+		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
+							   ADDLIST1("MAPPING FOR"));
 	if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
 			ADDLIST3("CURRENT_USER", "PUBLIC", "USER"));
-- 
2.9.2

#59Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#58)
Re: IF (NOT) EXISTS in psql-completion

2016-12-26 9:40 GMT+01:00 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp

:

Thanks for reviewing but I ran out of time for this CF..

I'm going to move this to the next CF.

I splitted the patch into small pieces. f3fd531 conflicted to
this so rebased onto the current master HEAD.

0001 is psql_completion refactoring.
0002-0003 are patches prividing new infrastructures.
0004 is README for the infrastructures.
0005 is letter-case correction of SET/RESET/SHOW using 0002.
0006-0008 are improvements of recursive syntaxes using 0001 and 0004.
0009-0016 are simplifying (maybe) completion code per syntax.

The last one (0017) is the IF(NOT)EXIST modifications. It
suggests if(not)exists for syntaxes already gets object
suggestion. So some kind of objects like operator, cast and so
don't get an if.. suggestion. Likewise, I intentionally didn't
modified siggestions for "TEXT SEARCH *".

lot of patches. I hope I look on these patches this week.

Regards

Pavel

Show quoted text

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#60Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Pavel Stehule (#59)
Re: IF (NOT) EXISTS in psql-completion

Hello,

At Mon, 26 Dec 2016 14:24:33 +0100, Pavel Stehule <pavel.stehule@gmail.com> wrote in <CAFj8pRD2qq6v0jm6kqmWMwo-yNSvn8Vvf+m=DBy3ps=Y0_3yPA@mail.gmail.com>
pavel.stehule> 2016-12-26 9:40 GMT+01:00 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp

:

Thanks for reviewing but I ran out of time for this CF..

I'm going to move this to the next CF.

I splitted the patch into small pieces. f3fd531 conflicted to
this so rebased onto the current master HEAD.

0001 is psql_completion refactoring.
0002-0003 are patches prividing new infrastructures.
0004 is README for the infrastructures.
0005 is letter-case correction of SET/RESET/SHOW using 0002.
0006-0008 are improvements of recursive syntaxes using 0001 and 0004.
0009-0016 are simplifying (maybe) completion code per syntax.

The last one (0017) is the IF(NOT)EXIST modifications. It
suggests if(not)exists for syntaxes already gets object
suggestion. So some kind of objects like operator, cast and so
don't get an if.. suggestion. Likewise, I intentionally didn't
modified siggestions for "TEXT SEARCH *".

lot of patches. I hope I look on these patches this week.

Thank you for looking this and sorry for the many files. But I
hople that they would be far easier to read.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#61Michael Paquier
michael.paquier@gmail.com
In reply to: Kyotaro HORIGUCHI (#60)
Re: IF (NOT) EXISTS in psql-completion

On Tue, Dec 27, 2016 at 12:23 PM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

Hello,

At Mon, 26 Dec 2016 14:24:33 +0100, Pavel Stehule <pavel.stehule@gmail.com> wrote in <CAFj8pRD2qq6v0jm6kqmWMwo-yNSvn8Vvf+m=DBy3ps=Y0_3yPA@mail.gmail.com>
pavel.stehule> 2016-12-26 9:40 GMT+01:00 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp

:

Thanks for reviewing but I ran out of time for this CF..

I'm going to move this to the next CF.

I splitted the patch into small pieces. f3fd531 conflicted to
this so rebased onto the current master HEAD.

0001 is psql_completion refactoring.
0002-0003 are patches prividing new infrastructures.
0004 is README for the infrastructures.
0005 is letter-case correction of SET/RESET/SHOW using 0002.
0006-0008 are improvements of recursive syntaxes using 0001 and 0004.
0009-0016 are simplifying (maybe) completion code per syntax.

The last one (0017) is the IF(NOT)EXIST modifications. It
suggests if(not)exists for syntaxes already gets object
suggestion. So some kind of objects like operator, cast and so
don't get an if.. suggestion. Likewise, I intentionally didn't
modified siggestions for "TEXT SEARCH *".

lot of patches. I hope I look on these patches this week.

Thank you for looking this and sorry for the many files. But I
hople that they would be far easier to read.

The current patch status was "waiting on author", but that's incorrect
as a new series of this patch has been sent. Please be careful with
the status of the CF app! I am moving it to next CF with "needs
review". Gerdan Santos has marked himself as a reviewer of the patch
but I saw no activity, so I have removed his name to not confuse
people looking for patches to review (that happens!).
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#62Pavel Stehule
pavel.stehule@gmail.com
In reply to: Michael Paquier (#61)
Re: IF (NOT) EXISTS in psql-completion

2017-01-31 6:51 GMT+01:00 Michael Paquier <michael.paquier@gmail.com>:

On Tue, Dec 27, 2016 at 12:23 PM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

Hello,

At Mon, 26 Dec 2016 14:24:33 +0100, Pavel Stehule <

pavel.stehule@gmail.com> wrote in <CAFj8pRD2qq6v0jm6kqmWMwo-
yNSvn8Vvf+m=DBy3ps=Y0_3yPA@mail.gmail.com>

pavel.stehule> 2016-12-26 9:40 GMT+01:00 Kyotaro HORIGUCHI <

horiguchi.kyotaro@lab.ntt.co.jp

:

Thanks for reviewing but I ran out of time for this CF..

I'm going to move this to the next CF.

I splitted the patch into small pieces. f3fd531 conflicted to
this so rebased onto the current master HEAD.

0001 is psql_completion refactoring.
0002-0003 are patches prividing new infrastructures.
0004 is README for the infrastructures.
0005 is letter-case correction of SET/RESET/SHOW using 0002.
0006-0008 are improvements of recursive syntaxes using 0001 and 0004.
0009-0016 are simplifying (maybe) completion code per syntax.

The last one (0017) is the IF(NOT)EXIST modifications. It
suggests if(not)exists for syntaxes already gets object
suggestion. So some kind of objects like operator, cast and so
don't get an if.. suggestion. Likewise, I intentionally didn't
modified siggestions for "TEXT SEARCH *".

lot of patches. I hope I look on these patches this week.

Thank you for looking this and sorry for the many files. But I
hople that they would be far easier to read.

The current patch status was "waiting on author", but that's incorrect
as a new series of this patch has been sent. Please be careful with
the status of the CF app! I am moving it to next CF with "needs
review". Gerdan Santos has marked himself as a reviewer of the patch
but I saw no activity, so I have removed his name to not confuse
people looking for patches to review (that happens!).

I tested new set of these patches and I found some regressions there -
mentioned in my last mail.

Maybe I miss new update, bit I don't know about it.

Regards

Pavel

Show quoted text

--
Michael

#63Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#62)
Re: IF (NOT) EXISTS in psql-completion

2017-01-31 6:51 GMT+01:00 Michael Paquier <michael.paquier@gmail.com>:

The current patch status was "waiting on author", but that's incorrect
as a new series of this patch has been sent. Please be careful with
the status of the CF app! I am moving it to next CF with "needs
review". Gerdan Santos has marked himself as a reviewer of the patch
but I saw no activity, so I have removed his name to not confuse
people looking for patches to review (that happens!).

I tested new set of these patches and I found some regressions there -
mentioned in my last mail.

Maybe I miss new update, bit I don't know about it.

Some infrastructure related patches (maybe 50%) from this set can be
committed now - the moving complete set of patches to next commitfest
generates generates lot of repeated useless work.

Regards

Pavel

Show quoted text

Regards

Pavel

--
Michael

#64Michael Paquier
michael.paquier@gmail.com
In reply to: Pavel Stehule (#62)
Re: IF (NOT) EXISTS in psql-completion

On Tue, Jan 31, 2017 at 2:53 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I tested new set of these patches and I found some regressions there -
mentioned in my last mail.

Maybe I miss new update, bit I don't know about it.

The last update I am aware of is that saying: "lot of patches. I hope
I look on these patches this week.". Here is the message:
/messages/by-id/CAFj8pRD2qq6v0jm6kqmWMwo-yNSvn8Vvf+m=DBy3ps=Y0_3yPA@mail.gmail.com

And there is no sign of reviews on the new versions.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#65Pavel Stehule
pavel.stehule@gmail.com
In reply to: Michael Paquier (#64)
Re: IF (NOT) EXISTS in psql-completion

2017-01-31 6:56 GMT+01:00 Michael Paquier <michael.paquier@gmail.com>:

On Tue, Jan 31, 2017 at 2:53 PM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

I tested new set of these patches and I found some regressions there -
mentioned in my last mail.

Maybe I miss new update, bit I don't know about it.

The last update I am aware of is that saying: "lot of patches. I hope
I look on these patches this week.". Here is the message:
/messages/by-id/CAFj8pRD2qq6v0jm6kqmWMwo-
yNSvn8Vvf+m=DBy3ps=Y0_3yPA@mail.gmail.com

And there is no sign of reviews on the new versions.

I found a error - I sent mail only to author 2016-12-31 :( - It is my
mistake. I am sorry

Show quoted text

--
Michael

#66Michael Paquier
michael.paquier@gmail.com
In reply to: Pavel Stehule (#65)
Re: IF (NOT) EXISTS in psql-completion

On Tue, Jan 31, 2017 at 2:58 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I found a error - I sent mail only to author 2016-12-31 :( - It is my
mistake. I am sorry

Ah... Thanks for the update. No problem.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#67Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Michael Paquier (#66)
Re: IF (NOT) EXISTS in psql-completion

At Tue, 31 Jan 2017 15:07:55 +0900, Michael Paquier <michael.paquier@gmail.com> wrote in <CAB7nPqTCuK1qVQNwPvnxhA9trxp228v0X6A4gKQb2Uc=mc+adQ@mail.gmail.com>

On Tue, Jan 31, 2017 at 2:58 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I found a error - I sent mail only to author 2016-12-31 :( - It is my
mistake. I am sorry

Ah... Thanks for the update. No problem.

Ouch. Sorry for missing you commnet. I'll dig my mail box for that.

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#68Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#67)
Re: IF (NOT) EXISTS in psql-completion

2017-01-31 11:10 GMT+01:00 Kyotaro HORIGUCHI <
horiguchi.kyotaro@lab.ntt.co.jp>:

At Tue, 31 Jan 2017 15:07:55 +0900, Michael Paquier <
michael.paquier@gmail.com> wrote in <CAB7nPqTCuK1qVQNwPvnxhA9trxp22
8v0X6A4gKQb2Uc=mc+adQ@mail.gmail.com>

On Tue, Jan 31, 2017 at 2:58 PM, Pavel Stehule <pavel.stehule@gmail.com>

wrote:

I found a error - I sent mail only to author 2016-12-31 :( - It is my
mistake. I am sorry

Ah... Thanks for the update. No problem.

Ouch. Sorry for missing you commnet. I'll dig my mail box for that.

I am doing a review of this set of patches:

1. There are no problem with patching, compiling - all regress tests passed

2. tab complete doesn't work well if I am manually writing "create index
on" - second "ON" is appended - it is a regression

I didn't find any other issues -

note: not necessary to implement (nice to have) - I miss a support for OR
REPLACE flag - it is related to LANGUAGE, TRANSFORMATION, FUNCTION and
RULE.

Regards

Show quoted text

--
Kyotaro Horiguchi
NTT Open Source Software Center

#69Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Pavel Stehule (#68)
Re: IF (NOT) EXISTS in psql-completion

Thank you for reviewing.

At Tue, 31 Jan 2017 11:28:17 +0100, Pavel Stehule <pavel.stehule@gmail.com> wrote in <CAFj8pRADxoycR=PqmtdJy0tBrYAWaQ0_xt6t-QBX_U8_ROGCwQ@mail.gmail.com>

I am doing a review of this set of patches:

1. There are no problem with patching, compiling - all regress tests passed

Oh! My new check script made from Michael's suggestion finds one
trailing space in the patch. And PUBLICATION/SUBSCRIPTION
conflicats with this so I'll post the reased version.

2. tab complete doesn't work well if I am manually writing "create index
on" - second "ON" is appended - it is a regression

I'll fix it in the version.

I didn't find any other issues -

note: not necessary to implement (nice to have) - I miss a support for OR
REPLACE flag - it is related to LANGUAGE, TRANSFORMATION, FUNCTION and
RULE.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#70Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#69)
Re: IF (NOT) EXISTS in psql-completion

2017-02-01 9:37 GMT+01:00 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp

:

Thank you for reviewing.

At Tue, 31 Jan 2017 11:28:17 +0100, Pavel Stehule <pavel.stehule@gmail.com>
wrote in <CAFj8pRADxoycR=PqmtdJy0tBrYAWaQ0_xt6t-QBX_U8_ROGCwQ@mail.
gmail.com>

I am doing a review of this set of patches:

1. There are no problem with patching, compiling - all regress tests

passed

Oh! My new check script made from Michael's suggestion finds one
trailing space in the patch. And PUBLICATION/SUBSCRIPTION
conflicats with this so I'll post the reased version.

the content of my last mail is a copy my mail from end of December.
Probably lot of changes there.

Show quoted text

2. tab complete doesn't work well if I am manually writing "create index
on" - second "ON" is appended - it is a regression

I'll fix it in the version.

I didn't find any other issues -

note: not necessary to implement (nice to have) - I miss a support for OR
REPLACE flag - it is related to LANGUAGE, TRANSFORMATION, FUNCTION and
RULE.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#71Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Pavel Stehule (#70)
1 attachment(s)
Re: IF (NOT) EXISTS in psql-completion

Hello. This is the new version of this patch.

- Rebased to the current master (555494d)
PUBLICATION/SUBSCRIPTION stuff conflicted.

- Fix a bug of CREATE INDEX(0012-Simplify-completion-for-CREATE-INDEX.patch).
CREATE INDEX ON no longer gets a suggestion of "ON".

- Added logging feature (0018-Debug-output-of-psql-completion.patch)

This might be suitable to be a separate patch. psql completion
code is defficult to debug when it is uncertain what line did a
suggestion. This patch allows completion logs to psql log,
which is activated by -L option.

psql -L <logfile> <dbname>

And the logs like the following will be printed.

| completion performed at tab-complete.c:1146 for "do"

- OR REPLACE suggestion (0019-Add-suggestion-of-OR-REPLACE.patch)

At Wed, 1 Feb 2017 09:42:54 +0100, Pavel Stehule <pavel.stehule@gmail.com> wrote in <CAFj8pRAHCwdwe+NRTQ9JrtMO2OdUWtp1demmv_jGBU2tRRs-CQ@mail.gmail.com>

2017-02-01 9:37 GMT+01:00 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp
the content of my last mail is a copy my mail from end of December.
Probably lot of changes there.

Thanks for reposting.

2. tab complete doesn't work well if I am manually writing "create index
on" - second "ON" is appended - it is a regression

I'll fix it in the version.

I didn't find any other issues -

note: not necessary to implement (nice to have) - I miss a support for OR
REPLACE flag - it is related to LANGUAGE, TRANSFORMATION, FUNCTION and
RULE.

Hmm. This patch perhaps should not 'add a feature' (how about the
logging..). Anyway the last 19th patch does that. The word
removal framework works well for this case.

After all, this patch is so large that I'd like to attach them as
one compressed file. The content of the file is the following.

0001-Refactoring-tab-complete-to-make-psql_completion-cod.patch
- Just a refactoring of psql_completion

0002-Make-keywords-case-follow-to-input.patch
- The letter case of additional suggestions for
COMPLETION_WITH_XX follows input.

0003-Introduce-word-shift-and-removal-feature-to-psql-com.patch
- A feature to ignore preceding words. And a feature to remove
intermediate words.

0004-Add-README-for-tab-completion.patch
- README

0005-Make-SET-RESET-SHOW-varialble-follow-input-letter-ca.patch
0006-Allow-complete-schema-elements-in-more-natural-way.patch
0007-Allow-CREATE-RULE-to-use-command-completion-recursiv.patch
0008-Allow-completing-the-body-of-EXPLAIN.patch
0009-Simpilfy-ALTER-TABLE-ALTER-COLUMN-completion.patch
0010-Simplify-completion-for-CLUSTER-VERBOSE.patch
0011-Simplify-completion-for-COPY.patch
0012-Simplify-completion-for-CREATE-INDEX.patch
0013-Simplify-completion-for-CREATE-SEQUENCE.patch
0014-Simplify-completion-for-DROP-INDEX.patch
0015-Add-CURRENT_USER-to-some-completions-of-role.patch
0016-Refactor-completion-for-ALTER-DEFAULT-PRIVILEGES.patch
0017-Add-suggestions-of-IF-NOT-EXISTS.patch
- A kind of sample refctoring (or augmenting) suggestion code
based on the new infrastructure.

0018-Debug-output-of-psql-completion.patch
- Debug logging for psql_completion (described above)

0019-Add-suggestion-of-OR-REPLACE.patch
- Suggestion of CREATE OR REPLACE.

# I hear the footsteps of another conflict..

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

if_not_exists_20170203.tar.gzapplication/octet-streamDownload
#72Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#71)
Re: IF (NOT) EXISTS in psql-completion

Hi

2017-02-03 9:17 GMT+01:00 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp

:

Hello. This is the new version of this patch.

- Rebased to the current master (555494d)
PUBLICATION/SUBSCRIPTION stuff conflicted.

- Fix a bug of CREATE INDEX(0012-Simplify-completion-for-CREATE-INDEX.
patch).
CREATE INDEX ON no longer gets a suggestion of "ON".

- Added logging feature (0018-Debug-output-of-psql-completion.patch)

This might be suitable to be a separate patch. psql completion
code is defficult to debug when it is uncertain what line did a
suggestion. This patch allows completion logs to psql log,
which is activated by -L option.

psql -L <logfile> <dbname>

And the logs like the following will be printed.

| completion performed at tab-complete.c:1146 for "do"

- OR REPLACE suggestion (0019-Add-suggestion-of-OR-REPLACE.patch)

At Wed, 1 Feb 2017 09:42:54 +0100, Pavel Stehule <pavel.stehule@gmail.com>
wrote in <CAFj8pRAHCwdwe+NRTQ9JrtMO2OdUWtp1demmv_
jGBU2tRRs-CQ@mail.gmail.com>

2017-02-01 9:37 GMT+01:00 Kyotaro HORIGUCHI <

horiguchi.kyotaro@lab.ntt.co.jp

the content of my last mail is a copy my mail from end of December.
Probably lot of changes there.

Thanks for reposting.

2. tab complete doesn't work well if I am manually writing "create

index

on" - second "ON" is appended - it is a regression

I'll fix it in the version.

I didn't find any other issues -

note: not necessary to implement (nice to have) - I miss a support

for OR

REPLACE flag - it is related to LANGUAGE, TRANSFORMATION, FUNCTION

and

RULE.

Hmm. This patch perhaps should not 'add a feature' (how about the
logging..). Anyway the last 19th patch does that. The word
removal framework works well for this case.

After all, this patch is so large that I'd like to attach them as
one compressed file. The content of the file is the following.

0001-Refactoring-tab-complete-to-make-psql_completion-cod.patch
- Just a refactoring of psql_completion

0002-Make-keywords-case-follow-to-input.patch
- The letter case of additional suggestions for
COMPLETION_WITH_XX follows input.

0003-Introduce-word-shift-and-removal-feature-to-psql-com.patch
- A feature to ignore preceding words. And a feature to remove
intermediate words.

0004-Add-README-for-tab-completion.patch
- README

0005-Make-SET-RESET-SHOW-varialble-follow-input-letter-ca.patch
0006-Allow-complete-schema-elements-in-more-natural-way.patch
0007-Allow-CREATE-RULE-to-use-command-completion-recursiv.patch
0008-Allow-completing-the-body-of-EXPLAIN.patch
0009-Simpilfy-ALTER-TABLE-ALTER-COLUMN-completion.patch
0010-Simplify-completion-for-CLUSTER-VERBOSE.patch
0011-Simplify-completion-for-COPY.patch
0012-Simplify-completion-for-CREATE-INDEX.patch
0013-Simplify-completion-for-CREATE-SEQUENCE.patch
0014-Simplify-completion-for-DROP-INDEX.patch
0015-Add-CURRENT_USER-to-some-completions-of-role.patch
0016-Refactor-completion-for-ALTER-DEFAULT-PRIVILEGES.patch
0017-Add-suggestions-of-IF-NOT-EXISTS.patch
- A kind of sample refctoring (or augmenting) suggestion code
based on the new infrastructure.

0018-Debug-output-of-psql-completion.patch
- Debug logging for psql_completion (described above)

0019-Add-suggestion-of-OR-REPLACE.patch
- Suggestion of CREATE OR REPLACE.

# I hear the footsteps of another conflict..

The patch 0018 was not be applied.

Few other notes from testing - probably these notes should not be related
to your patch set

1. When we have set of keywords, then the upper or lower chars should to
follow previous keyword. Is it possible? It should to have impact only on
keywords.

2. the list of possible functions after EXECUTE PROCEDURE in CREATE TRIGGER
statement should be reduced to trigger returns function only.

CREATE OR REPLACE FUNCTIONS works great, thank you!

Regards

Pavel

Show quoted text

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#73Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Pavel Stehule (#72)
1 attachment(s)
Re: IF (NOT) EXISTS in psql-completion

Thank you for the comment.

At Mon, 6 Feb 2017 17:10:43 +0100, Pavel Stehule <pavel.stehule@gmail.com> wrote in <CAFj8pRD85cnxEEgLtOoqL-Bda2XpzvHB3a6Mr+bvf+OKpiq3Eg@mail.gmail.com>

0001-Refactoring-tab-complete-to-make-psql_completion-cod.patch
- Just a refactoring of psql_completion

0002-Make-keywords-case-follow-to-input.patch
- The letter case of additional suggestions for
COMPLETION_WITH_XX follows input.

0003-Introduce-word-shift-and-removal-feature-to-psql-com.patch
- A feature to ignore preceding words. And a feature to remove
intermediate words.

0004-Add-README-for-tab-completion.patch
- README

0005-Make-SET-RESET-SHOW-varialble-follow-input-letter-ca.patch
0006-Allow-complete-schema-elements-in-more-natural-way.patch
0007-Allow-CREATE-RULE-to-use-command-completion-recursiv.patch
0008-Allow-completing-the-body-of-EXPLAIN.patch
0009-Simpilfy-ALTER-TABLE-ALTER-COLUMN-completion.patch
0010-Simplify-completion-for-CLUSTER-VERBOSE.patch
0011-Simplify-completion-for-COPY.patch
0012-Simplify-completion-for-CREATE-INDEX.patch
0013-Simplify-completion-for-CREATE-SEQUENCE.patch
0014-Simplify-completion-for-DROP-INDEX.patch
0015-Add-CURRENT_USER-to-some-completions-of-role.patch
0016-Refactor-completion-for-ALTER-DEFAULT-PRIVILEGES.patch
0017-Add-suggestions-of-IF-NOT-EXISTS.patch
- A kind of sample refctoring (or augmenting) suggestion code
based on the new infrastructure.

0018-Debug-output-of-psql-completion.patch
- Debug logging for psql_completion (described above)

0019-Add-suggestion-of-OR-REPLACE.patch
- Suggestion of CREATE OR REPLACE.

# I hear the footsteps of another conflict..

The patch 0018 was not be applied.

The fear came true. fd6cd69 conflicts with it but on a
comment. The attached patch set applies on top of the current
master head (ae0e550).

Few other notes from testing - probably these notes should not be related
to your patch set

1. When we have set of keywords, then the upper or lower chars should to
follow previous keyword. Is it possible? It should to have impact only on
keywords.

It sounds reasonable, more flexible than "upper"/"lower" of
COMP_KEYWORD_CASE. The additional 20th(!) patch does that. It
adds a new value 'follow-first' to COMP_KEYWORD_CASE. All
keywords in a command line will be in the case of the first
letter of the first word. ("CREATE" in the following case. I
think it is enogh for the purpose.)

postgres=# \set COMP_KEYWORD_CASE follow-first
postgres=# CREATE in<tab>
=># CREATE INDEX hoge <tab>
=># CREATE INDEX hoge ON emp<tab>
=># CREATE INDEX hoge ON employee ..
postgres=# create IN<tab>
=># create index

Typing tab at the first in a command line shows all available
keywords in upper case.

2. the list of possible functions after EXECUTE PROCEDURE in CREATE TRIGGER
statement should be reduced to trigger returns function only.

Actually Query_for_list_of_trigger_functions returns too many
candidates. The suggested restriction reduces them to a
reasonable number. The 21th patch does that.

CREATE OR REPLACE FUNCTIONS works great, thank you!

Thanks. It was easier than expected.

As the result, 21 paches are attached to this message. 1 - 19th
are described above and others are described below.

0020-New-COMP_KEYWORD_CASE-mode-follow-first.patch

- Add new COMP_KEYWORD_CASE mode "follow-first". The completion
works with the case of the first word. (This doesn't rely on
this patchset but works in more cases with 0002)

0021-Suggest-only-trigger-functions-for-CREAET-TRIGGER.EX.patch
- Restrict suggestion for the syntax to ones acutually usable
there. (This relies on none of this patchset, though..)

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

if_not_exists_20170214.tar.gzapplication/octet-streamDownload
����X�=�{����k�+t��mH�	oBz{�RJ���k	��=�~|���-��2Is���3�
��l�H���k��G�yIi��9�[����L��~�Xj����7�������o�Q+���oJ�b�X�O��j�z�R�)�����C�7c�1�����Kz���r����.��|[w��4���&���kkS��f�����5m�����p��z�*���(Uja�W��r�R������cO	5�J�\k�����f�+�#Co��Q�5�f�I���1��-rCg�� ��	�G����9!?<� L����2�&���Y� ~?��u�]�u���+��'�':��R�##��R���<)V�a�Y,fn��_����_�[��R,�K�I�BI�B�k�P�PJ3��x�)d2��-�-��#���
��_���sj�g��Q�e��a��
��!�02-���
�)���d����p�%���A�3$p�(�qO�������G��?����%��R����1u�;�-`	����3�t1����9d.��?|�=:x���3��3��!BLkh����>!��e��L6"�>@R��'�����h��$�lx����d@#�R��CHO�KA�he����G!�
b�\N��Y����L�5���Bt�S{n-���"t��2@�����8Tg�`fj�SbB��(0����'^Zg�P�9�
�f�qF�40D�8�����vdN(	}����0,E���J�.?1�
���RZ�a��I?R!H$#X�o��R	����rf���<9n6��
m��v�!H�e]���ZM�P&34G#�iw N�H.���~��~"F���TF�B���Q���������X���h�F�N����OsD?���N����#e�,a�n�������_��No����d�u��@��eH�0���9��i���	�V8��h=\�Q�>�����]��SnQ�@�>p�j����
��hdK\�GCzd�'���U���y0�R-���3�G�2����h�!_���s������J06��p�jjB���H��=��4vI�8������"8����^�{:�gS
���c�g$)�������D1"���~��������/Z���M�
<�g�EU�|����Se,�=!!�8�^�����#��.������`;C����`^�c�<!C���Gt�e+�`�������'��|dS�
��J������Bx�����>1�B�`��Cg�{@�y�#sT-�n�f�0���z�9������	��mN�a����`�q�$���pl  ��b��O����+��<��BG������ V�pQ�R�"+G��R�J�&�F��R�Z�!�G���`�(XS
v�`�no��S��p��g�`@�L����08��6�r�a����(�DU���z�?�|��w�: �2�{9��j�i��	��^#t�.�$��������D�#�l����>3Gn^����
��[b��j�3/�f�8J�����w�qNF���Y)���`E�����*��rr
�r~�N�DTr�
��!��B ����*i��fUKKo�K�������ZnV�u��}El)@j�E_������ �6��?�
�z~1J(������:!)@��0���qnv`�����(K�q~��(����������\*rH3����V��o���q��4F�I�Na�iO�9�o6������$�`+�)Z�h�����/T�	���G���W����J�TniX�)}�A�F}��te!�r�����*���[}��u��*�j�1v.	UTeU��X����,����*)����0�B����T�Z����<�����X|�����@�@����D��d4���f)M)��&�v�n%�{�C��1�����C�tg8�LL��y��v�/
��|�y��9\t,�����z�b�hX��|� ���Em�N)P�<$%��`�{��I�XX�A����+i�,i�|+���?�J�_�}%�tY�}5�}�c^�R�$�&I �s�+]Q���*������������HQ:�9�j�<�xU��:�@1p��V�P��a[=l�e�zr��Z�
�"���e=�����e��,�H._��p�V&MA����i���(���������'�,
��R��Mp�
!� RD��Kp�YA�����'�>T���J'O@��Z������@0���}��0���|`+����b�x�w�["���MM|���[��������:����t�{N
$wM��>c�����b3*���:���RT8��0�2Q�tq��W�_������]:�)sI)���>F,{$��#����l�9n�!���d�����~v�����A�g�n�ru�#�&���.�s���m����Jr3�1s0y�x�>���92��������q�WX2gT"��<'�������9.ynzC���M0��������y��+G�U@~���O�v�YV���y��0;���b��
u�~��hi�}8�-%�L������s�x�B���T��
��79�d���:�Z%��4��u�t8
���S�G������K�\��D�`����x�M�a��^��s�DG�r���a�,�R��s�s�����ZRH��?|
��%��w�;'��z���b���}�`����n�Z���R�f����������y2n` �
�6�U���z��I�zA�1f��,�G�2�#��v��G������eMkd/�
`��7nZ���>������ �%|9R h��7hB�v���������m'�A�,�B����I�I�H�txm^�Y���5������2�c+�u��m�K��`�I�Zj8_���l^�^�{gW��V�4�����S�6�C�������d�IU[��,k�x��'	�}H�[
9~�6�0!��p�M�{���g~��=��5��V�h�bc���F�'o��
>.����%�~��s���#��J�U���!�r�������&��i��,�-���F
y����rynq���j�������q���t��S�V����v���S�X�E�`���d����.���wC/�?�
����;�Y�)h?�����~v�<4x�������O�K����_*>
G�xs��q���@L�p���s%�C}����C�b�m�e�/5 mN���6����b)�T�����=��|CNwX�`�!,�I�:m���b�`����0� h�|�!E�����*
���N$��i�����L+d�-���'!<��P�hq�0T��fs�����Fx�mh[��w�ml�c�S~�Qgp=�`�qG��O�a�alO8�uw�bGnJ�J�\*���n���{�|����<�r� �����2>��mO����j/�8�
�9��r0�B�3��oZ$���.��mx�vx�vx���7m��7�V?�]�MC;�R�4�S���7���������Z�+�Tt����TJcoR>����~K�N4���s0���
�m��b\JO�NI_����h.��� ������R3����]�?_�N4��>��^��^�R.Tj���,$������j���;�T�8�/-j�����q���[��K�>�O>�H�q[6!/�����q_a�Y3���'��O8���q�&���6mF�[��,��j�2�]�	FQ��M�x���t6��-�HS[�x��A0c�!n5�H�q��P����y��]�����������}o�������nDW@7�+��+�dk���^����U����z���������Cr��9)$���!�]��&$�;��!�]��6$�[>�!�]��^Hn�EBr����{iBrI�P$$�CQ((������6��KK�S�{
N��!��0F����)C1m1��"-@ JaR��}���b�����bM�C����?��n?|�.gZ�����G#��1k'yfq����	�BTw@� sA�����O)/q�]��8T��u�!Z����|z�I��b���5G������������qn�
2�#��`���;�}�X,��y���!����^Bi��R��/�@Y�z9_���HH"���{��dF�0Fc������H�8�����=b�fLQ����A�2��4Ls���(�)G��d�����r\���N=�x���|��^h��OxS'<
������	OC`���!0u���:�iL��4�Nx�q�$�$1JH_���Zb�+-6=�&��oSC9�D8����hz�B*;!DN*�U�4�����WN*gI�4���W��$�T�y\z���,� ���r%��R�O'$H8=�\�i�������(KH8=�\�i���������"W����n��q���?�v�Vs�+��7����Er�a;�u=��Pd~dEh��C�b2��>���[�������v�6=�����H��E�)}�A��������AY������f��A]������������*�B�4Z�*�NLU5YU���������TY�U�4ZmRv�������F�O�4��c	)��F�H�4�������A��7���~�����7F\_w._��oZ�V�������:�>�A'���t3�� �d������iL��;D�{���s�{xi�\�|�H}"y
��>//_�J�C�=g *��RgBE���^:���?�(G�[?8N�s�s��|I��7�y�Z��|�����t2/��o�aSd`H
���^��b50�8��+b���b]�EzHg�"�	�h��UVX�4R�	�"K��P���X�.�~'py���'�E^�[���\^+�1k��Ed� �����2����&�(}@����c���wT4j�!E*�U�D�1<yi�K	#��OFQ��MO��������������"W�8�\�|�T��M���P.�z<��i���Da;�@��C��-a��a�M:P��9��1b��Z���>w�).���Q �]�Ngx��P�]v ���;���>gs,�{�����������C��dWQ.�=x�'���N�/wN��pn���%�Va��8|� ���]��B�0
����9���	��x�����x�����g8
�9�YL��&�B?X�O�0Mj��Es�@9�
R9=J�0�N����^^u{�o�uj�������������K�h�����:���������������m�Z���,��qq�0�^��;���|O��9����$�<x�T����?����-`�,�_i�z����h)}3�k�t]��I���I~(F�L��)�/}3-0�������K���$o�����)-�Q�D[�^�g�s?�t�W��H����@���(E��l����b���}n��m�

��w������O��~if�gn�P�M��s
�=�Q�N�q��wJ�q��;���G�x/�.yl��3���^YoK�O��a6{������*b/B��:�6f�aZl��^bY��~=��'5k���r������a��Y��E6��2���	���Q<P����A��X�N#��V}�����\pt���n������<�L�n�����c����������c���X����OY��Z���w�ZF�2�+�Z�`�U��Z�k�:��F:��^~�N*	
{����$�dU*��hSHE�S��{8������^��yG�T9���8Xv1i��X������c����$�!*{{	N���w�=��:?'g�����V���]v�����T
�FY>�����r}�$j��l����9E�E���������q�����~�j�-�7���n����u����y��s��^]�����n���s�#������o��������U�svzI+����Y7x_(ox��|�����k|�����y����u�����z�? u�}|u�Z�g���"o�:?��+������(�����?�����p�@�J������N�� ��fq����nn_���g�>������X������������7��R`���=���q������)y}��yl�;��>!������k�����V@l�_^������X�P�Y<��Z<���V�[<�\���;Q�\��T���Z��y�O��b�]�}�)�����?�\I��O��������Lfb��;x'�M��mCc���CC�0��UUI��n�/�3s�'>'cLK��RI�*�%��f���Yh�b��}3�e��.h����6�����m�Q���{�>�������x�7����C���&_����C�'D�q����j��d���Ds��#�%,Y��S��Q�r�b?���g�i]R�
������n���H&1�����#=����q�nE���l�����e����-��&��sw�V���%�����t�.���z�
E����+�/R�u[m���#g�OHC��x>2����	�g���3��v
����tO�$���`?����?z$�d��rp�x[�>G4��o�8`�
�*�Y��g��N~suyF�������������)�S\G����bL�2v�"��'����SWd��������v�31Z<�������cj�"O��N�����P��8������56	X��5����Fb�U�$	���Z��
$���E]N����>���]b,U0	��Z���mqP��������{�I]^���~��o���m�������~�>�5��\U����k�X"�����`�����K'��y,G�LN����W�]���];�n����#YI�J�m0����%Y����za��e$�
T�dI����i��H'�74,��F�.B�z%�B
l�K#1��8LM��R�~�|Z_��
��������/H����4���B%�:�N�D�����' Vi��I�b
��a��^icZ�A����?�C���\�
����M+w�LZ��f����z��8?##'<���t�Ye1
�RLr�DwK~�1
p��`h��
�Q�pR��r������V��/=a*�R�\d��b?�`�4����|G��E��*L���}U��G���Ir�i����(���������A���Q,n�b��2�r�Y}@#V[�-��p�SRs�v<�#�7o&~?5����o���R�'������ol9��bd�lU�����[�54��MV��g��m�I�
{�G��C��	
��-�*W.��,t}�H�� � S��O��(�1�}�����IN��^����^�p�/]b��vA	n��m������V_&����J3���zl��js��w�7��M���K�H�>~�(~��"t���.��^��w�3�
�|�.Eg/����Qj�l
D�jkk�KB	���c���� ��4j!������D���m]����"E^����e��5�	�����g�H\�e�P��X�� ������i��%���U��L]�.�;���'�o�����2�|���v6I`��>�Q�Q�4R�z��Y0��w1�*	:t��=0�ENE\G��X��
�y"���z�~�+�d$7�$c���&�e���6�����v<*��_w�m���������;R�<+������RL��q-�v%�6���%������X�e���2�v�8B���o�
4N�:����N�%���YE����s�3������R>p)6��VO�&�C��6!�L������|���� ����%��i�Q��3E���..�p�a62��:L��K����C��r��%��/*���|���Vl!3-cx�LK����$e����������}��L���B��:�RR��1��i!�������N:m'w�����l�������g#8��U����~{��ZEd����m��M�r�����������Z�gke,]�vqx�t0�hh<��6\����J�F1RT''��KDr��#7�����MW J�N�[?�����Mp�����F{�@9��_�&�����co�D�F����$"A,�����I�Cn$��7��ah��������Y�w�^A�i n-�I�qJIV@�����8�\\�m�9_�Z^.Q&����Z�z�[������:DE�������Z!^�����CZ�`d����HU
�:��7��al?a$h�	�&0��f��G(
h��pV�5^�&�sP��+�/(]B�)b�+����i���W=����x����),x�mD��a?ApU�?����k�!�������&>�����~��v��%���9��m�I`sc`��;����3D�
��zp��U�A'�W�Mcv���B&�
��A�J����N�����60]�&~�
����:<���\O�;t�H����!���h�6�h�}���_�#{l�8&�f��x!/�����*�1��b5[��r?�T���y���5��/���W�������qk7���)>(�`$?�Y�&���D�S,Ou:G�
�H �x{��������8���#�6��h�	��Hi����z��|��zq�����gEC����
�)GP0k��I����."_P�t�a�������u�z��+N��y�)�n�T�3�0����h��c��a��UL�R�������������<
@�FB���$@��O���)dp��7�Bx��5a��P��`��=Vl�[m���H��*���MuYn�v'���;%bi;�=���tI �t)�L�IQ�n�"�lsrh���#9�[n��|'�$&���aOz��9�N!4��~��G�[2��>�&<}+��>��I=94�&p����w4O���[�S|�Q�0��&%+�G1�]"������~�#�G~��6�+�wf���t��6��Jk"�S�i�H��%8-����BG��xA"���6���nP�a�e2��q��q�;6�<�"�������1I���+��y8����\`�@�z��{�X�&���6m(1.�Y(L��g��$��vtv�q��x��,��G���"@v�M��fI�w�d���f{�
�4,��;��+�7
Di�+]#�k)g�A�m��)L�����2���1������	�o�y������B�m*:�?�;�%�!�!r�oX��<�j�2>�������k�x����^NV�(���<�����g�v%�;�y'�� oaL��m7/����S�7q�0�(����4���h���v�F�l�1�>�"��(k��n�����x��"2m���$��E+�D���'����j9�^�l��=�<�J��s�7w1e�����5�s/C�?�U[��������\�4�x�W���;.T*�a��8!��l���%)OC��sa
���m����e�q�v����A"���~W�"��3CLx2����u5��d�������T��h�U�G������:��[�H�0�#4ar"��c�]"ur"�T�����$LP��w"I�=�~�}5
|�����U����7�('��?F0Q<��Z���!���)���Mp�Vv8l�0#F��b=�i��l!�������$���-(���U�L��2�#�.�v3R��Jg�&!�v%����ge���*M	���EWRtH�>��D�>�p�v�d'����9*u�Tg���I$(�N�DC�.�����u�4],�x?�S��f{<��P�}0�����T�����4J���1��@���PSW�{u����8;��Z�.�{jv{rH�U<~���\��q�3-��1z������G��P0
a���)��b�����%"oF�-�NYFh]���r��K�H!�t�����W�uE��D�C��I3������+�����R���}�O��H���f���)�1&D�S�Z���H8i<�G[����"��i)����1����g�~l~����!P�?bG�e�����KX�M��)p�SA��~#
���%��y1x?�C4Z��b�dw�>	���H���a��+��2��d����ev��++�&��5m�����2�)n���p����(���b���-*$m��X���~��@����X�aA��g���JB����g�*K�?�����M�'�y�/|`��w��5]o�y�8'R�8��!J��Wo���h���/[�M).�C|�H}`��w�� ��f��t����:����j�	KE�����p'�|�rh[��,k��t
�1��7�`���&�C�x?o���-���������c �e����������hA��^�]��ct8+�F�-u�(��IF����;/�����'h_�<3���;�:������G��R{��e������/;���'�[ ��r��[m��\�H�����
��1����~�{��s�7�Q�t4�T�piBE��������D\����������,�w�N �
��!���U��r�Y�=a�6\���|��k��kq�=�"�D���A����N�?D�����<�$��������V�����o:xh�6{��T�^-E(�^�d�U {?<�e��x�S��:�>	5Ro�N�sy
�������|�hg��y��WL[�+��&W2��X��uy`���2>���c���N��Q�E����@�������U���[N���n���TB�Z.i:���dM)L%�;�Dj�N�i*��P�	���9���$���`���+���GRE��e5�f�y���V�
�S�������v.?d�|<���A�V
�?fY�9u�\<��[`u��5����dl`���7gj9�2�����
�B�����O���^/�o���O`�#3)F���y������s8X��9
�M�>�
����-vMx�C"��?Y	�k����DA�@Vw�mhc�t"�{2�4�����&��,�Y��O��g@�#Q����B���y�U��6B�������'���-�B�������d83���?��'Q9�J&<�D)���*l�z�6!�~��_���p�<���J�e�v;I��x9��:~�hi���Y��J���`��SC�r�E����aI�!���;���z��h$������;����>��IDz����f��p����0!q 
��@���7�����:�)�z�M
��j�i����k_��8��c>��Ds��h�6�c�]�;i��h�%a�DWy�p�{"���k������gJ��OP��4S��F��n?�	iY5;����d>�\X<[S�5��vA6������e��&����������}�E����W_�Pn��pq: 3��r@�����3�D�����5r�s�����Y�Q�A�dc� �<mNt�+��3������M@A�f\��Z�tj��|f;�bYEA��ZiW�,��O�;��uv����:X2����2B}����&?w�H|��8d���3C���(������fF�:G@t���h����6��S�'8%bo@e�}�������� u�I���Zw��)�>��rl�0�;�������EM������s	S��;���
���j���>�#�O:���,��A=�I�o/7�}Kh��[���������m�� �rI����a�
]	��Mt�������J�:��D���IP���0��J��aQ��:r7jC��v���mXx*�Gf����m��{��v}Qi��^�u�����?Y/�R������������|��|sA+�c�!��{i�$,\22ep��X:U�c�����QG����"����M� ��S5�����Z%[���t�J����D�p�����|��oi$�v������'�=�����\�8���r&D��U���"�nb�����=��}&y�����T�A��	j�)'��o1���~��{sl�EwGp��������b����1Fn�d5]� i_3
�
�#�i9����T�����$brB�L�+��%�������6�)���M���?��E������$���I�I?"�/w�1��F���Q��he
V8�pt��$e��[��� �gX�p��jI����41z���p���;d��Wz*�y�j�z���j��dE��I��~����;��p�~<�����T��9\ b�1LR�>��`�����,5^f��y4c6����'1��@���!�dC����������@���� ��%�7��
~�w�
�8?��7�'_L��yg^

gZ|�E��'q	��kn����doT
�M,�c�]f�����#7�^�ko�i2�&<���[ 4����%B�#��������{B��i�a���tpow6�w:v
g���s��h*�8�� ������z�E�5�y�/����{�~���M!�I�/�#N��y��:i�$�;�w�5g����>g��t����H�O��
gP��JA������o�,�[����s�.�[���.�
���]/s,]��
bB���MSB�@lfK���7]�S#�t�Y���	�$�xrr�3qc[#�?��7�2�r�����1�������W�y}I}�f��wL���m�m���T�n���8���Y�u�#��p[��H�72�/?4�7����+�1�&���-�p�0�.�@��	
-J5���5�&�g��z���t#5+D7
�C�-��MQ..��������{|���4���@��f����\R�r����z��[L��{�O[�8!���^�u#�v���et�A��^n�?��
�
cX*u���X�.�%�~{�v�Q�m��~�J��MN����)�/��&����]J�ov)�`N��S-E_�s������|���E�	�_�$z�%�0��I�.]k�N)��))Bt��!H����7t��<<�&�6&u^�g�D-������U�2,F�p�ko���z
�(1��^�t���V7��|��6��1���%����2V�P3%0���SZ�x�M�_=��	r7��#EU���
8p�.?�L���@�1����p�v�����7M	����e<��'��D�=�*������D�m��e��{~��"�6O����������0|V��;�&�q��L�Q���#�8W!,��$e�O��*d1��>�w3��tQ�j�VAy1�f;���_t|�������"�1Sup���2;����9���`_�@��6_�#&�e�~�y�:���2
�f���b:�	:4�������z<U]XL��OE����Q7�A�J9�i�)�>����j�6�9��$'$F%1&��L��i��7Z���
Gpu���fCD*|��\�6{��%_MI�dR����Q*��;.�K�a���4�D,
;Pj
F�y�S�@��l�C��o""E��K�@?����d�m�Xf��a���3&�3	n~��l��
�0�)AT �v6����}���!��x�F�p%Xx����oZm��n��:�2���i�������u2��\P��h����t��8�}�:;'#���sT�������Q_�5W��1n�g��j�����n��g>�g:���5��/l���\��*���@�A~�n7�-`���[�i�ak��z�6'���Z<�y��"�<�e�y
�P�k�<E@j�'���N���1�&�$m��YO�����$8E���;6�������?�n����x��g/�7��E�{���M(��d��v�^��Hy�wFq���!�qL2�o�%���N����:�z�� ,���'FHL����X�U�B]����L��b���2��4���[���e��;,��i5��zF�(����E+e���LY�;u���>��@aYO���c(��(��e�~�
�%-��/�b������l
c
���:)������jF�de��H��$��8����d��[�)�����e���g�W�������&���HLr�N���F����/���~-��s������:�^����[�0��^�}�{�H�/�h����o�W%����73��q�6��\��1=���*wi;�,��������j�A���f/��z���q������r	^�xJ���3�5Z���q6m/���|h���U(	�^dKpz'$#����#������!e�m�F�T�,/��j��!%oaUW�M�L���������X�xf���2R����B7	�(�_��(<�5���s�0��MZ6��=Io��<�<eE(v�8��\��@n-����Z�����1�x1;�F&��|\��{�}��w
B�S������U�GW�z�����Q�do�b��cd��7�*t�������6�%��Z3�!��|�����y;��Yf�������v��
��l>��{B��
0K��p�:����Xk�}OL
K�������g^�rs��-ht��k�UZ�r�����`3
�%9@������H/����b����_��	�}��X��
�XKd�������{�����?���A�G�����36%��>���~*��c�!0�~k��m�__5_+b6V.���-������)���F{Y��L���*rn�r����[��7n���x��<n��7�l�����w��.���l��"@�NzT��Fo����A����4^=/+��%���}��t�2����<w6� \��b-'�,�������k��u�>���M�/�)X+�$9�+���s6�v���v������WE���4�6�=�=��k=���#<H�P�H���M�������Tj��`Eh��\���"A�h���&�t��3�>/����M��t���LT5A���v,���NF��F��m`"���$�1����+1�,qB7�H4��6��{
�Q'*�������5���#����t�.Z�X��5�7��������_�B]�*Re�2�Z��@�m�Y����c�n�t[%O2{���h�5H`���Nl�������4�R��=)�<"oQ}��\�����K��,"V/��D���Y������}.yZ���1�|��+s!�2�K�<�gp1����	g������Rv���I�-p�����O>���d�	��h%��6�kA�
�e�
�_��T��l�����~��"��V'����v����>���Z�����+t.�DE*n��G�`=��G� ]:R]4�k��L�T'��*z���,zi6~}+-��� O��TW���N5���x�=��&iQt9�U��n5�v�i�k�cd���l�:����Y��l��B��-�a�j��&���7�x���h.~��\�1�jak�%�{BC��%�i^\p���SYv�=��E5���.S;���0���F�@(�q���k�b6�~����������g��?M�p)�s������gla/o��C����e88���(G	S+�(���������6��v|��"�M���euC!Mdk��rq*,5/���S>^����t�[����������:	����;�0e�id��}u�a�	1/Gb�����7R��Y��EIv��
gP���+HFa��bcl��R�<n�;_����>��D4n.��{�A4��&p�������TFW�R����z��R��!x�yu��M���E�u����;`��u���?���u~i�������b9�2��7Q�p���mh0-9�m�s+��#�]��X-m�k���r�+:��S����Z����#1�z��N����\��4�9R�R�5����|���t�����N��M�O.�J��l�
��V<i���p��6�*�M[�dk�
��"���F&���x�Vo#�6�]6"����?m�l���Fh�F���DboB?$ E���w��I����?j����.�@&0.&�[����7�s�Iv����&3�������uE%��F>���X����h�6�z&�2���^,���=:!���|��������7��+�1���Ljq
?\��Fbn"����D�?��M���(=qU��v��N3���vIP���������{R�)Q�5�Z��-�/��a�81}�������,8�^����:R�6�@��M�f�PC�7���.��Uo]+�������cd���6P�E����b�Bm��ph������ �TL�������
��
��'����Y1���M�/V���c�sm��v�+B4`�)?R�9r������ruF�6I>��-�a`�^�����K�o��;	��8g�����D6�����
?> b�\.��=U����O+�:>��xYQ�"+��g�����k������4�t"��1��{����(_���TK5��k��:;&��j��9<-���P�)��bX=!6~7��������+������P������0FG�Nd���RC��8������$��y�I�1s��7�D���-U�(�s�����z��>���3���Y�������W��t�k�5�h�>�����Oom��tR�%��Z�;N���'��j*��+9�uH��U����S�(f�h�����SPD��8p�����2ho0�E4��/&������8F���[qj�����+��	b ��&��1Y�#!�T/6U����Dg2���#�&�������:�v|CH�@�CQV��v��yZ��zS����U�g`FVoZ�����*�rVS�Q�N!|��~�
7����_���E������!����g�?_�(B��gR�����F��FT{��N��q|pe���^�z��H�u�H�+��L���bk��O�UN��;Y9�����x���W�W�i~��g�f+�S�l���6 9,�
�LN�([����cD�|��R��]���$��(�Z1Y���sI~�v�W���I���|)��X���@��{%���sH�3��d��5A�)���r���b_�����Y=������ok��*C�r���
����q��t	8 ���H�u]���<�����������|��]�r���lr6!��L��W��{�0�v�|.���{�LS�����+(-��\�T�%p�����aw���{dTK�SDl�-��\�sV6������������g����1LBe:S��z��d8X:j)]�Jt5�����2#y��Y�S�������XW0�4�������B``���������?���=���V��f3{zD�=�F�#���o����A")����Y_���&��l!������w{T���p=�$P'���Y��C��>,�i���
����P���n����Z��n��~�y����3�a��?�8e'�*U��R�L���%�G���� Q,���T��,���g?I�e����G�Q�V���R���]<@e2�n�������=��I������(�N:aX}f�4�UoMb�;H6"�]�h�x��8YS�{D���V{��^`�H�)���w>��������~�m���zo����.�C���#c�u�=�K�e�S[D4�*��i��9�E�.[����l$�)����I��;g����E����y(5�q���Sd�N�=����M��� &
��a��k!��B+&���1�4++����8�������A?=*�:5yVF���O�,���d�w��nb���^�����K��zk<6��Fk��Mj��]�.��1���MZ8���_�:�&h�<�L�l���u�:QG�n�&���X��������]4_�.��,��XY}fZ78��E�8z�e��F����N0
@�����X����W�%���	:�q���l 1Hte
9�G�
S4*ut��V�;����4�ywT�������n��P�~�|.sz4��l������]���!3�����_�������V?>�G�rtM,
a[��p��R���9�����5���/���o
xFp�Y��l��
b�]���U�j�<]+cR�v��#C��%�e�w��]Q`�_K7Pk��C�l�>oC1�n"��W�94|���nQ�-����D�AtKG��M��_��^�����������H���~���w�d�����h��I�A|i����6�nM9b�t�`�<j�X�f���p�?z:"����2��S�M?A�F��(���2��)�!�F�DI���}m?�{/���;a�MS�������X����e�IIV�W=a0���.�[uh^���2����j��t"�^��f<
�^����,^���o��}�y<�H���M���t����2~L�1������t���v���������������?�u����������\o������Lu�����C�S����Y�UZK%��K�k���,L�|`}�!������|.;Im�m���m��7MYMKZL"s���7f� �2�P�����q�Ce+�#��A#8T�$����t�����|�La�1�K�7��0Q��f���O������/v<�m�����9Q]�MW��[8�k�
Z����4id���6]�)@Y7-?}��?�`�LH���k����DD��K�����9�!��LS���-Q�{�U�������F���	0�J
O�O�(���
x�.��3��kC�bM�Q��� 8����1����9�,����b4��
����'����d��O^_��*���3ke�V�S�P��|��-`�Z��?�j�����q	S
�?"R�9�������G3
J���s�x^5���>c3g�{�l7���������jn9����1;L���[���w.�.�;�y|�o�����k��(-���|'k�e7�!>�8��;sN"�yS^EOd�|��@��V��I���M���	���B~�g������|���iJ~'*�{���z=�X���aL@��xi�r"��G�l��6��>Y�L���{_e�7���,S�}d��;�A���2
���|��v������0b^?���|<>���j��u<�8����v��l���sn+��y�BE�%�!>��_��4e�c$Mh�EhH|�E���x��e�R��2��U�|`<�2�U�,f���K#������9��vS�������P���=�����{M)�Oig��4I,�Q���R������&6]��q�����<K��v��g����&��
��efe�@���_)�$p�=Q�MD�N����1n�'�ug��RY���������{�@��/A���r	��V���1�B����\�+����G�i�R^�k��y<�����3]�"�M/��������/��Z��������U�Q��Y�e�+QFOt�i�I�Ed�0��y���f��U��)I�t��t�W2�E$���x������D`e��H�/11�V3���K��_*���D/�t��z �v$S�j����%zU6*����5���>`u�w������#{�����r���-��#���g������(��v����>]����,�i>�#�����������������*�QL�l�6���>����9��@&���W��}���&�>+����+^%��ot�GD�X���*W���xV3Anj�'�QM��;��?�fp3=�!���7��>v�MR|�~��B��-���X���n�~;���P`_o���kA�'f����^���Z�+m-�6a������F���1W�*d��O���+pA
O��'��9a~ �����qpNQ����&��u����
5��o��qms��^%p�.f��h�_�k�L+Bd�z�~q'����Y�������p���w����G��L����m�����O�����_F��0��H����K�#�v��,|A�������Kk��3��[�Y��#�i4��F����i�����h!Vj/��=N�~4+/����cN;!^����=\�F�h�d����of���G�
j�
�lW�ns|1N��T� T��\���4s�p���� �G���I��r�us��o��>�N��f4M=�k�G��:����^������7�mE�p��(l'�<ET�,���u�{���a����5��_��]���~�8����f;\�T@I���(�<9�;��a�G@���y�"����I�t�V1�^�~5\�Oa�����5�Q�mf�P� ��P+��ZM�����z�*��`�ce<�Y/�z0�L�!�l�����w\D�p����EQ��E�w�rCj��5�.������o$4��v=��98�������Y�����j6�~4���pMy�( ������&����]b�!�
����e���y��h��C�+��{j����_|��w	�4/iE���N��t���>��������\_��q�5���gq4�[QL��	�=����.9F��%�/���8�����#�P�$g�)v	����[{��+��>��0��%���p��h���=�J	��r0�F�����!�p�U������&�;S�����5�^�9��S�2f[�	�G�x9��#�^���k/�"w�X��A��C$E��|.�1�.�C"+W������?����.����� ^������Q7{�#����v���H�Enc|k)sh9�����Q��c=��7~D�r|�h6 ��/������9��F��1�nm�7R$��a����|}=���"���e�6`��~GO�7-�`���
\St��������3y&/��z9K�-��w�=��^��]���z�����w(:z�iZ�n�;��;����:o�=V����y��E�������^�u�h�w{�*���>��t��n�4-���F(�|#��]�X����bi�Q4�6s���1��N�t"��������#^����5����,v�w����I�c#$J��q6���C%2�Odox��N���\D�hE��p�-��9Z/�����9�%�������MY�F��m�|�����t�8� o���[��#�	6��F�w!G{fM�Z�N,������k��[��
o!����B[8]�d�m�w�e��"�0�<�ad8�p�	��&p]y��M�]����mO�c����=����������_~���
M�����)��'!B`�r��T�������V�KqP��������1���W����R��|)_.�V�
��9��`e��pi[+����	��+������<��u�;��!q"@�q����jH��n�����{.�\�d��r����De`�������}�y��t�F�����������n��g.��lz��[����#����g����c&v+�e:�� ���\%���?����Z��w���c���Vp��r0:yg9�#���V��v������f>�`w0{��k������c����9�M�]k����P�����$��8Ku"�i6�J�����K����c�?�|�F�3���/���a��1����2.~O�t���%|K�t������^M.����X�B���-`��{DJ�y���b�U���".��\�O'������W�����j`�����B-_,�����b��������Gn��3_����V��d�X�N�q������2�.�*y�7�T,*�+�
+_�?e�}�R��������1�o����f�����=��y�OlV(���j��k���\���U�V��])�c�n���R����X�^�B�����������y�~��sb�����v�~��O~���gj]��V�����?@R�g|eY���s&�r��������|#�?��������������/>->3�P&9�1�����,zpp�^B	Q���	$�� HnA���B�Zi�es|����}@�<�����9a����p`�'Y�m~�M�K�6<t�����q�\D�,h���W+8Ly���}��	����m�!B��\��)��,A4q�@OV��b�����x�g����`�Bz����s�����A8\=.���]%��*�l�#�*��p�w�B	����d0�����$��ag1��r!�#�u���,��'\�=�e������{6����+���A�	��\Pq��/o�K��"n���9t����		 �Sr_,���w\�����>�����T���ZN��9B���m�q	"������R�*�~r��VD!�S��]���Xb{�:Z���/�Z�Kg�NN&wtp��1��n8r��X\'ht�N0�Q�������^��
V��E�K���f��a��R!�
�.C]O����l�bv��%N%/���f�w�h���������h��h�����`�(�I�8v<.��;3A�5f�8b��e+)|���e�l��ve��V�Lp��2��	)��=�
��+�J24���G����5��mXh����Oi:i*�h�~GXX���'O�M��*=�`_o'����+L�P�;�H%>N�/r��N�	�A���
�p�������G�eisD`�9���"H�Az�s>,���L% 3��O_���2\h,	�TB���)����T���,=���x�'���g �ma>RWR�B��=�������,&�j�o�����G�Ty
.P�B;��L�@���������|������X*�$}k�W	4����P�
���q�1h^����	�q�j�l���z��� i�B����,�����"��4��lB3��BM|�w�+�:8�Q���7Fa����Nf����;���������E�7u�X<�1�2��}1�A��?�?h��F�r���~���6��m���j_��k^\�2��+�r���%��J�Z`C���+F�d07�X1i���n��0���.b�4r	���P��2����?� �w�o�D�_-�'?~�u��*�o�R�I���q{��m�]P�q���vN��������J�+��J#[,�@��E]�M.�(�gg���q
G�9��+��b�,do�V����bT�"t�F��%���+E
Q�C`��0ee��,�Z�peu8������U�;F�^
���
S�dx
�W����_���p���8%82��?��[�\��xew����Q0Mlr�?�X<�\�
�J�Z�"�����r������n�7xj\2���0�Dv2��a�:F''��p\-���x�#o{Z�X'��������O��6�@p��>G�����<0QHvsH;`,��?|��H�0�|�deK0�y�e�����o���Uivu3�Gc�
�TL����WpO����f9�?���z���9nf	-�o�Y��kN7�\}d�=����"��4�cNs~� ���i��$���,���m*yR��wAa���'�i@qb�~�Y�����\	�����-!�v�K+�,:��|;��L�kT���;�PV���E(����?�����/��]h3�Odnp2$+&��#�>�(B�-w�N�q�C���PH�T*���d
Q���L����|�p����� G��N
�C�Bm[+�^�FD����M��P]��b�Y��I@�5�  �5r��k��
�yE���5�\f���Mf�8������0N�!�7����Kr��s�o��.r������*3L���������QAi~�zi;����y�������L��j�Am��IK8
���)d������d��H�c:U�c���:��[������'Z��q_��jS����%�|���u\���D�0�k
�����Ke%��`%/�VJ�,�����<��,���
�/u�P;o�����SJ�v�"�C��u���j�a�5��~:����iQ��8:��XaJ�\~�)����S���O;%2��w��e�&%H�Z���9����3���m`�t�\�mK�!�@�D�k��r�f�\y��~������3����>M�5fy��a7d��p�����&Y,����1��Qj�������7�������$���b_���~����nf�v�42~6M�@��VR�^��VY��i<�;*���,��#���=8G.�4C��sUI��o)��UK�B&�Z�{��?�G��o�8?r���=!�������������!����D����e�:;�i%��)��!�P���������A��P�,+��r�3f	�^�%���wJ�]�%4y�T���������`q����@�W������FD��%��{��:%���A0<�wR��d�A���!�'_��R_����������Z��F��r�v?qA����
��*��b�t�~%_��������������{��;��v(i��;����4��(�@�%�,��03j�X��j
��6����eS<�P��B�R�Y�Vj{�|�:��G���������9A�_^�c��T4U�B|t_$��a�����7Y]NI�L�F1��yi��(1��*Y�9Py��a����eGlr���e�Ku��~xzq��i�O���:��AmFM��u����x��{w�C���l����4;��9.tE+���1��<D���k����/ih���r7��!$v|-T�W^1m��@�y�V�v%v�������L��K�B����Ni�b^�mbAV9���P��x�R�+-��������qL�~t�;<
�D��2oj.�2c��x�2uus����v���s4�N�s�f�����Z��J e�@ic5%�)�\�M��dqDU(;D?����V����Z0|�Rr��i��U�����]�UP���^sL]��	��sS�+>�x���z;eL@����P�]������d�+`�/X��Q��%�K#�B_|!s6}k{@���F6�g��������bF������-��0��!�Hv��`��(��N�%4g��`a�����9�v������(U.�|L$n2|d�z�b����I���h����I|8�}�;!�� ��h�Q����Z=��%�wQ���rbz����Z���u~$<�V� �<Y;D�J&�oXl�R�O���N`w�V���b�@V���|�e<c��v�+�<D�4 ���XV@�J�"`��h3*A�����������d3�@�����f��O�����-��
�bQ���������
��UQ_UX���a�TP�fo���������72���_)]��"�q:0�`Z�b���7�����!]��
�Hy�@`o�ci�.[���=�	���z\g=����#��(���Q���Q���*(st=���9�����SF�T�1�7D@2���Y�?b�XT�<|���E����/��(�J[Q8-��]�#���X�J���z��{�N!:q���x�H	�_�/t4���"����
���C7��IJ������^��o��x�C:H��YE���ph���vlu��7��!(�a�m�D^��>��{�5I�UB�S��_��M�U�/���:�K��/i|�~�y�:�5j����R���y��>��a�oU5�Z�%5�c%:<9Y��>;k�ju�����u�>����������M8����C��6���_[�p�t��������]P1/����m�JK"�LPj�t����z.�����0=K���e+w��~	~jYto{��\]F��yC�
6�����[�P��#�����U]
|t:Q��*d��y��,��v��Lg����
��[
�V��}��q�UY�W�����	mT�w�j��`�9��k}h'��������t���m�P�Oj�:m�N�g����08r9�!�V��(���{�����}�8<�V�L���-���E�������H(�
t��
�;�94 ���nCD���h����!���d���k�CY_s,����=�%��|}���q����p���5&1���������j�J�@��tU���RvTe��^���k��;H7��s2�Dt���^N��K�`R`#����h�@�&��G��q��-���t��hz�[o/;�f�CD1QD�i�x�g[���i���8��DeLI��\�G��{L���Fne��+����JQv��z��.�^w����n�]���i��|�L�[�8����X��zrx�u]��d^{Y�
��g��yu���u��w���`�4��(�eO5����O��z�����������l��B|h���n�����E�'��Y����k�5��m��]����d���,���q�c�]4�������Y�u�u^��[�5MDI9]1�p~���f�
9���U��rc�2�x���T�T�,���6�
C�#���1���I�Ov��%�����%�L��g��/�S4_R�_�	�2�\3�r��7�Skn��a`��N��i��7��$�� rS�m����\��n�I�o��%�"i;�~?��Y�l�L��l�
pZ�:���H5%j�y����R
���)��6��_OMJw��]�6R���a�H�dr�� ������l�U��~��!���m�����+E_{� Ie�A��r��pNO���m��+)%�t���M��"Cc��L��*R����w�2T*�r�����_:�I��H����5~�%�L<U)V��*���x����s�QK�5~����q�!�����%��J 7�p"�����p��W�1 y����n���������f0h�Q�a�����=��d��W�3�O5�--V�{�-
�n~@8���p������{�l��7�F�rb�X=q�=���h���sm���p��4����g�n�U�E�?��������_~��dY���*�c�����{�?A����	�K��M���Z,C,n�Z����_	���5yl�=�q��������'W����y _����s@���/��|U������W����$nT�1]��@�����w�D��{�o��x��W���bF��RZm3}^(�/}yV��j�
��
sV+M$K� ��Kr�j�m���m���s�����+����|��]R���Qj��C3J�����=�o��|������O{ k�I�$�H�o0"��@U���Do4���1^�h���.k�<�������-�^��	s���@����7�o�P!�8PG2KI�"��{T�B��>sw?����2�Fkwlk9�,��-9Co^�m-1{�'��f����2l�N~������O���rV��cv���}����
K}?+a?�� ���}$Y_��!��z��co�����X������"��N��^@1�,�Y���aw�3>|l�d�GQDSk{T�Dr��U��������G#��I�,���[f�#K�>�^�pGpU62a6a����rbWN��}k������N����Wm`�Vx��P�g���Yb�3�@��71�fP�V�L�~���'}�A�,�WC�Zu>f0�	���kG"j�&��,�o�/g���m�p~�,��JeO�y�|#����J�4��o�,����X
�?l?�&��	�D�z���������KH����1���Qf�����8V3���U�:���@���������C�����&������^���?L//�Z4�k� ������k�,b��7lc���"Y��r�X�k�'�]����-��^��m��n���-��#�_���v�����0;�����}�$���o�����z�����!Py�� :qV�^L��I��o�R�>WD��&���l���wq��=��Lz����%9�9�2�?_��%�K��F���(����;�Sq����5}(1]��.$�������z�Nno�rmno�4U����$��|����;Pm��
�/Iz@����Ki���m3������
�����D��"����=�hp�!����z{����YSg����t���^����H����7%�'*A�Vst�������?;�mk
V�����e0����#�i4��F�������
�Q�����l�RiF&������O#��6��nn��������
�s���5��Rl���v�%{��:��A�B��g[QS����K�0�I�/W���fuC�����F�!��C9��������k$���l��L����Tg0����r��J3T�f�`.j�+��;g{v��v<�Y/�8O���}�h�H7~�s����+3���q���X�������	Xg�������on�p��	d���1�sb����z���cuO�1���Vt�gf"
P��!��W{��/�A���t��~�Z����6��}�i	%�'��j�^���"��]~q

O�����<��1��h��+jt��/ 	�`��'.u3T��|4���c-�I�f�<DR��q)��������J���=�������=wl����?�(��_B\6�W<��
g�7�����o����9<p���/���j�@�9�[�����W������t>���OL��O�����G{�P�c�V���}V�!�3�{`����q����0/G�:�u��y���>���O��+&o�z+����*�����F�� 7=��b��"����0B
����d�����}�/p,��r"��8�e�@'�ng�c��m�F����W��%�FY�=:YQ�p�� �
�[�?t����������>���fY�?�d>����?�?�$=8��r��?H�:c�b�)9�9��)�Q-�cT�]�!���t��-}6z"���R=������oV�G^�P�`�(���n~����\��~O���A�?v�6�����U���^��I�:����y�ZY�Q-��Z%��a�&���f��v�^��3�N�b�]���������}9��x	�4]g�����5���m���K�r7l��V��"s��+��q"Z=��4�����	���eb3�a���K������ 09���*%g�F)��$<�b8�4��y�&���3���"�����D��zu"$����O[��j��7qr���?=��h�p�!4����-N6N����w�'�DJ��QO�"�
`[8(��O�n���Fp�v�����#1E�CX��[OV#�_:$��g��r�|	�{��=�p��P���78���d<��6?_2.��*8�������������;�{���c����v�����hD:9�K���,r�e���-+�l���L�����?����'�����1��o ���]|�@�!'?Wb9C�|�`�6�������$p��-���T��,�o_�f2�����p�t��4�����w�c��z��=����O����q������^��������b7���H�R�I�Y��1��?�)t;�aCGloE���)�(Q-�b1%�m�g������'xANk�,9�����iPv��9cb���S��\���
�t��W!�����]�E�Xx���<OB�G(1>(�}� R��r��^!�K��1���O��=Cy(�������o3�����c�g�����f���>"s}��72,W>����Q��Q��:��H9a?�C���8������}�n���Fj�H���H%���n�xO�P�9�h9��a�"�[wt������{����8�w�f���������'�;(�4N���������|5��k���/���B�i>�/��g��|��9X99�v2^���(����_�in��������90/���}��s��>X�Z-��B��W�{��J��
�|%_�?e�})_-�������0�o����f�����=��y�eBV��R!_-��Z�.5���<�J��U��F#_-7
�{;����`\_�����Y������r?����7H+����n��������Z�'���d8?����gH7|�,+�����X��B�Y���Rb��F>�[_�����������/=->3�C��9�
e�C�)u�$�
����t�\�
�����p�
Q�r�9~����^�p�0,����K�5��t~s����:z�	k�sFZ�Z\�&3�-�����;_M���V�7����� '����.���
�A��|t.����z��0�����I���9��B���o�%�W/&DV����%��P@T�*��Mnf��7�-T�D�{�m%�����V�l�{s����[}�)p�]�����;9%��f����G�Pb�'*F���;8��){"l�m��H�]Aga���\4{���a���r�h�<����_:\w��'|d��x��8��'XQC0dN!����	(�D5������_2!��i���y�ke���4��� e��1�n�L!���G'\��}�j������T������mH��-�E��-���)�����80�q`v�5�[�C��R�����! w������d�����u-���v�6�P���������d�����J���X���+`e�+h�Y"��c.�k%~�"wtp0���,������iD�4:���v~x=.7NN��a�T��?*�e�Y�wp����R=e���\�V�2p��}d�'3Y�>�:�l������G�f
m�:4C���Yd������J�|��!F���|}`b\[��@e�oy���
N;W�~�(��v�B(�`�#V�������79�(��3w�\�7���m��&�$V&��o�)��Y���P�RNB�[&"����� ��:~�a����!/�"a�����]�i]���}�v`|�GJv�B}c��o��V�u����!vF�f&�Pb>#���m���6d'0�����u�
�""��������+I�;�^�Rv�N_�9�_�9V���'4Sa	u����h�L����3B-eO)`��&��z���^L�%D�W���|O9P/����.q`�9���n���������=��4�v��0��jK��k�Y����E�i�p1o6,�A8��[�������>�JIAah�l��Y����E�j�V�*	BVN��E<J�|>���i���+@P��o����G�{Q,)!���������h|w1���EjQ���Y�x�$ �� ����I�����(ge�,)&e#&�����I������d�,
�#����0�HT$U�c�;U�S%�������%C�H���'�?9.��:T�W5������mC�����O�:��`�E�z��hQ�,�������Q���hP��i����>a�G#�h�f���a�I����P���R�2�`�gO���@���p\��D0����H(���(( ���Z��k�R����Z)�O��"�#A_�
mG�	�b���{���/������#X;1/5HI/T)�/y#�_\V���y�A�y�
6~��?z�J�	�"1@�����g�oR�4�m-���B=�����-a�j�����e8{_�g���7�)X=�91���A�?v������k�i8�^�'��,��TSq/�,�U(5���F���X�B����g����`����Wx�������\�p�\V����L�*�9yB�,%9���G����+���`�]��]�����-���L��{�u�S��_����������*�_���H>�]GC�����-W��l����+4��0��{��tO������5��cG5b9
�W�p���\e����^L�
I�TM�P�G�2�H�#mU1���B�k��:�J���T�����v�8�W�f�+7W�beI��zD��h_�H���2��n0�+������������r7XS�_Q6�G��r��"mzD���C_x�.E���@xZ2��G�AR�AD�]&��Cs�v���&�(%Ta���:u-`��G[�M�x�
c�#n,��4����Z�5��Dq���cq��t�DN}�+��c��*���x�+���n�4�����b���%@���n�����6������a��\�����@����!�+p�y5-��B�Y��*��y��	������n����Z�����w����E�-������������2����K-
L���!�N�/2��Z���</�:-+�eN�R�"��lk,�������f��T�*���f���CR�����L�`������1����flS�U8(c�WY���r�BTL�����N����qhQ�sPd��@��\���Z=�z�V���q�R�>������*DdA����3��g�����%��8|gs�:��>�T�^�V�~���f�7���@�E���]�C@y����7�a�=�{�e/:�w�}@1�e�S�WE[����D���r?�++�~��0�2����;�1Z��(=����#�����o=��
�k�,����8�L�]�m�6O����
\�%��=F9�����h8W������n�K��^����,o���
�|�������2��DH�����@����zO����
�{2�C�_t��� G�����~�s<���\�����|� ���9]���{q�@��nX#|�q��n_MC����Q&
(����J�J��|&��o�&�Z�������M���'{J�"���pZoEoJ��Yh3�G��}2�+��;X��,�Em[ Sg�L�������@OH{���5"~��QLL!�O�*x�T�O��}���d6�R`�k���NK�����b�+����p�C�%&.�e6�:���s�
�y������es�Y����?~�#c~����U1�Z!jRx���z}.j�N�����h�$`�W=�%�+�$_��1
!&�G`N$���Y��2e7��~NsJ�l����6����nh/��gx����Fx$RT�4L|Z$]��$i�bF�OZ$���>����Qd���CBiD��R���J�����Mj�-0�[�o��S��i]\����UkkR=�@F%�$��t���oiKPx�HH����n��p��j��? Z��u$\����
$g��p�R>%?@(l���4��;>p@��|>���=�Q����_9��g��������0/�A|�B�y>U��+}���z�R���qK�;�=
M�
$��8�r�����Y�1N�	��DJH���rm`�_0��nOj8���o�mo��-���A�$���(��##=�C� �P`���h�,�#����c��/���Q��j��m�8+�E	��4�������B�X������Z��#��{�`�~8,���Z�>�
U�����������+��UnkE�V}�����[fg������k���2��Ch5q(���q��&���(����:����GD��;�n>��i"���0��1
f�WR�@�Nh��ON���=��f�tdy:[O�:�Hxx8��Y�v�������'F �j
��'�@�/��x����C����3��"�hjC��Ij�e�w!�]%3���5�5���&` �)3�$�r�CLD����-gB�,!�(��A�8�U�$�^��
�\����"���>8F|e<5B���(�	d#������|��)��;`��}0	=��
�	��laO�z���w^.Q��b;�����AY0�SRHD�����2�"��� ����#�|m��k�n�1�@F���O�,'6F�*�{�f������R�	��8�g\#oY���m
�'0A�&~,zJ,�;���F����%�'8<���r ���#|�����DE%`.�=`!/�=I�x�����o��dQ�7P�&h�c/�4g&�I!������J�)��p]�|��;r�yT�9c!"S*C��^qd*�)�����X���CZ��1��"���K��,U����'3 �K�2�x��Jy�3��a������f����
L�!��pr��{p�V3"�I��-m�J����2����UVPN���Q�/�@_d''�>���O��p���J�1�T7�
E�,�1���;����=�Ut�]�W���!�bax�W�(�BK}��	������T���������W���MiQ��CoE���27C���������)����=���g���2SA�&�P�5���z>u+��d��,��B�����}�f+/����'d
"[��z��@?B������=�<v�5@/��F�����/��3Y�[,������H]=mo� ���b����Y����@��3��{TG�Q�,qm<��z�$�x� ����p�a�R����	��g�m`����������P�ry���@>}\!+HK���t���z��c��~z����0�xi� D���`x*>/r8���`�-�2F�����R P��`_mHp8aJn����dD���W��Q��#�($�_	���%����^.�19���!�3+K��32���K9�|>S�0r"v���N?R���;$�����	�	�x�]q"1����C�@���s�":F�Q�*5�O��4y�W�8�����Q1�0����I?�vr|~q����`>�7�����g���^������N[B�U��p�CHt3�CFKe1����X��S�����X@����8��XKH�jzs������<�����m(�����t�I���"���:1����vx�,+k��Nx
+Z60�i��\�Z�����'���c���k��c����H�)��Ja=^?���p�o��p,��R��,�G&��,�� ����W&q�/��4������H
@�+���p���-~�)�
����������wE�YL���f��,�������T�
K~=8P�t���{�	���|�
�B4�{98�.�;�gK���%R���8
~\%.��1"3�l
3�0gt}vwI�r���tr�0���tFe����������zw#G��{�t���|�h`'�<y�1�$�d},����1�N~L�(GHy=YAbP��`��G��*�I�1�;~}fX�*���U��j�=K��������������'���r�$��$���fN)���\�����
��y@@�'��H������ca��S>�>����X�L���/F��u?���RG���z:�������y�u�������r�����7p�f"4Q���f�9��c���0 z�&�"�od!+?�mq������/y�9�I�'_sd#9D;&������2^/!����"�|*�Dq�	I���D�Yd�����t�Q���Qq��	�>�Zv8k!z��0<"����b�(
zS����5d 8���3���#�����n�	R�b��k�!���e���/	xK��Jx�SQ"��}����u�M�_����Z�]��`����xO�������:8����0�aD8���l{E�kAa/^�@������U@%��@D�����c�����y#�k�s������>���S.���1r�g�H
�
������r����/���~���-�riX����~m��a�67�������BcT����y�[�����I�[��_t/���A��2�C}B������Q�����\��J��9��qh���������bD�

��!�\Ib�����}��������*3&]���mX�����
�{�b0�u1�U���N��7����E�j����~�V���.TUWat�������*���Q��B4����,#�_������oN������T���P���ns�@���V���2�{�����G��f�C��Y_�}_�-d�>:z����cj_�z��rx��"�"F�!������m�y��w�=v�|��G��Fd��Cp{�<6��-f�����V��E��:�Q�AnA#�,��d�s�F1�'�
��U�=���|z�(U��5�gQ)1g���������^��D��g�����Qg'�w"(
�T���H����,�h�"!G�R�6 #G�!��q��D��~���exFc@�!���d,QJW���9t��C�!�7_�By��uoA�����U�M������v�e�����&�*�K���m���=P��v����C�����ng�s��\������e��E�YU��m:D��L����C������U�M�{��M���:�0AL��7�nb���t5��
o��*��v9�u.v�z������
~��^��] .����������Y��������D�b1��@�5���|i�f�`gMsPnH�S��T�U��_�\���}���*���0_^���85���u�*��e�E���k�������_�g���R���U���'7Q����
�N�P&8
b�/M���,����MzY���3}(G�m�������J�.|�|�/�u���bb�z�����kQ�K��`A
���;�{����&#r�D]�@\��������3��b����
��Y��8PPu'��|���/i���A
	��IB�����D��B�5:���$����%�eT��!��\yP��t���J�^-�
�V�
���_��Msp�R|�g#8Uu����D|Dq���K//8�1�
� .)����58��:hw�����q�{���~7�.�����;HW��� ���n���G�����"�8��5������;�3)���)\�QK6�[��o������0��D�j�����
�5���`�R3dO����
#FC�6�@zn��!6	�Q���o�6�F�s����Pp�k94����J"�l�T�����S����x�D!y��E�K�
�.���f�}���� ����`��_�^�z�����M8qU��(����;n��P�p���b���8�i��I���x�
'��	�
.��Z����l6��g��1_"�+R>@z���/�)���
����e��~+� ��z*�7�� ����8��`���|6�'���0.�uu���U_�L��[���P�c���&=��=�n�ib)�������������9�Q5���y��-9�yc����z�T��V�Z���e�P�����W�n��5��g�~~��6//q�H3�IW���M�}vA+�]������?:��~(�x��b@f�N�~�����m~sL���x.I_����K7_=���=��W�@���=���o���������!�qW/�6*�q�P�Z_$��wAD�X��%��������C,�F�����j ���HOa�k�s"�b�k�1�������0�iE�^��.l�Fm�>�v��?�N[Z�0P�D�����(�9���X���6L�/[��zH�!S��oK�^I
�Ww�;���1qi���
��g4J���,������332D� ��(�Q��3��Y���P,-Q�MU�s��4������{���[���i]V���J�Xa��|��x��������7�"�gs������Y�����*����5�T���.����I2B[o/7��N���`�+�1q�4�hX*
Z)��$�-�@�{�=osi���m�W�s����t>������S��:���31F�������^�D��&��Wv)-�b��2N�G�^n��L�&��:W�����rtX�T�A�dbf��%���nO\��������\�����<������@���=_���q&�ko�-T%Z 
�^�`nk"�O��)B��-W���pU�L�Dd�/��f:&��1���J}QQ���[�s�p�7_�.�*#z��R�*�����P�
�t���V������Y2�\�y;T��{�������0K�b�/���t��s���^�`�e���d���o5�X�U�&u�������	(��}%L?h.s���b&��T��|�����bCpC E����k�/���
���x�:�����hGJFeJ�iP�EB������ob�*l3�\�'�;�,W:�C�7�!���K$���y��'M��i����
ZI�L
�c�@��n�W�q2�F���>��
HC��]��1r$�������	����R
�s]�Lw����"�jKs��7!����y�+�>{G~�pQ���Z������V�e�S�������n��uv��XRTH1���B|���].���o[g��8S'�����1M��S�_���������bh���}�oEu�
�0R,����C4J
�c��(���n��2�����O/(e�d�S*	:K=T��f/^�7���p[����3�@&Z�8�d�#���X�D�^�0h�a]���BZ��
�
5*�[��E���ue $�%$�`#���A��Mp��0������zw<	��@^,'_&S��`��&R�2��G$�����������J�XFWsv��f(�pf����g���x��M����x�n;���7��*�S���I����bBG�"��z
lJb7n�|��<>�������=�b����S�������M?����
dm`f�1
��<���{<'�w�����t��h�qa&������x���W|�iC����6bf68-r��p��� ObDZ�uh{��
����]d�74��������Q���F��	s����IvY�6'7��L�7��4���$�v���{������0�0��!��B"�?}+�cO���n�}��>\�z��_�r�������Tf�X.���}y�����������Z�&�C�>ov3Yw���m!5�&E����M��pHI�����3��I��bx�#�k�b9����+J�s�+,p��N�'`6x"�����:4�8��0b�z5 ��f��?uL�(�������%��-��y��0w�#����D@����������z�����?�y��;�����7���Bb�����B�cbyBw��1j���l�`����q�,Q����a_��I�/���L|5��.��F���Osu�		M�nc�kJ������?������$M5���h�V����s2��;�sK{�^:�/�����B)������=~(�3o�j�Qu\��������r�*U����Z)7�J����{���U��ES�gM��VP�P�t�zJ,��P��}�d+��0��r���DeR����	��7
�8pC �a2����P������i�1����H�+O�X)%(Q�X�#���t�''����/�}G:�lUx���fU�s�a��hW2�/��c���� ����A+U�����o��i���h����$-		0��"^�wQ�Zd�+d�uG�1�&R&Gx��2
X���/���T�U�s���nw0��/�F���sqQ{�a�_}Qk��i��52��B�FB!:�@��y�����n${�2��|<�~�8���l�K�4�:��(�E��K���n�Q.�z�l��[6�l��N[,*�a�7��L��q��)G�ZD�(?�>:�p��m�4p�b%�"�&�^�yI��������d��y���d���	?������_}�����.p�#�
�"�T
u�a<.V���3�����.
"K��Wn�zy�<og�h�2{5_��2B�����\�B�%���(�C�����w�F���Hi�0��[��KU�%�f��!���`�3dtF��!��pKQ������P!>�A
}p��g��1_�(����.�>����6�����j�g*8�������O��&C��	*% O��@��z�?���|��i@�f���
�?>zK)v�
�B�6����!�*T�\�v9b0�1n��*����o��2���E���%�R/��R5������HX�[��?"������;D�_��\G])B���=	Q��BM�C5� h�3{z�K~8�\�P[�r2�,Q�J��
5O�KI��S�����U�L��t�bb�R���TI��j��� E-�	b7��f�'{<��������PnF�!�kcv���e�T�Z!�$t�Zc�hD
��Q�4�h}���
�O����i�7�3�V8Q'�_x+	�d�����R�������?�#�zs���]�G���8'��$e�c��C��@��J�X�a��?h����bqX(X�������z��-k\��|��g����?+���K&�=��Q����^p��h(�Q���x�\s���]�(�X���BU�I�]N��(�f��f)/�����_��0Pj~�6{a�?9)������w�}�\B�=���'����1y(���/;=��!��;��f�v����|="2kL�������E�+��E������T]8����ty������h�e2Zs�t�SR�)�wg~��W�@����Zwd|�����d��������j�j�����;M�z��g�K�U����=�����_�S�����q%�c>����n1���s���C�^|�3���8D�q�
�J����k?�?�<��|?.j�x8��Z�fU��b����h��+�������>�W�������w��j�g�P�c�ZO'Q������+{W�J�w%�r�,�n�����Z�z�A�����dTU������P)70[}�[�#_���F�W��i�+��M&*�����q��������V�?UE��X���\�T�pv��J;��q��hp�xf����3�i<�+�Y�'^����&x�P%�H&�B���He��H�y�
 Gu;��	� �j�Ks�4KH�����$���}�������[����F�����Td��[���t%�����v���C�.yg��v�������MV�(]�d<u�y&�Jw�}�Y��-��I�W>
W����e�z��
�s���I���+�F�y���#�4�C��P9��p�d����G��j����B>'�e����O9�9a,�5��%�����R����=~P������U�._���=�T�����������\�5��Z�z�}��B�Y�j��
yW�C�.�N%8�IUt?CK�U�j�n�$J�t�6*x���W{H�N�u''�rc�/���L�V�dA����B�n�8l����V{f��~I����o
V5C�5�����AAt���P����7�1�n�|����G��O^�!��G)��
������h*
Q��F*����L
TI;A�a$"d�H����.�o��g9��t
�"O�U�r�^OM�����Q�Qv�b2;��'wM(?D����W0���I���O����������w�A��X�_s��6,����x<�V�z�|]�s�(��B�6�.�k�S��=+�&��-��F}��V�{y�nv?$���0����+<h�A��NNF�j����_�����1}���N4�F>0�����������3�B���������O�}y����/�8R���7?������e���%xmgY�9u�6���H���r�h�kk��3��[�hG2�KH����|�4�/��t0���b������Re&gSE�
Q#&
zD��l������W���3�9�8���8�a9
��((�5EX�7�4PX,����H#p-��=��e�-�	��R �A����d�
��O)4��R�~T��QDM*�;���s���	�#��3Q>$Rk�1[�~G/X��V�i���w��u��Q�o��K,�
_��x :vx;�&o8�N#�g�V9���I��������f��v��zbm��=����:��S������3����&-D�J*�H���t��i����|����<#���U�v�V��I���:��<"�����J�_������e���f��s	5���X)�?����C�_c<����V���j�b�����(�F�|iT-�j�R�������r��W���)uR|USo-�Yli)V�o��
k��� ����'�M�� ����[����@6�@��
�"!���A\q���K�j�w��F�b�N���
��Q0�p�����b7��a�	d�8��aM�j=���e%�d	e���29�OX�-���b�Ao4H�=9iX�Q���]��W��
U
��V�|�J�!�d�
����xO�K���6_�e�^�� Y���5�n�ys����i%)*�z��*��Z��0����V
������t��T�=���>�0��5l���U��j�~����yv���	0�����M)_��t���\��o��J�*�Zx�����I�������������Y������D���E�����t}7sX���dxK������#.�Y��=��S����hD��������a|9N�lf�#��oN���_�%D���]����[
����Kd�C�`�*>@�J<|���ESB��[MC���:�z����X|����1���������dL:�_%�?�:Q������;L|�;�����K��xb-�M�f�|h��l�d	���PI ��������8<t���R3]���5uK��}c���hah����
a�1�Wo�o~�_v.�O?(�I?"���Ev=
�X�'��!��&:���:��\;�����@��os����x�U��u��qNe.n-�����_! �l�+���d��]���R��W��~������F������JyX(�����U����>��G�Q�lY��k��T��+&�_)�����W�@jP��������Q�SY�R���2aC����U��������VX��E5[�^�<����jtH��N��5�-,b8<��@m�� ���o�h�^�;�����?7u?�1#�i��o�����SrP��o����~��g�!tQ)�)@�Jj*�a�JxF�2�!4����)�}�3�/p���_6��e���/A��r0�S�V�q����+��P�
v1_o�V�PW��QiT/�Cd8n\[�����T(?+�M�9���(O�P���5
�}�,��*3����z���������am��s�X����"{9��B���5�3���>4v��w$�������A�$�	O��������w��vP:�������$j\����&nQ��?v�o�c>�+��h��:���P�	�"y��T�P�y��O�B9_��~��.?x���uN�z���������R�V������5���*�qcTy��O�1P����������h<�+��q4b*���
:�(*��<�*�d������p5���Yt

��p7q�ak/�~���c��M'��>XN�Z�px��^%�,��]s�L���6���������.�{��)���F�+��pA����%kr�d��%|4AI���2����Cy'���Oe]E1W�����z��$s���������Wh���-*o�r���w������V��`I���< ���:ra��x0.t��E�j�5��u�su)������p����V_;pK(.@X���M����	%G��a�4�����L0��KC��(��-���F�V7�]�{��c_���v�RS+`��b���Y������2R�����(�[�R��V�{�&*������W�&n�.g�X�^�/�]�v03^`mYu<_�6��x~�%�D����+����UY��Y�6��9?�%}�a]0Y�{�>mze��Q�[�"UfP�^��`~�Q����E,�@�g��/ ����k$���������W//�O��S������O����nl�,hsE�����2�z)���qP������a,������eOd�)	N�0�Y�/�Q��3��AJ*e���`'����^�\�[(>�b���J�M9�s������xx���<`�1!�H����~%~�V�[f��{��vU������j����=��1`����>��uv.�P��
����E�d&�Nh��`]��17�
��i�<�����2�W�7����q���<���l���o�M�R��<��/�RV}~���(��I,A�C��t��K!(���Ac��xi�/Jx�#�v�8u ?!0�o��i���5Sb�	��G�"�1������������e����E�u�g�����*�j��[�����.?h����v���Y�B�2*�����hT*E�����e���]?x���b����W)o��V��+94x�KFY�S�����]X��fc��;PL����w{E�(�����h=����O������}����v�����X��`���'���K��M]�f�`\]L�`,��''k�������� ���7�����=���s� )�)?��_ :d�/H�w�p��O�)�)����x�
<���`���A���+����/;�7�5}A�C��(Q*T�%b-����|���A�����[�����~�ls�v?G��\�@�{x5R��`X��'\���*w,�u���G������Q���������vi���[��mA�%f-�'�����%��^�=��8�;*J��m
�Huu����e���3���j��e�,#&0Wi�"���G�|#��c����b0\}c/X$�>�+T���xD�ct�?\{�y��g��r�	'����TW����2Y�����UC��px�_�J�S��K�_�`��!_A�G�b����a��z��GT���/��9x��:��E����;���m��<�U��"��!���`M�����r�z�2
Q6�E���Jl���Y��:Yb��#��
q��yX+L���HM�c�\,���e��`��I	�&@:x�U)���
��t{����.p�l�l����[����)�u2P���y:\�������l��������41����[t�2���o>��e�	[$�k���T�����.���Wb�
�%�����B1��~��r�N?���S�h�	�D��%����?�����{����6�4j�Beh���r��>,6��q�a��Q�V,��Zc<����_4�����
��Q���p=b���6�S��� c��f5Khi���Z�LD��_��G�9�{iM��E����Uh��-�)�pt�;����%��vN[gW�v�l��j�NVe�	���|��`��C�~�����^����;����s�0e_�����mcq��$R�H�`F����+��g�X���	p�f>����������$��������8hw�����#8x������^��sY�dTqk�'�	��{�y����{�<@�eRyz1�l<��=�����*<\�9��`�R�|d��q"�#
����ZA�����+{�	=[e�k��z�yW�V�#<t��7�n���kg��L�H�B�������<>���Ld��w�,U-�5�V-�_[����W�����f�V��#�MG��#Q�`!�T���Nrr���v	Z��V��f.����~����UL8�"`9Z�������,���[�e��gO����,���-�z���v3����tR<
�-����$zy�I���JS�j���Q���v�l�X������P���B�Xjd�|��t�0n6��*g���	9k��\���Y4Q5Y�^�l�]��9��s>)�K��zz��	��&\��
�������Zz�z�����i�ZNK�+�Z����@���O�����d�������UJ�5���?�Ml
����w����7��nn�^�$���Ei�Ic�����%a��C���Eb��>�y]���T,}A��������Y+<5�r���X�9G%-Y��I�?�{DL_Ew��:���R���w�!kP6��vI_V��x���nZ�,��L/��V�c�sm����!��%YGX�0M�)
�d>����>#�8��R7t��K���kH1����0��H�"������ ��D��b��P��u������;�3]&L�����h��v'���D.�7�J��2�&�������~��8�6��J�(p
z�s���;�:W�/\���[�<{����������Db�%� A�rK��V��IY��{*�
f�(T��l�!����p���
�.u��W�����^*����#�/6�^�����i�e�]#3+�%����Zh1��eq{cN	�"�����m����u�������2���tP,�.SD��-������)�%�$�e���L>���q�2U�8���%�^��v�yg5SI��$�V`��6��iN�fa4���w����g��cNi*� G��*L��`���������0��\d��Gt�����_<���p=y��P|�Z��rr�^�i���T4����	������0�.����K�y9mv�aCl"�93���T�.L/}t���uQ�����F�*����:����j��`5|%��G�g�8�bC�[L����)T/��lG{����o��K����1���E�cy��:�R�F�6�y>� w�M>Np����52|�K.�kh�dN������F�d�;�gEuTre����^"v����KU���������F����yvziw�RA=i<V��;nT��{.B���*����)�V7
����f�A�[�Jq��S<���\�(h�,i[�8�`X�bAl�bA83�����>������*�h4yw�����A��k��c�DA:*��������������'KLtU�$rE��@*�����x��8����c)
�=���}k+9���4���v�tb�p�����wN��SRq���K���6y�����!�����2��iiU����f����6y��
m^�N�������3�����"$u��j�h �)q�'�iQu��I2���m�oJ�����#dI��%m
�,e��Vc#h�����x�Fn��8~�:|��{�%p��g_B|%)_��M�'��b=���Fab�c�������&���Z��9�#���R2���M��E�`G��j��a�c�h*�i������tL����s��b��GC	���v4�_�:��k��>K����aBi��`W��+���9�M\��v���_w[����i������/� ���������6&��`��
U&�d���4��l�Xyrn��w����mD��
�7��J����������5�����G��U�m�d���%'&YCf~fk���z=����9�b:jUOc������+��4i����U� ���;2�dFa�
��i#0�1�^�����_��j�ZNnn " �~��Lk���T��|f���lX���X��.1�&���)V��l0}ub�s���e!��,�'��b�x��T�w[Ga���&����^�ts;�f7k�F�n0gt�D���������D�^�HH=�d�D�1%,�����b1���`R��D�v0��w���������m��I'�-6t(G�	�"��9�w=f������=��y���%{��
������MsU�.A���vW�~v�R�}C0��}���J9�J!��.)�P�S&�
�����ka���@���/�2F�p�	�r��U�m^��+=G���d��
m'YM7
[x���������_,��Y��.�K��+���Kr�
��e�A���_W>V�6�3��st����^��"K�mu��)�f6�v2�hS�>)+����J�&��*��H�&L'B�s���VQ��	���3��X*����j~+�y���p�
(+,��LT
����a�M��a�G��a�I���8)vH�R3d�0���/LX(�:�.1!�NE�p����Hh���2��46k��I!]
�3��s�*�%��(����e���;m������0�
iQ��{U����~f6��EJ�_$, �x��A����.)�9�S=wf_�or��j�^A�'�l%x��O1����R!\�9_����{�`��aedY���h�j�|��#kTkF��J��
G�Z�0z������,+�W�5���B�Y���R4��c�'dPF
.��������A���
% ����M��4�a�	\�@���p=���#�W������t_���k�+H��_zf�'3H�q�����g��W������!u���(wg
�s������[�G�/��@+����Q��V�z���M��
�&I7E�����L�5��Z=�h��U�)��dPn#�u�\6J�����U��5;a�)o���S^C �6���;p��U�0s����K(u��n&\X�&� t���.���5��'���G�F[��������������<n<�����1�H�s)�z�k�\t^g�����������Ek0������$�[��
���x�6>�&�7����3���E��8���G���}6��Pz�2R���}�g?����.���m�r=���j�Q��CS����p�T��,5�{�����w����/�'G���0�h4�=����y�C.��z9Ss���H�"�`������������-�?�p��+"m��_y��#������@y��J1�&��n�1���<��r��Q+�@x��rm�!x��*F�����~[|�|OL��I��I�u(M�X�T����������y������4!BU����yD�b��;���!��t�����K����_����Oe�����ja$�Y��BT?��*�f��Y83(�\��L�:}q��'J�,[�X�	Q�%��[:��������R�����u�7����_��#��tL0}�2�h���_d�l{2�\��
~wg}�o})lD?�����F��7�K.�o ���8���$�N��$�����e�����l0��m`���neZ��*�_Jk��$����hlm0t����"G������c0��j�M��+0��`(?���P�TI��o���k���f4|����+6�$���6}�����^���t�#��AP��O]��cA@?�x�d�����.&�f@`�;h�;������z�_��4��Y_���!4]#;jD�L
��07*(�`�.���}��#G�������w���Brfs�@�4j����$��g+�:�����Y����Gc�G�G��#��9�����q��|�,G�;Go��Z���@Ux�*)YE�VG#{�Q�����xi���|i�����M�O�O��D�kTq'���H!���c�����h�'�x$���U�0���>���c�34z��1��?P/�X�P�o�kn�5���1^����!C0>{�s�(V������d��Lf?x�C�0.�"�ozl��������j����c��W��c���v�+ee~,�&L`���7Ra���f#���Q%� W��2b�r����J�Q76����Tz9��X3�n�{f/-����J����K�D��
6=�$	��|7���o���/pP����:U���{`���I���Q[��R5<8�N��g�h�O����e��;��>���^����s�����._���g�8qq��f��Cih�0��5JrTo�$�+�����i8_��& i��/�)��0��4h[D#b��*�GY�X�c�F���d*q�����z��+�q��r�}r����pB;�Ha���(��f�.�U�*k����Y���W�W����l�{:L���*b�|���k�ZC���Tm���
�����eX9x(��{����<�]$p�^<T\^�A�<����p�\��N������5����N�u��,����F��n��M�a�����7��7\a��\��	����1{����+������#�Y��qP���g#����.��5|N{��+�a�����=��d�x"��&��Q@��_GX��gXrz�5����g(�I.���'��\(��%�ZD������"IL�[�b$��.���R�e��\a�L(r��x���}
�n��	,��x����������@V�`Oiw0���t�w�o�H�����L���G�_|�.���T�"
4�K�K��p�2���X�=g�3�T�C.���@�	�I�K���/''�J-Ry�b�*�'��0{����L;�x<];�����iH�*�6���a��a��yk���������N7�m]^4O[����-��+_�Wk��R�R����=~���Q�n���r�`Wk�V�*k���e��Q�P�n����w���>�4�A���WCS�d��Jzp �L��\a��~~�59LVs�d��
W�+�������;9�4][��_�Q����|����R6."�LT��	cy1�"���Et�A���2��2g��VW$uT��AG��G�\��k\����pg�w��P�q�*��Q��8M�������5	!W�4V%�A~��^��=}�#��Spuc�(�	��#)Y�}��nV7G����)���^?0-xJhld�;�I����	��B�����R����x ���x������)��1>���J�W�F��'jt�-C���� Z�J��=�W��P�1���9T��
P��v�j9M����W���;�a�2aU�����N��(��0������V�o�0#k�B��xfu��Ltv���q|�9�7������)P�������ft>6A����� tyz��lsa��o?�����'��([�����b>����P����}�{68m�Z�����Q���2�����_��>~��R.����]~P�+X�j�*�+U�`���Q=��U��Re\.Y��u-o[�����`�����r�����k�b@��,��8��,zxp�Q<3��B�
���y2_��q��lOeyO���56���kt�J�h>D�����>]�cT�r��	|��HzdP�t�X�!��
��7_������e�LcXlY,�uXQ ���)��q;������c���+�Z�~rR.����@�N����4�8�*x���:�������(���N�,lM�/��;�^,��OO��p�asz���t�i���1�o�����k21��5����� ��@'�k�s�7��=���5���[tV_9���
������X�_J�/��.^t_�����E�����Go8B#��N<<z
7>?�_����u�~���/���K��#,,���6=�WhT��prb��|3GXV�����=�AE?��En]���l}��,�s��G�m
.��`�]kp)?�
.:��
D�\�{l/<��_g����\�G�W��^��������I���V�L��t������urr��G�z=��nW����mnZ�7V��{(��S,
\S�_~F|�����7�9����X�
q3pVKh=�[dh(.���<<�E	F���!nd�)w��.��@�c������|g-[|n[��|	�'�3�k&y�)�����wD��������U��[���U/�)�H8"��0�k�6Xaz=9�V���p�ol���W���sx���<G�D#[��,�}�.#F�2�x�H��P��Y�G����|Rh�s�P���Hx;n�{�(�`���2�F&u(d4V
����72�9R��1�j��'��8\���c����������&�CV�E�H������h����}��e"L���	��
��=D�I���S�5!�ok4�E�������	���e��	K<v���~lgh-$�!c����5�5 �����j�$����^��P(�r�b"=��i5����g�pY�����W��g�����o����sa =������e)�����U������<x��`�e�?�L�Z�����@pY�c��4��?���o�W�����}e�K?�)��j�3G����������u""Yz���5�����V����c_1)G\�X+���:�����qjp���/����p�-9������4@��a�����!�����.EF=�����09=����a�`��K�A�!��q�Wr�������}������F���
:�N�t"i���Ar������t
�f��3�����Y�-�T�� ���T��4�n��.��\�!9@���+�"�M��H�?��*8����#,$��������~����=5q?���}
A?��m,�'-:|��Mk����P��*��A�2���\x����������\��&rp����96��8\9��h�s�F������������O�[�a��?h����j���G�E0��G����2��Ri��XV�dU����k�-�Uj&�o��=7����M��P�f��?��O8��N�p���k��
QBq&"1�+����
w^X"\��3~���j~���	�� ��R��`��~#�
S�,m��C2)p���I"W$N��7��T�
����"�RB}X��''���T���)�~!�X�'5��]�����$Q�FLf`8���:�W �����X��l�B	�x�SP�'��)4?�M���4>�c�����-��R��2qB}}���
&��7�Js��	D��!��{a
m������T���P�k�����[J��d>�e�d~^b���eA��S0(�"}4)��R~��W��}��@������| ���}��{�L�i_T�^�8���I��5I�����F���[�b�
,���^6�h�N"S�/a�?�??~~������������??~�����l<��
#74Pavel Stehule
pavel.stehule@gmail.com
In reply to: Kyotaro HORIGUCHI (#73)
Re: IF (NOT) EXISTS in psql-completion

2017-02-14 11:51 GMT+01:00 Kyotaro HORIGUCHI <
horiguchi.kyotaro@lab.ntt.co.jp>:

Thank you for the comment.

At Mon, 6 Feb 2017 17:10:43 +0100, Pavel Stehule <pavel.stehule@gmail.com>
wrote in <CAFj8pRD85cnxEEgLtOoqL-Bda2XpzvHB3a6Mr+bvf+OKpiq3Eg@
mail.gmail.com>

0001-Refactoring-tab-complete-to-make-psql_completion-cod.patch
- Just a refactoring of psql_completion

0002-Make-keywords-case-follow-to-input.patch
- The letter case of additional suggestions for
COMPLETION_WITH_XX follows input.

0003-Introduce-word-shift-and-removal-feature-to-psql-com.patch
- A feature to ignore preceding words. And a feature to remove
intermediate words.

0004-Add-README-for-tab-completion.patch
- README

0005-Make-SET-RESET-SHOW-varialble-follow-input-letter-ca.patch
0006-Allow-complete-schema-elements-in-more-natural-way.patch
0007-Allow-CREATE-RULE-to-use-command-completion-recursiv.patch
0008-Allow-completing-the-body-of-EXPLAIN.patch
0009-Simpilfy-ALTER-TABLE-ALTER-COLUMN-completion.patch
0010-Simplify-completion-for-CLUSTER-VERBOSE.patch
0011-Simplify-completion-for-COPY.patch
0012-Simplify-completion-for-CREATE-INDEX.patch
0013-Simplify-completion-for-CREATE-SEQUENCE.patch
0014-Simplify-completion-for-DROP-INDEX.patch
0015-Add-CURRENT_USER-to-some-completions-of-role.patch
0016-Refactor-completion-for-ALTER-DEFAULT-PRIVILEGES.patch
0017-Add-suggestions-of-IF-NOT-EXISTS.patch
- A kind of sample refctoring (or augmenting) suggestion code
based on the new infrastructure.

0018-Debug-output-of-psql-completion.patch
- Debug logging for psql_completion (described above)

0019-Add-suggestion-of-OR-REPLACE.patch
- Suggestion of CREATE OR REPLACE.

# I hear the footsteps of another conflict..

The patch 0018 was not be applied.

The fear came true. fd6cd69 conflicts with it but on a
comment. The attached patch set applies on top of the current
master head (ae0e550).

Now first patch is broken :(

It is pretty sensitive to any changes. Isn't possible to commit first four
patches first and separately maybe out of commitfest window?

Few other notes from testing - probably these notes should not be related
to your patch set

1. When we have set of keywords, then the upper or lower chars should to
follow previous keyword. Is it possible? It should to have impact only on
keywords.

It sounds reasonable, more flexible than "upper"/"lower" of
COMP_KEYWORD_CASE. The additional 20th(!) patch does that. It
adds a new value 'follow-first' to COMP_KEYWORD_CASE. All
keywords in a command line will be in the case of the first
letter of the first word. ("CREATE" in the following case. I
think it is enogh for the purpose.)

postgres=# \set COMP_KEYWORD_CASE follow-first
postgres=# CREATE in<tab>
=># CREATE INDEX hoge <tab>
=># CREATE INDEX hoge ON emp<tab>
=># CREATE INDEX hoge ON employee ..
postgres=# create IN<tab>
=># create index

Typing tab at the first in a command line shows all available
keywords in upper case.

It is great - from my perspective the best step in last years in this area.

2. the list of possible functions after EXECUTE PROCEDURE in CREATE

TRIGGER

statement should be reduced to trigger returns function only.

Actually Query_for_list_of_trigger_functions returns too many
candidates. The suggested restriction reduces them to a
reasonable number. The 21th patch does that.

CREATE OR REPLACE FUNCTIONS works great, thank you!

Thanks. It was easier than expected.

As the result, 21 paches are attached to this message. 1 - 19th
are described above and others are described below.

0020-New-COMP_KEYWORD_CASE-mode-follow-first.patch

- Add new COMP_KEYWORD_CASE mode "follow-first". The completion
works with the case of the first word. (This doesn't rely on
this patchset but works in more cases with 0002)

0021-Suggest-only-trigger-functions-for-CREAET-TRIGGER.EX.patch
- Restrict suggestion for the syntax to ones acutually usable
there. (This relies on none of this patchset, though..)

regards,

Thank you very much for this your work.

Regards

Pavel

Show quoted text

--
Kyotaro Horiguchi
NTT Open Source Software Center

#75Robert Haas
robertmhaas@gmail.com
In reply to: Pavel Stehule (#74)
Re: IF (NOT) EXISTS in psql-completion

On Wed, Feb 22, 2017 at 12:38 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Now first patch is broken :(

It is pretty sensitive to any changes. Isn't possible to commit first four
patches first and separately maybe out of commitfest window?

Yeah, maybe, but we'd need a committer to take more of an interest in
this patch series. Personally, I'm wondering why we need a series of
19 patches to add tab completion support for IF NOT EXISTS. The
feature which is the subject of this thread arrives in patch 0017, and
a lot of the patches which come before that seem to change a lot of
stuff without actually improving much that would really benefit users.

0001 seems like a lot of churn for no real benefit that I can immediately see.
0002 is a real feature, and probably a good one, though unrelated to
the subject of this thread. In the process, it changes many lines of
code in fairly mechanical ways; does it need to do that?
0003 is infrastructure.
0004 adds a README. Do we really need that? It seems to be
explaining things which are mostly fairly clear from just looking at
the code. If we add a README, we have to update it when we change
things. That's worthwhile if it helps people write code better, I'm
not sure if it will do that.
0005 extends 0002.
0006 prevents incorrect completions in obscure circumstances.
0007 adds some kind of tab completion for CREATE RULE; I'm fuzzy on the details.
0008 improves tab completion after EXPLAIN.
0009-0014 uses the infrastructure from 0003 to improve tab completion
for various commands. They say they're merely simplifying tab
completion for those things, but actually they're extending it to some
obscure situations that aren't currently covered.
0015 adds completion for magic keywords like CURRENT_USER when role
commands are used.
0016 refactors tab completion for ALTER DEFAULT PRIVILEGES, possibly
improving it somehow.
0017 implements the titular feature.
0018 adds optional debugging output.
0019 improves things for CREATE OR REPLACE completion.

Phew. That's a lot of work for relatively obscure improvements to tab
completion. I grant that the result is probably better, but it's a
lot of code change for what we get out of it. I'm not saying we
should reject it on that basis, but it may be the reason why nobody's
jumped in to work on getting this committed.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#76Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#75)
Re: IF (NOT) EXISTS in psql-completion

2017-02-26 19:43 GMT+01:00 Robert Haas <robertmhaas@gmail.com>:

On Wed, Feb 22, 2017 at 12:38 AM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Now first patch is broken :(

It is pretty sensitive to any changes. Isn't possible to commit first

four

patches first and separately maybe out of commitfest window?

Yeah, maybe, but we'd need a committer to take more of an interest in
this patch series. Personally, I'm wondering why we need a series of
19 patches to add tab completion support for IF NOT EXISTS. The
feature which is the subject of this thread arrives in patch 0017, and
a lot of the patches which come before that seem to change a lot of
stuff without actually improving much that would really benefit users.

0001 seems like a lot of churn for no real benefit that I can immediately
see.
0002 is a real feature, and probably a good one, though unrelated to
the subject of this thread. In the process, it changes many lines of
code in fairly mechanical ways; does it need to do that?
0003 is infrastructure.
0004 adds a README. Do we really need that? It seems to be
explaining things which are mostly fairly clear from just looking at
the code. If we add a README, we have to update it when we change
things. That's worthwhile if it helps people write code better, I'm
not sure if it will do that.

it needs a separation to refactoring part and to new features part. The
refactoring looks well and I am sure so has sense.

about README - there are described fundamental things - that should be
stable. With last changes and this set of patches, the autocomplete is not
trivial and I am sure, so any documentation is better than nothing. Not all
developers has years of experience with PostgreSQL hacking.

0005 extends 0002.
0006 prevents incorrect completions in obscure circumstances.
0007 adds some kind of tab completion for CREATE RULE; I'm fuzzy on the
details.
0008 improves tab completion after EXPLAIN.
0009-0014 uses the infrastructure from 0003 to improve tab completion
for various commands. They say they're merely simplifying tab
completion for those things, but actually they're extending it to some
obscure situations that aren't currently covered.
0015 adds completion for magic keywords like CURRENT_USER when role
commands are used.
0016 refactors tab completion for ALTER DEFAULT PRIVILEGES, possibly
improving it somehow.
0017 implements the titular feature.
0018 adds optional debugging output.
0019 improves things for CREATE OR REPLACE completion.

Phew. That's a lot of work for relatively obscure improvements to tab
completion. I grant that the result is probably better, but it's a
lot of code change for what we get out of it. I'm not saying we
should reject it on that basis, but it may be the reason why nobody's
jumped in to work on getting this committed.

These patches are big - but in the end it cleaning tab complete code, and
open a doors for more smarter completion.

Some features can be interesting for users too - repeated writing IF
EXISTS, IF NOT EXISTS or OR REPLACE is really scary - mainly so some other
parts of tab complete are friendly enough now.

Can be solution a splitting this set of patches to more independent parts?
We should to start with refactoring. Other patches can be processed
individually - with individual discussion.

Regards

Pavel

Show quoted text

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#77Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#75)
Re: IF (NOT) EXISTS in psql-completion

Robert Haas <robertmhaas@gmail.com> writes:

Yeah, maybe, but we'd need a committer to take more of an interest in
this patch series. Personally, I'm wondering why we need a series of
19 patches to add tab completion support for IF NOT EXISTS. The
feature which is the subject of this thread arrives in patch 0017, and
a lot of the patches which come before that seem to change a lot of
stuff without actually improving much that would really benefit users.

FWIW, one reason this committer hasn't jumped in is that we already
rewrote tab-complete.c pretty completely in 9.6. If we accept a patch
that completely rewrites it again, we're going to be faced with
maintaining three fundamentally different implementations for the next
three-plus years (until 9.5 dies). Admittedly, we don't back-patch
fixes in tab-complete.c every week, but a look at the git history says
we do need to do that several times a year.

Also, the nature of the primary refactoring (changing the big else-chain
into standalone ifs, if I read it correctly) is particularly bad from a
back-patching standpoint because all you have to do is insert an "else",
or fail to insert one, to silently and almost completely break either
one or the other branch. And I don't really understand why that's a good
idea anyway: surely we can return at most one set of completions, so how
is turning the function into a lot of independent actions a win?

So I'd be a whole lot happier if it didn't do that. Can we really not
add the desired features in a more localized fashion?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#78Michael Paquier
michael.paquier@gmail.com
In reply to: Tom Lane (#77)
Re: IF (NOT) EXISTS in psql-completion

On Mon, Feb 27, 2017 at 5:21 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

Yeah, maybe, but we'd need a committer to take more of an interest in
this patch series. Personally, I'm wondering why we need a series of
19 patches to add tab completion support for IF NOT EXISTS. The
feature which is the subject of this thread arrives in patch 0017, and
a lot of the patches which come before that seem to change a lot of
stuff without actually improving much that would really benefit users.

FWIW, one reason this committer hasn't jumped in is that we already
rewrote tab-complete.c pretty completely in 9.6. If we accept a patch
that completely rewrites it again, we're going to be faced with
maintaining three fundamentally different implementations for the next
three-plus years (until 9.5 dies). Admittedly, we don't back-patch
fixes in tab-complete.c every week, but a look at the git history says
we do need to do that several times a year.

Indeed, having worked on the 9.6 refactoring a bit as well... I'll
vote for not doing this again as HEAD is in a more readable shape
compared to the pre-9.5 area, and I am not convinced that it is worth
the trouble. There are a couple of things that can be extracted from
this set of patches, but I would vote for not doing the same level of
refactoring.

Also, the nature of the primary refactoring (changing the big else-chain
into standalone ifs, if I read it correctly) is particularly bad from a
back-patching standpoint because all you have to do is insert an "else",
or fail to insert one, to silently and almost completely break either
one or the other branch. And I don't really understand why that's a good
idea anyway: surely we can return at most one set of completions, so how
is turning the function into a lot of independent actions a win?

So I'd be a whole lot happier if it didn't do that. Can we really not
add the desired features in a more localized fashion?

As "if not exists" is defined after the object type if would not be
that complicated to add completion for IE/INE after the object type
with a set of THING_* flags in words_after_create. One missing piece
would be to add completion for the objects themselves after IE or INE
have been entered by the user, but I would think that tweaking the
checks on words_after_create[i] would be doable as well. And that
would be localized.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#79Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#78)
Re: IF (NOT) EXISTS in psql-completion

Michael Paquier <michael.paquier@gmail.com> writes:

On Mon, Feb 27, 2017 at 5:21 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

So I'd be a whole lot happier if it didn't do that. Can we really not
add the desired features in a more localized fashion?

As "if not exists" is defined after the object type if would not be
that complicated to add completion for IE/INE after the object type
with a set of THING_* flags in words_after_create. One missing piece
would be to add completion for the objects themselves after IE or INE
have been entered by the user, but I would think that tweaking the
checks on words_after_create[i] would be doable as well. And that
would be localized.

BTW ... can anyone explain to me the reason why we offer to complete
CREATE OBJECT with the names of existing objects of that kind?
That seems pretty darn stupid. I can see offering the names of existing
schemas there, if the object type is one that has schema-qualified names,
but completing with an existing object name is just setting up to fail
isn't it?

If we dropped that behavior, seems like it would become much easier
to plug in IF NOT EXISTS at those spots.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#80Michael Paquier
michael.paquier@gmail.com
In reply to: Tom Lane (#79)
Re: IF (NOT) EXISTS in psql-completion

On Mon, Feb 27, 2017 at 10:12 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Michael Paquier <michael.paquier@gmail.com> writes:

On Mon, Feb 27, 2017 at 5:21 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

So I'd be a whole lot happier if it didn't do that. Can we really not
add the desired features in a more localized fashion?

As "if not exists" is defined after the object type if would not be
that complicated to add completion for IE/INE after the object type
with a set of THING_* flags in words_after_create. One missing piece
would be to add completion for the objects themselves after IE or INE
have been entered by the user, but I would think that tweaking the
checks on words_after_create[i] would be doable as well. And that
would be localized.

BTW ... can anyone explain to me the reason why we offer to complete
CREATE OBJECT with the names of existing objects of that kind?
That seems pretty darn stupid. I can see offering the names of existing
schemas there, if the object type is one that has schema-qualified names,
but completing with an existing object name is just setting up to fail
isn't it?

Isn't that to facilitate commands appended after CREATE SCHEMA? Say
table foo is in schema1, and creating it in schema2 gets easier with
tab completion?
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#81Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#80)
Re: IF (NOT) EXISTS in psql-completion

Michael Paquier <michael.paquier@gmail.com> writes:

On Mon, Feb 27, 2017 at 10:12 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

BTW ... can anyone explain to me the reason why we offer to complete
CREATE OBJECT with the names of existing objects of that kind?

Isn't that to facilitate commands appended after CREATE SCHEMA? Say
table foo is in schema1, and creating it in schema2 gets easier with
tab completion?

Seems like pretty much of a stretch. I've never done anything like
that, have you?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#82Michael Paquier
michael.paquier@gmail.com
In reply to: Tom Lane (#81)
Re: IF (NOT) EXISTS in psql-completion

On Mon, Feb 27, 2017 at 10:20 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Michael Paquier <michael.paquier@gmail.com> writes:

On Mon, Feb 27, 2017 at 10:12 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

BTW ... can anyone explain to me the reason why we offer to complete
CREATE OBJECT with the names of existing objects of that kind?

Isn't that to facilitate commands appended after CREATE SCHEMA? Say
table foo is in schema1, and creating it in schema2 gets easier with
tab completion?

Seems like pretty much of a stretch. I've never done anything like
that, have you?

Never, but that was the only reason I could think about. I recall
reading something else on -hackers but I cannot put my finger on it,
nor does a lookup at the archives help... Perhaps that's the one I
just mentioned as well.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#83Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Michael Paquier (#82)
Re: IF (NOT) EXISTS in psql-completion

At Mon, 27 Feb 2017 10:43:39 +0900, Michael Paquier <michael.paquier@gmail.com> wrote in <CAB7nPqQr7apg8W+p41W1azTjy7LSasSEvWvKePTU4knnxWCZkw@mail.gmail.com>

On Mon, Feb 27, 2017 at 10:20 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Michael Paquier <michael.paquier@gmail.com> writes:

On Mon, Feb 27, 2017 at 10:12 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

BTW ... can anyone explain to me the reason why we offer to complete
CREATE OBJECT with the names of existing objects of that kind?

Isn't that to facilitate commands appended after CREATE SCHEMA? Say
table foo is in schema1, and creating it in schema2 gets easier with
tab completion?

Seems like pretty much of a stretch. I've never done anything like
that, have you?

Never, but that was the only reason I could think about. I recall
reading something else on -hackers but I cannot put my finger on it,
nor does a lookup at the archives help... Perhaps that's the one I
just mentioned as well.

I suppose it is for suggesting what kind of word should come
there, or avoiding silence for a tab. Or for symmetry with other
types of manipulation, like DROP. Another possibility is creating
multiple objects with similar names, say CREATE TABLE
employee_x1, CREATE TABLE employee_x2. Just trying to complete
existing *schema* is one more another possible objective.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#84Stephen Frost
sfrost@snowman.net
In reply to: Kyotaro HORIGUCHI (#83)
Re: IF (NOT) EXISTS in psql-completion

* Kyotaro HORIGUCHI (horiguchi.kyotaro@lab.ntt.co.jp) wrote:

I suppose it is for suggesting what kind of word should come
there, or avoiding silence for a tab. Or for symmetry with other
types of manipulation, like DROP. Another possibility is creating
multiple objects with similar names, say CREATE TABLE
employee_x1, CREATE TABLE employee_x2. Just trying to complete
existing *schema* is one more another possible objective.

I don't buy any of these arguments either. I *really* don't want us
going down some road where we try to make sure that hitting 'tab' never
fails...

Thanks!

Stephen

#85David Fetter
david@fetter.org
In reply to: Stephen Frost (#84)
Re: IF (NOT) EXISTS in psql-completion

On Mon, Feb 27, 2017 at 11:53:17PM -0500, Stephen Frost wrote:

* Kyotaro HORIGUCHI (horiguchi.kyotaro@lab.ntt.co.jp) wrote:

I suppose it is for suggesting what kind of word should come
there, or avoiding silence for a tab. Or for symmetry with other
types of manipulation, like DROP. Another possibility is creating
multiple objects with similar names, say CREATE TABLE employee_x1,
CREATE TABLE employee_x2. Just trying to complete existing
*schema* is one more another possible objective.

I don't buy any of these arguments either. I *really* don't want us
going down some road where we try to make sure that hitting 'tab'
never fails...

Wouldn't that just be a correct, grammar-aware implementation of tab
completion? Why wouldn't you want that?

Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david(dot)fetter(at)gmail(dot)com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#86Stephen Frost
sfrost@snowman.net
In reply to: David Fetter (#85)
Re: IF (NOT) EXISTS in psql-completion

* David Fetter (david@fetter.org) wrote:

On Mon, Feb 27, 2017 at 11:53:17PM -0500, Stephen Frost wrote:

* Kyotaro HORIGUCHI (horiguchi.kyotaro@lab.ntt.co.jp) wrote:

I suppose it is for suggesting what kind of word should come
there, or avoiding silence for a tab. Or for symmetry with other
types of manipulation, like DROP. Another possibility is creating
multiple objects with similar names, say CREATE TABLE employee_x1,
CREATE TABLE employee_x2. Just trying to complete existing
*schema* is one more another possible objective.

I don't buy any of these arguments either. I *really* don't want us
going down some road where we try to make sure that hitting 'tab'
never fails...

Wouldn't that just be a correct, grammar-aware implementation of tab
completion? Why wouldn't you want that?

No, it wouldn't, it would mean we have to provide something for cases
where it doesn't make sense to try and provide an answer, as being
discussed here for CREATE TABLE.

We can't provide an answer based on tab-completion to what you want to
call your new table.

Thanks!

Stephen

#87Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Stephen Frost (#86)
Re: IF (NOT) EXISTS in psql-completion

Hello,

At Tue, 28 Feb 2017 10:39:01 -0500, Stephen Frost <sfrost@snowman.net> wrote in <20170228153901.GH9812@tamriel.snowman.net>

* David Fetter (david@fetter.org) wrote:

On Mon, Feb 27, 2017 at 11:53:17PM -0500, Stephen Frost wrote:

* Kyotaro HORIGUCHI (horiguchi.kyotaro@lab.ntt.co.jp) wrote:

I suppose it is for suggesting what kind of word should come
there, or avoiding silence for a tab. Or for symmetry with other
types of manipulation, like DROP. Another possibility is creating
multiple objects with similar names, say CREATE TABLE employee_x1,
CREATE TABLE employee_x2. Just trying to complete existing
*schema* is one more another possible objective.

I don't buy any of these arguments either. I *really* don't want us
going down some road where we try to make sure that hitting 'tab'
never fails...

These suggestions exist before this patch. Whether to remove them
would be another discussion. I was going to add some but finally
I believe I have added no such things in this patchset.

Wouldn't that just be a correct, grammar-aware implementation of tab
completion? Why wouldn't you want that?

No, it wouldn't, it would mean we have to provide something for cases
where it doesn't make sense to try and provide an answer, as being
discussed here for CREATE TABLE.

We can't provide an answer based on tab-completion to what you want to
call your new table.

The difference seems to be that what we take this feature to
be. If we see it as just a fast-path of entering a word where we
know what words should come, silence is not a problem. If we see
it as safety-wheels to guide users to the right way to go,
silence would be bad. A silence during word completion suggests
something wrong in the preceding words to me so it is a bit
annoying.

As an analogous operation, mkdir on bash suggests existing
directories. We can suggest existing tables for CREATE TABLE with
the same basis.

Another possible way to go would be showing a 'suggestion' not a
list of possibilities. If readline allows such operation, I
imagine the following. But this is a quite diferrent discussion.

=# CREATE TABLE <tab>
<<a table name to create>>
=# CREATE TABLE table1 <tab>

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#88David Steele
david@pgmasters.net
In reply to: Kyotaro HORIGUCHI (#87)
Re: IF (NOT) EXISTS in psql-completion

Hello,

On 3/1/17 9:38 PM, Kyotaro HORIGUCHI wrote:

At Tue, 28 Feb 2017 10:39:01 -0500, Stephen Frost <sfrost@snowman.net> wrote in <20170228153901.GH9812@tamriel.snowman.net>

* David Fetter (david@fetter.org) wrote:

On Mon, Feb 27, 2017 at 11:53:17PM -0500, Stephen Frost wrote:

* Kyotaro HORIGUCHI (horiguchi.kyotaro@lab.ntt.co.jp) wrote:

I suppose it is for suggesting what kind of word should come
there, or avoiding silence for a tab. Or for symmetry with other
types of manipulation, like DROP. Another possibility is creating
multiple objects with similar names, say CREATE TABLE employee_x1,
CREATE TABLE employee_x2. Just trying to complete existing
*schema* is one more another possible objective.

I don't buy any of these arguments either. I *really* don't want us
going down some road where we try to make sure that hitting 'tab'
never fails...

These suggestions exist before this patch. Whether to remove them
would be another discussion. I was going to add some but finally
I believe I have added no such things in this patchset.

Wouldn't that just be a correct, grammar-aware implementation of tab
completion? Why wouldn't you want that?

No, it wouldn't, it would mean we have to provide something for cases
where it doesn't make sense to try and provide an answer, as being
discussed here for CREATE TABLE.

We can't provide an answer based on tab-completion to what you want to
call your new table.

The difference seems to be that what we take this feature to
be. If we see it as just a fast-path of entering a word where we
know what words should come, silence is not a problem. If we see
it as safety-wheels to guide users to the right way to go,
silence would be bad. A silence during word completion suggests
something wrong in the preceding words to me so it is a bit
annoying.

As an analogous operation, mkdir on bash suggests existing
directories. We can suggest existing tables for CREATE TABLE with
the same basis.

Another possible way to go would be showing a 'suggestion' not a
list of possibilities. If readline allows such operation, I
imagine the following. But this is a quite diferrent discussion.

=# CREATE TABLE <tab>
<<a table name to create>>
=# CREATE TABLE table1 <tab>

regards,

It has been a while since this thread has received any comments or a new
patch. The general consensus seems to be that this feature is too large
a rewrite of tab completion considering a major rewrite was done for 9.6.

Are you considering writing a localized patch for this feature as Tom
suggested? If so, please post that by 2017-03-16 AoE.

If no new patch is submitted by that date I will mark this submission
"Returned with Feedback".

Thanks,
--
-David
david@pgmasters.net

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#89Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: David Steele (#88)
Re: IF (NOT) EXISTS in psql-completion

At Mon, 13 Mar 2017 10:42:05 -0400, David Steele <david@pgmasters.net> wrote in <1e8297fd-f7f2-feab-848d-5121e45c8cba@pgmasters.net>

It has been a while since this thread has received any comments or a new
patch. The general consensus seems to be that this feature is too large
a rewrite of tab completion considering a major rewrite was done for 9.6.

Are you considering writing a localized patch for this feature as Tom
suggested? If so, please post that by 2017-03-16 AoE.

If no new patch is submitted by that date I will mark this submission
"Returned with Feedback".

It's a pity. I had to take a week's leave..

I understood that the 'localized fashion' means "without removing
eles's", that is the core of this patch. So, if it is not
acceptable this should be abandoned. I'll try put the inidividual
enahancements other than refactoring in other shape, in the next
commitfest.

Thanks.

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#90David Steele
david@pgmasters.net
In reply to: Kyotaro HORIGUCHI (#89)
Re: IF (NOT) EXISTS in psql-completion

On 3/17/17 3:58 AM, Kyotaro HORIGUCHI wrote:

At Mon, 13 Mar 2017 10:42:05 -0400, David Steele <david@pgmasters.net> wrote in <1e8297fd-f7f2-feab-848d-5121e45c8cba@pgmasters.net>

It has been a while since this thread has received any comments or a new
patch. The general consensus seems to be that this feature is too large
a rewrite of tab completion considering a major rewrite was done for 9.6.

Are you considering writing a localized patch for this feature as Tom
suggested? If so, please post that by 2017-03-16 AoE.

If no new patch is submitted by that date I will mark this submission
"Returned with Feedback".

It's a pity. I had to take a week's leave..

I understood that the 'localized fashion' means "without removing
eles's", that is the core of this patch. So, if it is not
acceptable this should be abandoned. I'll try put the inidividual
enahancements other than refactoring in other shape, in the next
commitfest.

I have marked this submission "Returned with Feedback". Please feel
free to resubmit when you have a new version.

--
-David
david@pgmasters.net

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers