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 <da...@pgmasters.net> wrote in 
<56f17977.8040...@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
>From 2f46c0aa00fd8fd6357dcb76a26e49e3a66e2572 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 | 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

-- 
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