/*
* psql - the PostgreSQL interactive terminal
*
- * Copyright (c) 2000-2018, PostgreSQL Global Development Group
+ * Copyright (c) 2000-2019, PostgreSQL Global Development Group
*
* src/bin/psql/tab-complete.c
*/
.qualresult = "pg_catalog.quote_ident(t.typname)",
};
+static const SchemaQuery Query_for_list_of_composite_datatypes = {
+ .catname = "pg_catalog.pg_type t",
+ /* selcondition --- only get composite types */
+ .selcondition = "(SELECT c.relkind = " CppAsString2(RELKIND_COMPOSITE_TYPE)
+ " FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) "
+ "AND t.typname !~ '^_'",
+ .viscondition = "pg_catalog.pg_type_is_visible(t.oid)",
+ .namespace = "t.typnamespace",
+ .result = "pg_catalog.format_type(t.oid, NULL)",
+ .qualresult = "pg_catalog.quote_ident(t.typname)",
+};
+
static const SchemaQuery Query_for_list_of_domains = {
.catname = "pg_catalog.pg_type t",
.selcondition = "t.typtype = 'd'",
" OR '\"' || relname || '\"'='%s') "\
" AND pg_catalog.pg_table_is_visible(c.oid)"
+#define Query_for_list_of_attribute_numbers \
+"SELECT attnum "\
+" FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c "\
+" WHERE c.oid = a.attrelid "\
+" AND a.attnum > 0 "\
+" AND NOT a.attisdropped "\
+" AND substring(attnum::pg_catalog.text,1,%d)='%s' "\
+" AND (pg_catalog.quote_ident(relname)='%s' "\
+" OR '\"' || relname || '\"'='%s') "\
+" AND pg_catalog.pg_table_is_visible(c.oid)"
+
#define Query_for_list_of_attributes_with_schema \
"SELECT pg_catalog.quote_ident(attname) "\
" FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c, pg_catalog.pg_namespace n "\
" UNION ALL SELECT 'CURRENT_USER'"\
" UNION ALL SELECT 'SESSION_USER'"
-/* the silly-looking length condition is just to eat up the current word */
-#define Query_for_table_owning_index \
-"SELECT pg_catalog.quote_ident(c1.relname) "\
-" FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i"\
-" WHERE c1.oid=i.indrelid and i.indexrelid=c2.oid"\
-" and (%d = pg_catalog.length('%s'))"\
-" and pg_catalog.quote_ident(c2.relname)='%s'"\
-" and pg_catalog.pg_table_is_visible(c2.oid)"
-
/* the silly-looking length condition is just to eat up the current word */
#define Query_for_index_of_table \
"SELECT pg_catalog.quote_ident(c2.relname) "\
{NULL} /* end of list */
};
+/* Storage parameters for CREATE TABLE and ALTER TABLE */
+static const char *const table_storage_parameters[] = {
+ "autovacuum_analyze_scale_factor",
+ "autovacuum_analyze_threshold",
+ "autovacuum_enabled",
+ "autovacuum_freeze_max_age",
+ "autovacuum_freeze_min_age",
+ "autovacuum_freeze_table_age",
+ "autovacuum_multixact_freeze_max_age",
+ "autovacuum_multixact_freeze_min_age",
+ "autovacuum_multixact_freeze_table_age",
+ "autovacuum_vacuum_cost_delay",
+ "autovacuum_vacuum_cost_limit",
+ "autovacuum_vacuum_scale_factor",
+ "autovacuum_vacuum_threshold",
+ "fillfactor",
+ "log_autovacuum_min_duration",
+ "parallel_workers",
+ "toast.autovacuum_enabled",
+ "toast.autovacuum_freeze_max_age",
+ "toast.autovacuum_freeze_min_age",
+ "toast.autovacuum_freeze_table_age",
+ "toast.autovacuum_multixact_freeze_max_age",
+ "toast.autovacuum_multixact_freeze_min_age",
+ "toast.autovacuum_multixact_freeze_table_age",
+ "toast.autovacuum_vacuum_cost_delay",
+ "toast.autovacuum_vacuum_cost_limit",
+ "toast.autovacuum_vacuum_scale_factor",
+ "toast.autovacuum_vacuum_threshold",
+ "toast.log_autovacuum_min_duration",
+ "toast_tuple_target",
+ "user_catalog_table",
+ "vacuum_index_cleanup",
+ NULL
+};
+
/* Forward declaration of functions */
static char **psql_completion(const char *text, int start, int end);
COMPLETE_WITH("PARTITION");
else if (Matches("ALTER", "INDEX", MatchAny, "ATTACH", "PARTITION"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+ /* ALTER INDEX <name> ALTER */
+ else if (Matches("ALTER", "INDEX", MatchAny, "ALTER"))
+ COMPLETE_WITH("COLUMN");
+ /* ALTER INDEX <name> ALTER COLUMN */
+ else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN"))
+ {
+ completion_info_charp = prev3_wd;
+ COMPLETE_WITH_QUERY(Query_for_list_of_attribute_numbers);
+ }
/* ALTER INDEX <name> ALTER COLUMN <colnum> */
else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN", MatchAny))
COMPLETE_WITH("SET STATISTICS");
+ /* ALTER INDEX <name> ALTER COLUMN <colnum> SET */
+ else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN", MatchAny, "SET"))
+ COMPLETE_WITH("STATISTICS");
+ /* ALTER INDEX <name> ALTER COLUMN <colnum> SET STATISTICS */
+ else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STATISTICS"))
+ {
+ /* Enforce no completion here, as an integer has to be specified */
+ }
/* ALTER INDEX <name> SET */
else if (Matches("ALTER", "INDEX", MatchAny, "SET"))
COMPLETE_WITH("(", "TABLESPACE");
COMPLETE_WITH("(");
/* ALTER INDEX <foo> SET|RESET ( */
else if (Matches("ALTER", "INDEX", MatchAny, "RESET", "("))
- COMPLETE_WITH("fillfactor", "recheck_on_update",
+ COMPLETE_WITH("fillfactor",
"vacuum_cleanup_index_scale_factor", /* BTREE */
"fastupdate", "gin_pending_list_limit", /* GIN */
"buffering", /* GiST */
"pages_per_range", "autosummarize" /* BRIN */
);
else if (Matches("ALTER", "INDEX", MatchAny, "SET", "("))
- COMPLETE_WITH("fillfactor =", "recheck_on_update =",
+ COMPLETE_WITH("fillfactor =",
"vacuum_cleanup_index_scale_factor =", /* BTREE */
"fastupdate =", "gin_pending_list_limit =", /* GIN */
"buffering =", /* GiST */
else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
COMPLETE_WITH("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
+ /* ALTER TABLE ALTER [COLUMN] <foo> SET STATISTICS */
+ else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STATISTICS") ||
+ Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STATISTICS"))
+ {
+ /* Enforce no completion here, as an integer has to be specified */
+ }
/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
*/
else if (Matches("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
- /* If we have ALTER TABLE <sth> SET WITH provide OIDS */
- else if (Matches("ALTER", "TABLE", MatchAny, "SET", "WITH"))
- COMPLETE_WITH("OIDS");
/* If we have ALTER TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
else if (Matches("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
COMPLETE_WITH("CLUSTER", "OIDS");
COMPLETE_WITH("(");
/* ALTER TABLE <foo> SET|RESET ( */
else if (Matches("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
- {
- static const char *const list_TABLEOPTIONS[] =
- {
- "autovacuum_analyze_scale_factor",
- "autovacuum_analyze_threshold",
- "autovacuum_enabled",
- "autovacuum_freeze_max_age",
- "autovacuum_freeze_min_age",
- "autovacuum_freeze_table_age",
- "autovacuum_multixact_freeze_max_age",
- "autovacuum_multixact_freeze_min_age",
- "autovacuum_multixact_freeze_table_age",
- "autovacuum_vacuum_cost_delay",
- "autovacuum_vacuum_cost_limit",
- "autovacuum_vacuum_scale_factor",
- "autovacuum_vacuum_threshold",
- "fillfactor",
- "parallel_workers",
- "log_autovacuum_min_duration",
- "toast_tuple_target",
- "toast.autovacuum_enabled",
- "toast.autovacuum_freeze_max_age",
- "toast.autovacuum_freeze_min_age",
- "toast.autovacuum_freeze_table_age",
- "toast.autovacuum_multixact_freeze_max_age",
- "toast.autovacuum_multixact_freeze_min_age",
- "toast.autovacuum_multixact_freeze_table_age",
- "toast.autovacuum_vacuum_cost_delay",
- "toast.autovacuum_vacuum_cost_limit",
- "toast.autovacuum_vacuum_scale_factor",
- "toast.autovacuum_vacuum_threshold",
- "toast.log_autovacuum_min_duration",
- "user_catalog_table",
- NULL
- };
-
- COMPLETE_WITH_LIST(list_TABLEOPTIONS);
- }
+ COMPLETE_WITH_LIST(table_storage_parameters);
else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
{
completion_info_charp = prev5_wd;
/*
* ANALYZE [ ( option [, ...] ) ] [ table_and_columns [, ...] ]
* ANALYZE [ VERBOSE ] [ table_and_columns [, ...] ]
- *
- * Currently the only allowed option is VERBOSE, so we can be skimpier on
- * the option processing than VACUUM has to be.
*/
else if (Matches("ANALYZE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_analyzables,
" UNION SELECT 'VERBOSE'");
- else if (Matches("ANALYZE", "("))
- COMPLETE_WITH("VERBOSE)");
+ else if (HeadMatches("ANALYZE", "(*") &&
+ !HeadMatches("ANALYZE", "(*)"))
+ {
+ /*
+ * This fires if we're in an unfinished parenthesized option list.
+ * get_previous_words treats a completed parenthesized option list as
+ * one word, so the above test is correct.
+ */
+ if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
+ COMPLETE_WITH("VERBOSE", "SKIP_LOCKED");
+ }
else if (HeadMatches("ANALYZE") && TailMatches("("))
/* "ANALYZE (" should be caught above, so assume we want columns */
COMPLETE_WITH_ATTR(prev2_wd, "");
COMPLETE_WITH("WORK", "TRANSACTION", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
/* END, ABORT */
else if (Matches("END|ABORT"))
- COMPLETE_WITH("WORK", "TRANSACTION");
+ COMPLETE_WITH("AND", "WORK", "TRANSACTION");
/* COMMIT */
else if (Matches("COMMIT"))
- COMPLETE_WITH("WORK", "TRANSACTION", "PREPARED");
+ COMPLETE_WITH("AND", "WORK", "TRANSACTION", "PREPARED");
/* RELEASE SAVEPOINT */
else if (Matches("RELEASE"))
COMPLETE_WITH("SAVEPOINT");
/* ROLLBACK */
else if (Matches("ROLLBACK"))
- COMPLETE_WITH("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
+ COMPLETE_WITH("AND", "WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
+ else if (Matches("ABORT|END|COMMIT|ROLLBACK", "AND"))
+ COMPLETE_WITH("CHAIN");
/* CALL */
else if (Matches("CALL"))
COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures, NULL);
/* Handle COPY [BINARY] <sth> FROM|TO filename */
else if (Matches("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
Matches("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
- COMPLETE_WITH("BINARY", "OIDS", "DELIMITER", "NULL", "CSV",
+ COMPLETE_WITH("BINARY", "DELIMITER", "NULL", "CSV",
"ENCODING");
/* Handle COPY [BINARY] <sth> FROM|TO filename CSV */
else if (Matches("CREATE", "STATISTICS", MatchAny))
COMPLETE_WITH("(", "ON");
else if (Matches("CREATE", "STATISTICS", MatchAny, "("))
- COMPLETE_WITH("ndistinct", "dependencies");
- else if (HeadMatches("CREATE", "STATISTICS", MatchAny) &&
- previous_words[0][0] == '(' &&
- previous_words[0][strlen(previous_words[0]) - 1] == ')')
+ COMPLETE_WITH("ndistinct", "dependencies", "mcv");
+ else if (Matches("CREATE", "STATISTICS", MatchAny, "(*)"))
COMPLETE_WITH("ON");
else if (HeadMatches("CREATE", "STATISTICS", MatchAny) &&
TailMatches("FROM"))
/* Limited completion support for partition bound specification */
else if (TailMatches("PARTITION", "OF", MatchAny))
COMPLETE_WITH("FOR VALUES", "DEFAULT");
+ /* Complete CREATE TABLE <name> with '(', OF or PARTITION OF */
+ else if (TailMatches("CREATE", "TABLE", MatchAny) ||
+ TailMatches("CREATE", "TEMP|TEMPORARY|UNLOGGED", "TABLE", MatchAny))
+ COMPLETE_WITH("(", "OF", "PARTITION OF");
+ /* Complete CREATE TABLE <name> OF with list of composite types */
+ else if (TailMatches("CREATE", "TABLE", MatchAny, "OF") ||
+ TailMatches("CREATE", "TEMP|TEMPORARY|UNLOGGED", "TABLE", MatchAny, "OF"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_composite_datatypes, NULL);
+ /* Complete CREATE TABLE name (...) with supported options */
+ else if (TailMatches("CREATE", "TABLE", MatchAny, "(*)") ||
+ TailMatches("CREATE", "UNLOGGED", "TABLE", MatchAny, "(*)"))
+ COMPLETE_WITH("INHERITS (", "PARTITION BY", "TABLESPACE", "WITH (");
+ else if (TailMatches("CREATE", "TEMP|TEMPORARY", "TABLE", MatchAny, "(*)"))
+ COMPLETE_WITH("INHERITS (", "ON COMMIT", "PARTITION BY",
+ "TABLESPACE", "WITH (");
+ /* Complete CREATE TABLE (...) WITH with storage parameters */
+ else if (TailMatches("CREATE", "TABLE", MatchAny, "(*)", "WITH", "(") ||
+ TailMatches("CREATE", "TEMP|TEMPORARY|UNLOGGED", "TABLE", MatchAny, "(*)", "WITH", "("))
+ COMPLETE_WITH_LIST(table_storage_parameters);
+ /* Complete CREATE TABLE ON COMMIT with actions */
+ else if (TailMatches("CREATE", "TEMP|TEMPORARY", "TABLE", MatchAny, "(*)", "ON", "COMMIT"))
+ COMPLETE_WITH("DELETE ROWS", "DROP", "PRESERVE ROWS");
/* CREATE TABLESPACE */
else if (Matches("CREATE", "TABLESPACE", MatchAny))
else if (Matches("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
COMPLETE_WITH("ddl_command_start", "ddl_command_end", "sql_drop");
+ /*
+ * Complete CREATE EVENT TRIGGER <name> ON <event_type>. EXECUTE FUNCTION
+ * is the recommended grammar instead of EXECUTE PROCEDURE in version 11
+ * and upwards.
+ */
+ else if (Matches("CREATE", "EVENT", "TRIGGER", MatchAny, "ON", MatchAny))
+ {
+ if (pset.sversion >= 110000)
+ COMPLETE_WITH("WHEN TAG IN (", "EXECUTE FUNCTION");
+ else
+ COMPLETE_WITH("WHEN TAG IN (", "EXECUTE PROCEDURE");
+ }
+ else if (HeadMatches("CREATE", "EVENT", "TRIGGER") &&
+ TailMatches("WHEN|AND", MatchAny, "IN", "(*)"))
+ {
+ if (pset.sversion >= 110000)
+ COMPLETE_WITH("EXECUTE FUNCTION");
+ else
+ COMPLETE_WITH("EXECUTE PROCEDURE");
+ }
+ else if (HeadMatches("CREATE", "EVENT", "TRIGGER") &&
+ TailMatches("EXECUTE", "FUNCTION|PROCEDURE"))
+ COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+
/* DEALLOCATE */
else if (Matches("DEALLOCATE"))
COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
else if (Matches("REINDEX"))
COMPLETE_WITH("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
else if (Matches("REINDEX", "TABLE"))
- COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexables, NULL);
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexables,
+ " UNION SELECT 'CONCURRENTLY'");
else if (Matches("REINDEX", "INDEX"))
- COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+ " UNION SELECT 'CONCURRENTLY'");
else if (Matches("REINDEX", "SCHEMA"))
- COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas
+ " UNION SELECT 'CONCURRENTLY'");
else if (Matches("REINDEX", "SYSTEM|DATABASE"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_databases
+ " UNION SELECT 'CONCURRENTLY'");
+ else if (Matches("REINDEX", "TABLE", "CONCURRENTLY"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexables, NULL);
+ else if (Matches("REINDEX", "INDEX", "CONCURRENTLY"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+ else if (Matches("REINDEX", "SCHEMA", "CONCURRENTLY"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+ else if (Matches("REINDEX", "SYSTEM|DATABASE", "CONCURRENTLY"))
COMPLETE_WITH_QUERY(Query_for_list_of_databases);
/* SECURITY LABEL */
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
- "DISABLE_PAGE_SKIPPING");
+ "DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
+ "INDEX_CLEANUP");
+ else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP"))
+ COMPLETE_WITH("ON", "OFF");
}
else if (HeadMatches("VACUUM") && TailMatches("("))
/* "VACUUM (" should be caught above, so assume we want columns */
else if (TailMatchesCS("\\password"))
COMPLETE_WITH_QUERY(Query_for_list_of_roles);
else if (TailMatchesCS("\\pset"))
- COMPLETE_WITH_CS("border", "columns", "expanded",
+ COMPLETE_WITH_CS("border", "columns", "csv_fieldsep", "expanded",
"fieldsep", "fieldsep_zero", "footer", "format",
"linestyle", "null", "numericlocale",
"pager", "pager_min_lines",
else if (TailMatchesCS("\\pset", MatchAny))
{
if (TailMatchesCS("format"))
- COMPLETE_WITH_CS("unaligned", "aligned", "wrapped", "html",
- "asciidoc", "latex", "latex-longtable",
- "troff-ms");
+ COMPLETE_WITH_CS("aligned", "asciidoc", "csv", "html", "latex",
+ "latex-longtable", "troff-ms", "unaligned",
+ "wrapped");
else if (TailMatchesCS("linestyle"))
COMPLETE_WITH_CS("ascii", "old-ascii", "unicode");
else if (TailMatchesCS("pager"))
else if (TailMatchesCS("SHOW_CONTEXT"))
COMPLETE_WITH_CS("never", "errors", "always");
else if (TailMatchesCS("VERBOSITY"))
- COMPLETE_WITH_CS("default", "verbose", "terse");
+ COMPLETE_WITH_CS("default", "verbose", "terse", "sqlstate");
}
else if (TailMatchesCS("\\sf*"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL);
if (PQresultStatus(result) != PGRES_TUPLES_OK)
{
#ifdef NOT_USED
- psql_error("tab completion query failed: %s\nQuery was:\n%s\n",
+ pg_log_error("tab completion query failed: %s\nQuery was:\n%s",
PQerrorMessage(pset.db), query);
#endif
PQclear(result);