From 9c72fe4b22b96890aaeb87c3e695ac4cf68fcbdc Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Mon, 28 Nov 2016 15:14:35 +0900
Subject: [PATCH 04/12] Allow complete schema elements in more natural way

Some syntaxes like CREATE TABLE|INDEX, GRANT or REVOKE can be schema
elements of CREATE SCHEMA. Currently Matches/HeadMatches cannot be
used in completing schema elements since they begin midst of input
words. It leads to unnatural difference on completions that cannot use
TailMatches.

This patch allows to shift the beginning of input words so that schema
elements can be completed in just the same way with bare syntax.
---
 src/bin/psql/tab-complete.c | 103 ++++++++++++++++++++++++++------------------
 1 file changed, 60 insertions(+), 43 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index f54ad40..23c8e9b 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1069,10 +1069,27 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (previous_words_count == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 
+	/*
+	 * If this is in CREATE SCHEMA, seek to the last CREATE and regard it as
+	 * current command to complete.
+	 */
+	if (HeadMatches2("CREATE", "SCHEMA"))
+		SHIFT_TO_LAST1("CREATE|GRANT|REVOKE");
+
 /* CREATE */
 	/* complete with something you can create */
 	if (Matches1("CREATE"))
-		return completion_matches(text, create_command_generator);
+	{
+		/*
+		 * If this is the top level CREATE, complete with all CREATable
+		 * objects
+		 */
+		if (head_shift == 0)
+			return completion_matches(text, create_command_generator);
+
+		/* schema_element allows only some kinds of objects */
+		COMPLETE_WITH_LIST5("TABLE", "VIEW", "INDEX", "SEQUENCE", "TRIGGER");
+	}
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
@@ -1800,33 +1817,33 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
 
-	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
+	/* CREATE INDEX --- is allowed inside CREATE SCHEMA */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	if (TailMatches2("CREATE", "UNIQUE"))
+	if (Matches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
 	/*
 	 * If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY", and
 	 * existing indexes
 	 */
-	if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	if (Matches2("CREATE|UNIQUE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
 									  ADDLIST2("ON", "CONCURRENTLY"));
 	/* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
-	if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
-			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
+	if (Matches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
+		Matches2("INDEX|CONCURRENTLY", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm);
 
 	/*
 	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
 	 * indexes
 	 */
-	if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+	if (Matches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_indexes,
 									  ADDLIST1("ON"));
 	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
-			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
+	if (Matches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
+		Matches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/*
@@ -1898,25 +1915,25 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
-/* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
-			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
+/* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA */
+	if (Matches3("CREATE", "SEQUENCE", MatchAny) ||
+		Matches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
-		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
+	if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
+		Matches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
 	if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
-/* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE TABLE --- is allowed inside CREATE SCHEMA */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	if (Matches2("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	if (TailMatches2("CREATE", "UNLOGGED"))
+	if (Matches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
 /* CREATE TABLESPACE */
@@ -1932,35 +1949,35 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
-/* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE TRIGGER --- is allowed inside CREATE SCHEMA */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	if (Matches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	if (Matches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	if (Matches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
-	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
+	if (Matches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+		Matches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
 	/*
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
 	 * tables
 	 */
-	if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
+	if (Matches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
-	if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
+	if (Matches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("ON", MatchAny))
 		COMPLETE_WITH_LIST7("NOT DEFERRABLE", "DEFERRABLE", "INITIALLY",
 							"REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
 	if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches1("DEFERRABLE") || TailMatches2("INITIALLY", "IMMEDIATE|DEFERRED")))
+		(TailMatches1("DEFERRABLE") || TailMatches2("INITIALLY", "IMMEDIATE|DEFERRED")))
 		COMPLETE_WITH_LIST4("REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
 	if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("REFERENCING"))
 		COMPLETE_WITH_LIST2("OLD TABLE", "NEW TABLE");
@@ -2034,12 +2051,12 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
-/* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* CREATE VIEW --- is allowed inside CREATE SCHEMA */
 	/* Complete CREATE VIEW <name> with AS */
-	if (TailMatches3("CREATE", "VIEW", MatchAny))
+	if (Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
+	if (Matches4("CREATE", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
@@ -2238,9 +2255,9 @@ psql_completion_internal(const char *text, char **previous_words,
 	if (TailMatches2("FOREIGN", "SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
 
-/* GRANT && REVOKE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+/* GRANT && REVOKE --- is allowed inside CREATE SCHEMA */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	if (TailMatches1("GRANT|REVOKE"))
+	if (Matches1("GRANT|REVOKE"))
 		COMPLETE_WITH_QUERY_KW(Query_for_list_of_roles,
 			ADDLIST13("SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE",
 					  "REFERENCES", "TRIGGER", "CREATE", "CONNECT", "TEMPORARY",
@@ -2250,11 +2267,11 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	if (TailMatches2("GRANT|REVOKE", MatchAny))
+	if (Matches2("GRANT|REVOKE", MatchAny))
 	{
 		if (TailMatches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
-		if (TailMatches2("GRANT", MatchAny))
+		if (Matches2("GRANT", MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2271,7 +2288,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
 	 */
-	if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
+	if (Matches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY_KW(Query_for_list_of_tsvmf,
 			   ADDLIST15("ALL FUNCTIONS IN SCHEMA",
 						 "ALL SEQUENCES IN SCHEMA",
@@ -2289,11 +2306,11 @@ psql_completion_internal(const char *text, char **previous_words,
 						 "TABLESPACE",
 						 "TYPE"));
 
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
+	if (Matches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
 							"TABLES IN SCHEMA");
 
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+	if (Matches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/*
@@ -2302,7 +2319,7 @@ psql_completion_internal(const char *text, char **previous_words,
 	 *
 	 * Complete "GRANT/REVOKE * ON *" with "TO/FROM".
 	 */
-	if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
+	if (Matches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
 		if (TailMatches1("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
@@ -2343,27 +2360,27 @@ psql_completion_internal(const char *text, char **previous_words,
 		COMPLETE_WITH_CONST("FROM");
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
+	if (Matches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
 	{
-		if (TailMatches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (Matches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	if (Matches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 	{
-		if (TailMatches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (Matches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
+	if (Matches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
 	{
-		if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (Matches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
-- 
2.9.2

