Client interactive suggest (extract info from CREATE queries)

This will parse CREATE queries and add the following things to
completion list for clickhouse-client/clickhouse-local:
- table
- database
- columns
This commit is contained in:
Azat Khuzhin 2021-12-26 19:10:25 +03:00
parent 02146a11d8
commit 36c4fc054c
7 changed files with 156 additions and 1 deletions

View File

@ -65,6 +65,27 @@ std::optional<LineReader::Suggest::WordsRange> LineReader::Suggest::getCompletio
});
}
void LineReader::Suggest::addWords(const std::vector<std::string> & new_words)
{
for (const auto & new_word : new_words)
{
if (std::binary_search(words.begin(), words.end(), new_word))
continue;
words.push_back(new_word);
words_no_case.push_back(new_word);
}
std::sort(words.begin(), words.end());
std::sort(words_no_case.begin(), words_no_case.end(), [](const std::string & str1, const std::string & str2)
{
return std::lexicographical_compare(begin(str1), end(str1), begin(str2), end(str2), [](const char char1, const char char2)
{
return std::tolower(char1) < std::tolower(char2);
});
});
}
LineReader::LineReader(const String & history_file_path_, bool multiline_, Patterns extenders_, Patterns delimiters_)
: history_file_path(history_file_path_), multiline(multiline_), extenders(std::move(extenders_)), delimiters(std::move(delimiters_))
{

View File

@ -20,6 +20,7 @@ public:
/// Get iterators for the matched range of words if any.
std::optional<WordsRange> getCompletions(const String & prefix, size_t prefix_length) const;
void addWords(const std::vector<std::string> & new_words);
};
using Patterns = std::vector<const char *>;

View File

@ -48,6 +48,7 @@
#include <Parsers/ASTQueryWithOutput.h>
#include <Parsers/ASTLiteral.h>
#include <Parsers/ASTIdentifier.h>
#include <Parsers/ASTColumnDeclaration.h>
#include <Processors/Formats/Impl/NullFormat.h>
#include <Processors/Formats/IInputFormat.h>
@ -552,6 +553,25 @@ void ClientBase::initLogsOutputStream()
}
}
void ClientBase::updateSuggest(const ASTCreateQuery & ast_create)
{
std::vector<std::string> new_words;
if (ast_create.database)
new_words.push_back(ast_create.getDatabase());
new_words.push_back(ast_create.getTable());
if (ast_create.columns_list && ast_create.columns_list->columns)
{
for (const auto & elem : ast_create.columns_list->columns->children)
{
if (const auto * column = elem->as<ASTColumnDeclaration>())
new_words.push_back(column->name);
}
}
suggest->addWords(new_words);
}
void ClientBase::processTextAsSingleQuery(const String & full_query)
{
@ -565,6 +585,18 @@ void ClientBase::processTextAsSingleQuery(const String & full_query)
String query_to_execute;
/// Query will be parsed before checking the result because error does not
/// always means a problem, i.e. if table already exists, and it is no a
/// huge problem if suggestion will be added even on error, since this is
/// just suggestion.
if (auto * create = parsed_query->as<ASTCreateQuery>())
{
/// Do not update suggest, until suggestion will be ready
/// (this will avoid extra complexity)
if (suggest && suggest->ready)
updateSuggest(*create);
}
// An INSERT query may have the data that follow query text. Remove the
/// Send part of query without data, because data will be sent separately.
auto * insert = parsed_query->as<ASTInsertQuery>();
@ -1463,7 +1495,6 @@ void ClientBase::runInteractive()
/// Initialize DateLUT here to avoid counting time spent here as query execution time.
const auto local_tz = DateLUT::instance().getTimeZone();
std::optional<Suggest> suggest;
suggest.emplace();
if (load_suggestions)
{

View File

@ -136,6 +136,8 @@ private:
void readArguments(int argc, char ** argv, Arguments & common_arguments, std::vector<Arguments> & external_tables_arguments);
void parseAndCheckOptions(OptionsDescription & options_description, po::variables_map & options, Arguments & arguments);
void updateSuggest(const ASTCreateQuery & ast_create);
protected:
bool is_interactive = false; /// Use either interactive line editing interface or batch mode.
bool is_multiquery = false;
@ -144,6 +146,8 @@ protected:
bool echo_queries = false; /// Print queries before execution in batch mode.
bool ignore_error = false; /// In case of errors, don't print error message, continue to next query. Only applicable for non-interactive mode.
bool print_time_to_stderr = false; /// Output execution time to stderr in batch mode.
std::optional<Suggest> suggest;
bool load_suggestions = false;
std::vector<String> queries_files; /// If not empty, queries will be read from these files

View File

@ -38,6 +38,8 @@ private:
/// Words are fetched asynchronously.
std::thread loading_thread;
Words new_words;
};
}

View File

@ -0,0 +1,89 @@
#!/usr/bin/expect -f
log_user 0
set timeout 60
set uuid ""
match_max 100000
# A default timeout action is to do nothing, change it to fail
expect_after {
timeout {
send_user "FAILED (uuid=$uuid)\n"
exit 1
}
}
set basedir [file dirname $argv0]
spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_CLIENT_BINARY \$CLICKHOUSE_CLIENT_OPT"
expect ":) "
# Make a query
send -- "set max_distributed"
expect "set max_distributed"
# Wait for suggestions to load, they are loaded in background
set is_done 0
set timeout 1
while {$is_done == 0} {
send -- "\t"
expect {
"_" {
set is_done 1
}
default {
# Reset the expect_after
}
}
}
set timeout 60
send -- ""
expect ":) "
send_user "Completion loaded\n"
# Generate UIUD to avoid matching old database/tables/columns from previous test runs.
send -- "select 'begin-' || replace(toString(generateUUIDv4()), '-', '') || '-end' format TSV\r"
expect -re TSV.*TSV.*begin-(.*)-end.*
set uuid $expect_out(1,string)
expect ":) "
send_user "UUID generated\n"
# Create
send -- "create database new_${uuid}_database\r"
expect ":) "
send -- "create table new_${uuid}_table (new_${uuid}_column Int) engine=Null()\r"
expect ":) "
send_user "Structure created\n"
# Check completion
send -- "new_${uuid}_data"
expect "new_${uuid}_data"
send -- "\t"
expect "base"
send -- ""
expect ":) "
send_user "Database completion works\n"
send -- "new_${uuid}_ta"
expect "new_${uuid}_ta"
send -- "\t"
expect "ble"
send -- ""
expect ":) "
send_user "Table completion works\n"
send -- "new_${uuid}_col"
expect "new_${uuid}_col"
send -- "\t"
expect "umn"
send -- ""
expect ":) "
send_user "Column completion works\n"
# Cleanup
send -- "drop database new_${uuid}_database\r"
expect ":) "
send -- "drop table new_${uuid}_table\r"
expect ":) "
send_user "Cleanup\n"
send -- ""
expect eof

View File

@ -0,0 +1,7 @@
Completion loaded
UUID generated
Structure created
Database completion works
Table completion works
Column completion works
Cleanup