v8-tab-complete-partitions

text/plain

Filename: v8-tab-complete-partitions
Type: text/plain
Part: 0
Message: Re: adding tab completions
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 7bb47ea..699a102 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -705,6 +705,7 @@ static const SchemaQuery Query_for_list_of_tmf = {
 	"pg_catalog.pg_class c",
 	/* selcondition */
 	"c.relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+	CppAsString2(RELKIND_PARTITIONED_TABLE) ", "
 	CppAsString2(RELKIND_MATVIEW) ", "
 	CppAsString2(RELKIND_FOREIGN_TABLE) ")",
 	/* viscondition */
@@ -1282,6 +1283,7 @@ static PGresult *exec_query(const char *query);
 static char **get_previous_words(int point, char **buffer, int *nwords);
 
 static char *get_guctype(const char *varname);
+static int prev_parens(int nwords, char **words);
 
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
@@ -2222,6 +2224,7 @@ psql_completion(const char *text, int start, int end)
 			"fillfactor",
 			"parallel_workers",
 			"log_autovacuum_min_duration",
+			"toast_tuple_target",
 			"toast.autovacuum_enabled",
 			"toast.autovacuum_freeze_max_age",
 			"toast.autovacuum_freeze_min_age",
@@ -2385,7 +2388,7 @@ psql_completion(const char *text, int start, int end)
 		{"ACCESS METHOD", "CAST", "COLLATION", "CONVERSION", "DATABASE",
 			"EVENT TRIGGER", "EXTENSION",
 			"FOREIGN DATA WRAPPER", "FOREIGN TABLE",
-			"SERVER", "INDEX", "LANGUAGE", "POLICY", "PUBLICATION", "RULE",
+			"SERVER", "INDEX", "LANGUAGE", "POLICY", "PUBLICATION",
 			"SCHEMA", "SEQUENCE", "STATISTICS", "SUBSCRIPTION",
 			"TABLE", "TYPE", "VIEW", "MATERIALIZED VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
 			"PROCEDURE", "ROUTINE",
@@ -2703,7 +2706,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 	/* Complete PARTITION BY with RANGE ( or LIST ( or ... */
 	else if (TailMatches2("PARTITION", "BY"))
-		COMPLETE_WITH_LIST2("RANGE (", "LIST (");
+		COMPLETE_WITH_LIST3("RANGE (", "LIST (", "HASH (");
 	/* If we have xxx PARTITION OF, provide a list of partitioned tables */
 	else if (TailMatches2("PARTITION", "OF"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
@@ -2996,14 +2999,21 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches1("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
 
-/* EXPLAIN */
-
-	/*
-	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
-	 */
+/* 
+ * EXPLAIN [ ( option [, ...] ) ] statement
+ * EXPLAIN [ ANALYZE ] [ VERBOSE ] statement
+ */
 	else if (Matches1("EXPLAIN"))
-		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
-							"ANALYZE", "VERBOSE");
+		COMPLETE_WITH_LIST8("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
+							"ANALYZE", "VERBOSE", "(");
+	else if (HeadMatches2("EXPLAIN", "("))
+		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
+			COMPLETE_WITH_LIST7("ANALYZE", "VERBOSE", "COSTS", "BUFFERS", "TIMING", "SUMMARY", "FORMAT");
+		else
+			COMPLETE_WITH_LIST4(",", ")", "ON", "OFF");
+	else if (HeadMatches1("EXPLAIN") && previous_words_count==2 && prev_wd[0]=='(' && ends_with(prev_wd, ')'))
+		/* If the parenthesis are balanced, the list is apparently parsed as a single word */
+		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 	else if (Matches2("EXPLAIN", "ANALYZE"))
 		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"VERBOSE");
@@ -3563,33 +3573,57 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /*
- * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
- * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
+ * VACUUM [ ( option [, ...] ) ] [ table_and_columns [, ...] ]
+ * VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ table_and_columns [, ...] ]
  */
 	else if (Matches1("VACUUM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tpm,
 								   " UNION SELECT 'FULL'"
 								   " UNION SELECT 'FREEZE'"
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches2("VACUUM", "FULL|FREEZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
+	else if (Matches2("VACUUM", "FULL"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tpm,
+								   " UNION SELECT 'FREEZE'"
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
-	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
+	else if (Matches2("VACUUM", "FULL|FREEZE") ||
+		Matches3("VACUUM", "FULL", "FREEZE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tpm,
+								   " UNION SELECT 'VERBOSE'"
 								   " UNION SELECT 'ANALYZE'");
-	else if (Matches2("VACUUM", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
+	else if (Matches2("VACUUM", "VERBOSE") ||
+			Matches3("VACUUM", "FULL|FREEZE", "VERBOSE") ||
+		Matches4("VACUUM", "FULL", "FREEZE", "VERBOSE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tpm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (Matches2("VACUUM", "ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'VERBOSE'");
+	else if (HeadMatches1("VACUUM") && TailMatches1("ANALYZE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tpm, NULL);
+	else if (HeadMatches2("VACUUM", "("))
+		if (ends_with(prev_wd, ',') || ends_with(prev_wd, '('))
+			COMPLETE_WITH_LIST5("FULL", "FREEZE", "ANALYZE", "VERBOSE", "DISABLE_PAGE_SKIPPING" );
+		else
+			COMPLETE_WITH_LIST2(",", ")");
+
+	else if (HeadMatches2("VACUUM", MatchAny) &&
+		/* Handle (ANALYZE): */
+		!(previous_words_count==2 && prev_wd[0]=='(' && ends_with(prev_wd, ')')) &&
+		/* Avoid completing to column names following end of list: VACUUM ANALYZE t(i), ... */
+		!(previous_words_count>1 && prev2_wd[0]=='(' && ends_with(prev2_wd, ')')) &&
+		-1!=prev_parens(previous_words_count, previous_words)) {
+		int attword = prev_parens(previous_words_count, previous_words);
+		if (!ends_with(prev_wd, '(') && !ends_with(prev_wd, ','))
+			COMPLETE_WITH_LIST3(",", "(", ")");
+		else
+			COMPLETE_WITH_ATTR(previous_words[attword], " UNION SELECT ')' UNION SELECT ','");
+
+	} else if (HeadMatches2("VACUUM", MatchAny) && !ends_with(prev_wd, ',') &&
+			!(previous_words_count==2 && prev_wd[0]=='(' && ends_with(prev_wd, ')')))
+			/* Comma to support vacuuming multiple tables */
+			/* Parens to support analyzing a partial column list */
+			COMPLETE_WITH_LIST2(",", "(");
 	else if (HeadMatches1("VACUUM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tpm, NULL);
 
 /* WITH [RECURSIVE] */
 
@@ -3600,11 +3634,42 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches1("WITH"))
 		COMPLETE_WITH_CONST("RECURSIVE");
 
-/* ANALYZE */
-	/* Complete with list of tables */
+/*
+ * ANALYZE [ ( option [, ...] ) ] [ table_and_columns [, ...] ]
+ * ANALYZE [ VERBOSE ] [ table_and_columns [, ...] ]
+ */
+	else if (Matches2("ANALYZE", "("))
+		COMPLETE_WITH_CONST("VERBOSE)");
 	else if (Matches1("ANALYZE"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf,
+				" UNION SELECT 'VERBOSE'"
+				" UNION SELECT '('"
+				);
+
+	else if (HeadMatches2("ANALYZE", MatchAny) &&
+		/* Handle (VERBOSE): */
+		!(previous_words_count==2 && prev_wd[0]=='(' && ends_with(prev_wd, ')')) &&
+		/* Avoid completing to column names following end of list: ANALYZE t(i), ... */
+		!(previous_words_count>1 && prev2_wd[0]=='(' && ends_with(prev2_wd, ')')) &&
+		-1!=prev_parens(previous_words_count, previous_words)) {
+		int attword = prev_parens(previous_words_count, previous_words);
+		if (!ends_with(prev_wd, '(') && !ends_with(prev_wd, ','))
+			COMPLETE_WITH_CONST(",");
+		else
+			COMPLETE_WITH_ATTR(previous_words[attword], " UNION SELECT ')' UNION SELECT ','");
+
+	} else if (HeadMatches2("ANALYZE", MatchAny) &&
+                        !ends_with(prev_wd, ',') &&
+			!(previous_words_count==2 && prev_wd[0]=='(' && ends_with(prev_wd, ')')) &&
+                        !TailMatches1("VERBOSE"))
+                        /* Support analyze of multiple tables */
+                        /* or analyze table(column1, column2) */
+                        if (ends_with(prev_wd, ')'))
+                                COMPLETE_WITH_CONST(",");
+                        else
+                                COMPLETE_WITH_LIST2(",", "(");
+	else if (HeadMatches1("ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
-
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
 	else if (TailMatches2(MatchAny, "WHERE"))
@@ -4654,6 +4719,30 @@ get_guctype(const char *varname)
 	return guctype;
 }
 
+/*
+ * Return the word before the beginning of a current parenthesized list,
+ * or -1 if none.
+ */
+int
+prev_parens(int nwords, char **words)
+{
+	int i;
+	for (i=0; i<nwords-1; ++i) {
+		/* "a b c ( x y" returns: c */
+		if (words[i][0]=='(')
+			return i+1;
+
+		/* "a b c ( x ) y" fails */
+		/* "a b c ) y" fails */
+		if (words[i][0]==')')
+			return -1;
+	}
+
+	/* "a b c" fails */
+	return -1;
+}
+
+
 #ifdef NOT_USED
 
 /*