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
>From 82928996a0887882212d05aca3406a3bd3cf22e5 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyot...@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

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

Reply via email to