mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-10 09:32:06 +00:00
Merge main, mainImpl code, allow interactive mode for clickhouse-local
This commit is contained in:
parent
0b5dab1326
commit
026fa0ca64
@ -1,8 +1,6 @@
|
||||
set (CLICKHOUSE_CLIENT_SOURCES
|
||||
Client.cpp
|
||||
ConnectionParameters.cpp
|
||||
QueryFuzzer.cpp
|
||||
Suggest.cpp
|
||||
)
|
||||
|
||||
set (CLICKHOUSE_CLIENT_LINK
|
||||
|
@ -1,6 +1,4 @@
|
||||
#include "ConnectionParameters.h"
|
||||
#include "QueryFuzzer.h"
|
||||
#include "Suggest.h"
|
||||
#include "TestHint.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
@ -17,7 +15,6 @@
|
||||
#include <boost/program_options.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <Poco/String.h>
|
||||
//#include <Poco/Util/Application.h>
|
||||
#if !defined(ARCADIA_BUILD)
|
||||
# include <Common/config_version.h>
|
||||
#endif
|
||||
@ -80,7 +77,6 @@
|
||||
#include <common/argsToConfig.h>
|
||||
//#include <Common/TerminalSize.h>
|
||||
#include <Common/UTF8Helpers.h>
|
||||
#include <Common/ProgressIndication.h>
|
||||
#include <filesystem>
|
||||
#include <Common/filesystemHelpers.h>
|
||||
|
||||
@ -141,8 +137,6 @@ public:
|
||||
Client() = default;
|
||||
|
||||
private:
|
||||
/// If not empty, queries will be read from these files
|
||||
std::vector<std::string> queries_files;
|
||||
/// If not empty, run queries from these files before processing every file from 'queries_files'.
|
||||
std::vector<std::string> interleave_queries_files;
|
||||
|
||||
@ -161,8 +155,6 @@ private:
|
||||
size_t insert_format_max_block_size = 0; /// Max block size when reading INSERT data.
|
||||
size_t max_client_network_bandwidth = 0; /// The maximum speed of data exchange over the network for the client in bytes per second.
|
||||
|
||||
bool has_vertical_output_suffix = false; /// Is \G present at the end of the query string?
|
||||
|
||||
SharedContextHolder shared_context = Context::createShared();
|
||||
ContextMutablePtr context = Context::createGlobal(shared_context.get());
|
||||
|
||||
@ -182,15 +174,8 @@ private:
|
||||
String server_logs_file;
|
||||
BlockOutputStreamPtr logs_out_stream;
|
||||
|
||||
String home_path;
|
||||
|
||||
String current_profile;
|
||||
|
||||
String prompt_by_server_display_name;
|
||||
|
||||
/// Path to a file containing command history.
|
||||
String history_file;
|
||||
|
||||
/// How many rows have been read or written.
|
||||
size_t processed_rows = 0;
|
||||
|
||||
@ -209,15 +194,6 @@ private:
|
||||
|
||||
UInt64 server_revision = 0;
|
||||
String server_version;
|
||||
String server_display_name;
|
||||
|
||||
/// true by default - for interactive mode, might be changed when --progress option is checked for
|
||||
/// non-interactive mode.
|
||||
bool need_render_progress = true;
|
||||
|
||||
bool written_first_block = false;
|
||||
|
||||
ProgressIndication progress_indication;
|
||||
|
||||
/// External tables info.
|
||||
std::list<ExternalTable> external_tables;
|
||||
@ -230,12 +206,12 @@ private:
|
||||
QueryFuzzer fuzzer;
|
||||
int query_fuzzer_runs = 0;
|
||||
|
||||
std::optional<Suggest> suggest;
|
||||
|
||||
/// We will format query_id in interactive mode in various ways, the default is just to print Query id: ...
|
||||
std::vector<std::pair<String, String>> query_id_formats;
|
||||
QueryProcessingStage::Enum query_processing_stage;
|
||||
|
||||
bool supportPasswordOption() override { return true; }
|
||||
|
||||
void initialize(Poco::Util::Application & self) override
|
||||
{
|
||||
Poco::Util::Application::initialize(self);
|
||||
@ -273,14 +249,7 @@ private:
|
||||
query_id_formats.emplace_back("Query id:", " {query_id}\n");
|
||||
}
|
||||
|
||||
|
||||
int main(const std::vector<std::string> & /*args*/) override
|
||||
{
|
||||
try
|
||||
{
|
||||
return mainImpl();
|
||||
}
|
||||
catch (const Exception & e)
|
||||
void processMainImplException(const Exception & e) override
|
||||
{
|
||||
bool print_stack_trace = config().getBool("stacktrace", false);
|
||||
|
||||
@ -302,26 +271,10 @@ private:
|
||||
{
|
||||
std::cerr << "Stack trace:" << std::endl << e.getStackTraceString();
|
||||
}
|
||||
|
||||
/// If exception code isn't zero, we should return non-zero return code anyway.
|
||||
return e.code() ? e.code() : -1;
|
||||
}
|
||||
catch (...)
|
||||
|
||||
bool isInteractive() override
|
||||
{
|
||||
std::cerr << getCurrentExceptionMessage(false) << std::endl;
|
||||
return getCurrentExceptionCode();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int mainImpl()
|
||||
{
|
||||
UseSSL use_ssl;
|
||||
|
||||
registerFormats();
|
||||
registerFunctions();
|
||||
registerAggregateFunctions();
|
||||
|
||||
/// Batch mode is enabled if one of the following is true:
|
||||
/// - -e (--query) command line option is present.
|
||||
/// The value of the option is used as the text of query (or of multiple queries).
|
||||
@ -329,134 +282,24 @@ private:
|
||||
/// - stdin is not a terminal. In this case queries are read from it.
|
||||
/// - -qf (--queries-file) command line option is present.
|
||||
/// The value of the option is used as file with query (or of multiple queries) to execute.
|
||||
if (!stdin_is_a_tty || config().has("query") || !queries_files.empty())
|
||||
is_interactive = false;
|
||||
|
||||
if (config().has("query") && !queries_files.empty())
|
||||
{
|
||||
throw Exception("Specify either `query` or `queries-file` option", ErrorCodes::BAD_ARGUMENTS);
|
||||
return stdin_is_a_tty && !config().has("query") && queries_files.empty();
|
||||
}
|
||||
|
||||
std::cout << std::fixed << std::setprecision(3);
|
||||
std::cerr << std::fixed << std::setprecision(3);
|
||||
|
||||
if (is_interactive)
|
||||
void loadSuggestionDataIfPossible() override
|
||||
{
|
||||
clearTerminal();
|
||||
showClientVersion();
|
||||
}
|
||||
|
||||
is_default_format = !config().has("vertical") && !config().has("format");
|
||||
if (config().has("vertical"))
|
||||
format = config().getString("format", "Vertical");
|
||||
else
|
||||
format = config().getString("format", is_interactive ? "PrettyCompact" : "TabSeparated");
|
||||
|
||||
format_max_block_size = config().getInt("format_max_block_size", context->getSettingsRef().max_block_size);
|
||||
|
||||
insert_format = "Values";
|
||||
|
||||
/// Setting value from cmd arg overrides one from config
|
||||
if (context->getSettingsRef().max_insert_block_size.changed)
|
||||
insert_format_max_block_size = context->getSettingsRef().max_insert_block_size;
|
||||
else
|
||||
insert_format_max_block_size = config().getInt("insert_format_max_block_size", context->getSettingsRef().max_insert_block_size);
|
||||
|
||||
if (!is_interactive)
|
||||
{
|
||||
need_render_progress = config().getBool("progress", false);
|
||||
echo_queries = config().getBool("echo", false);
|
||||
ignore_error = config().getBool("ignore-error", false);
|
||||
}
|
||||
|
||||
ClientInfo & client_info = context->getClientInfo();
|
||||
client_info.setInitialQuery();
|
||||
client_info.quota_key = config().getString("quota_key", "");
|
||||
|
||||
connect();
|
||||
|
||||
/// Initialize DateLUT here to avoid counting time spent here as query execution time.
|
||||
const auto local_tz = DateLUT::instance().getTimeZone();
|
||||
|
||||
if (is_interactive)
|
||||
{
|
||||
if (config().has("query_id"))
|
||||
throw Exception("query_id could be specified only in non-interactive mode", ErrorCodes::BAD_ARGUMENTS);
|
||||
if (print_time_to_stderr)
|
||||
throw Exception("time option could be specified only in non-interactive mode", ErrorCodes::BAD_ARGUMENTS);
|
||||
|
||||
suggest.emplace();
|
||||
if (server_revision >= Suggest::MIN_SERVER_REVISION && !config().getBool("disable_suggestion", false))
|
||||
{
|
||||
/// Load suggestion data from the server.
|
||||
suggest->load(connection_parameters, config().getInt("suggestion_limit"));
|
||||
}
|
||||
|
||||
/// Load command history if present.
|
||||
if (config().has("history_file"))
|
||||
history_file = config().getString("history_file");
|
||||
else
|
||||
{
|
||||
auto * history_file_from_env = getenv("CLICKHOUSE_HISTORY_FILE");
|
||||
if (history_file_from_env)
|
||||
history_file = history_file_from_env;
|
||||
else if (!home_path.empty())
|
||||
history_file = home_path + "/.clickhouse-client-history";
|
||||
}
|
||||
|
||||
if (!history_file.empty() && !fs::exists(history_file))
|
||||
bool processQueryFromInteractive(const String & input) override
|
||||
{
|
||||
/// Avoid TOCTOU issue.
|
||||
try
|
||||
{
|
||||
FS::createFile(history_file);
|
||||
}
|
||||
catch (const ErrnoException & e)
|
||||
{
|
||||
if (e.getErrno() != EEXIST)
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
LineReader::Patterns query_extenders = {"\\"};
|
||||
LineReader::Patterns query_delimiters = {";", "\\G"};
|
||||
|
||||
#if USE_REPLXX
|
||||
replxx::Replxx::highlighter_callback_t highlight_callback{};
|
||||
if (config().getBool("highlight"))
|
||||
highlight_callback = highlight;
|
||||
|
||||
ReplxxLineReader lr(*suggest, history_file, config().has("multiline"), query_extenders, query_delimiters, highlight_callback);
|
||||
|
||||
#elif defined(USE_READLINE) && USE_READLINE
|
||||
ReadlineLineReader lr(*suggest, history_file, config().has("multiline"), query_extenders, query_delimiters);
|
||||
#else
|
||||
LineReader lr(history_file, config().has("multiline"), query_extenders, query_delimiters);
|
||||
#endif
|
||||
|
||||
/// Enable bracketed-paste-mode only when multiquery is enabled and multiline is
|
||||
/// disabled, so that we are able to paste and execute multiline queries in a whole
|
||||
/// instead of erroring out, while be less intrusive.
|
||||
if (config().has("multiquery") && !config().has("multiline"))
|
||||
lr.enableBracketedPaste();
|
||||
|
||||
do
|
||||
{
|
||||
auto input = lr.readLine(prompt(), ":-] ");
|
||||
if (input.empty())
|
||||
break;
|
||||
|
||||
has_vertical_output_suffix = false;
|
||||
if (input.ends_with("\\G"))
|
||||
{
|
||||
input.resize(input.size() - 2);
|
||||
has_vertical_output_suffix = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!processQueryText(input))
|
||||
break;
|
||||
return processQueryText(input);
|
||||
}
|
||||
catch (const Exception & e)
|
||||
{
|
||||
@ -482,15 +325,24 @@ private:
|
||||
/// So we reconnect and allow to enter the next query.
|
||||
connect();
|
||||
}
|
||||
} while (true);
|
||||
|
||||
if (isNewYearMode())
|
||||
std::cout << "Happy new year." << std::endl;
|
||||
else if (isChineseNewYearMode(local_tz))
|
||||
std::cout << "Happy Chinese new year. 春节快乐!" << std::endl;
|
||||
else
|
||||
std::cout << "Bye." << std::endl;
|
||||
return 0;
|
||||
/// Continue processing queries.
|
||||
return true;
|
||||
}
|
||||
|
||||
int childMainImpl() override
|
||||
{
|
||||
UseSSL use_ssl;
|
||||
|
||||
registerFormats();
|
||||
registerFunctions();
|
||||
registerAggregateFunctions();
|
||||
|
||||
connect();
|
||||
|
||||
if (is_interactive)
|
||||
{
|
||||
runInteractive();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -513,11 +365,10 @@ private:
|
||||
// case so that at least we don't lose an error.
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void connect()
|
||||
{
|
||||
@ -611,12 +462,10 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
Strings keys;
|
||||
|
||||
prompt_by_server_display_name = config().getRawString("prompt_by_server_display_name.default", "{display_name} :) ");
|
||||
|
||||
Strings keys;
|
||||
config().keys("prompt_by_server_display_name", keys);
|
||||
|
||||
for (const String & key : keys)
|
||||
{
|
||||
if (key != "default" && server_display_name.find(key) != std::string::npos)
|
||||
@ -647,13 +496,6 @@ private:
|
||||
boost::replace_all(prompt_by_server_display_name, "{" + key + "}", value);
|
||||
}
|
||||
|
||||
|
||||
inline String prompt() const
|
||||
{
|
||||
return boost::replace_all_copy(prompt_by_server_display_name, "{database}", config().getString("database", "default"));
|
||||
}
|
||||
|
||||
|
||||
void nonInteractive()
|
||||
{
|
||||
String text;
|
||||
@ -2192,11 +2034,6 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
static void showClientVersion()
|
||||
{
|
||||
std::cout << DBMS_NAME << " client version " << VERSION_STRING << VERSION_OFFICIAL << "." << std::endl;
|
||||
}
|
||||
|
||||
public:
|
||||
void readArguments(int argc, char ** argv, Arguments & common_arguments, std::vector<Arguments> & external_tables_arguments) override
|
||||
{
|
||||
@ -2508,6 +2345,26 @@ public:
|
||||
{
|
||||
context->getClientInfo().client_trace_context.tracestate = options["opentelemetry-tracestate"].as<std::string>();
|
||||
}
|
||||
|
||||
is_default_format = !config().has("vertical") && !config().has("format");
|
||||
if (config().has("vertical"))
|
||||
format = config().getString("format", "Vertical");
|
||||
else
|
||||
format = config().getString("format", is_interactive ? "PrettyCompact" : "TabSeparated");
|
||||
|
||||
format_max_block_size = config().getInt("format_max_block_size", context->getSettingsRef().max_block_size);
|
||||
|
||||
insert_format = "Values";
|
||||
|
||||
/// Setting value from cmd arg overrides one from config
|
||||
if (context->getSettingsRef().max_insert_block_size.changed)
|
||||
insert_format_max_block_size = context->getSettingsRef().max_insert_block_size;
|
||||
else
|
||||
insert_format_max_block_size = config().getInt("insert_format_max_block_size", context->getSettingsRef().max_insert_block_size);
|
||||
|
||||
ClientInfo & client_info = context->getClientInfo();
|
||||
client_info.setInitialQuery();
|
||||
client_info.quota_key = config().getString("quota_key", "");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <Interpreters/executeQuery.h>
|
||||
#include <Interpreters/loadMetadata.h>
|
||||
#include <Interpreters/DatabaseCatalog.h>
|
||||
#include <common/getFQDNOrHostName.h>
|
||||
#include <Common/Exception.h>
|
||||
#include <Common/Macros.h>
|
||||
#include <Common/Config/ConfigProcessor.h>
|
||||
@ -191,15 +192,27 @@ static void attachSystemTables(ContextPtr context)
|
||||
attachSystemTablesLocal(*system_database);
|
||||
}
|
||||
|
||||
|
||||
int LocalServer::main(const std::vector<std::string> & /*args*/)
|
||||
void LocalServer::processMainImplException(const Exception &)
|
||||
{
|
||||
try
|
||||
{
|
||||
cleanup();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
tryLogCurrentException(__PRETTY_FUNCTION__);
|
||||
}
|
||||
|
||||
std::cerr << getCurrentExceptionMessage(config().hasOption("stacktrace")) << '\n';
|
||||
}
|
||||
|
||||
int LocalServer::childMainImpl()
|
||||
{
|
||||
Poco::Logger * log = &logger();
|
||||
ThreadStatus thread_status;
|
||||
UseSSL use_ssl;
|
||||
|
||||
if (!config().has("query") && !config().has("table-structure") && !config().has("queries-file")) /// Nothing to process
|
||||
if (!is_interactive && !config().has("query") && !config().has("table-structure") && !config().has("queries-file")) /// Nothing to process
|
||||
{
|
||||
if (config().hasOption("verbose"))
|
||||
std::cerr << "There are no queries to process." << '\n';
|
||||
@ -208,9 +221,19 @@ try
|
||||
}
|
||||
|
||||
if (config().has("query") && config().has("queries-file"))
|
||||
{
|
||||
throw Exception("Specify either `query` or `queries-file` option", ErrorCodes::BAD_ARGUMENTS);
|
||||
}
|
||||
|
||||
echo_queries = config().hasOption("echo") || config().hasOption("verbose");
|
||||
|
||||
prompt_by_server_display_name = config().getRawString("prompt_by_server_display_name.default", "{display_name} :) ");
|
||||
server_display_name = config().getString("display_name", getFQDNOrHostName());
|
||||
|
||||
/// Prompt may contain the following substitutions in a form of {name}.
|
||||
std::map<String, String> prompt_substitutions{{"display_name", server_display_name}};
|
||||
|
||||
/// Quite suboptimal.
|
||||
for (const auto & [key, value] : prompt_substitutions)
|
||||
boost::replace_all(prompt_by_server_display_name, "{" + key + "}", value);
|
||||
|
||||
shared_context = Context::createShared();
|
||||
global_context = Context::createGlobal(shared_context.get());
|
||||
@ -301,7 +324,40 @@ try
|
||||
attachSystemTables(global_context);
|
||||
}
|
||||
|
||||
/// we can't mutate global global_context (can lead to races, as it was already passed to some background threads)
|
||||
/// so we can't reuse it safely as a query context and need a copy here
|
||||
query_context = Context::createCopy(global_context);
|
||||
|
||||
query_context->makeSessionContext();
|
||||
query_context->makeQueryContext();
|
||||
|
||||
query_context->setUser("default", "", Poco::Net::SocketAddress{});
|
||||
query_context->setCurrentQueryId("");
|
||||
applyCmdSettings(query_context);
|
||||
|
||||
/// Use the same query_id (and thread group) for all queries
|
||||
CurrentThread::QueryScope query_scope_holder(query_context);
|
||||
|
||||
if (need_render_progress)
|
||||
{
|
||||
query_context->setProgressCallback([&](const Progress & value)
|
||||
{
|
||||
/// Write progress only if progress was updated
|
||||
if (progress_indication.updateProgress(value))
|
||||
progress_indication.writeProgress();
|
||||
});
|
||||
|
||||
progress_indication.setFileProgressCallback(query_context);
|
||||
}
|
||||
|
||||
if (is_interactive)
|
||||
{
|
||||
runInteractive();
|
||||
}
|
||||
else
|
||||
{
|
||||
processQueries();
|
||||
}
|
||||
|
||||
global_context->shutdown();
|
||||
global_context.reset();
|
||||
@ -311,23 +367,6 @@ try
|
||||
|
||||
return Application::EXIT_OK;
|
||||
}
|
||||
catch (const Exception & e)
|
||||
{
|
||||
try
|
||||
{
|
||||
cleanup();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
tryLogCurrentException(__PRETTY_FUNCTION__);
|
||||
}
|
||||
|
||||
std::cerr << getCurrentExceptionMessage(config().hasOption("stacktrace")) << '\n';
|
||||
|
||||
/// If exception code isn't zero, we should return non-zero return code anyway.
|
||||
return e.code() ? e.code() : -1;
|
||||
}
|
||||
|
||||
|
||||
std::string LocalServer::getInitialCreateTableQuery()
|
||||
{
|
||||
@ -351,6 +390,37 @@ std::string LocalServer::getInitialCreateTableQuery()
|
||||
"; ";
|
||||
}
|
||||
|
||||
void LocalServer::processQuery(const String & query, std::exception_ptr exception)
|
||||
{
|
||||
written_first_block = false;
|
||||
progress_indication.resetProgress();
|
||||
|
||||
ReadBufferFromString read_buf(query);
|
||||
WriteBufferFromFileDescriptor write_buf(STDOUT_FILENO);
|
||||
|
||||
if (echo_queries)
|
||||
{
|
||||
writeString(query, write_buf);
|
||||
writeChar('\n', write_buf);
|
||||
write_buf.next();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
std::cerr << "executing query\n";
|
||||
executeQuery(read_buf, write_buf, /* allow_into_outfile = */ true, query_context, {});
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
if (!config().hasOption("ignore-error"))
|
||||
throw;
|
||||
|
||||
if (!exception)
|
||||
exception = std::current_exception();
|
||||
|
||||
std::cerr << getCurrentExceptionMessage(config().hasOption("stacktrace")) << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
void LocalServer::processQueries()
|
||||
{
|
||||
@ -375,69 +445,11 @@ void LocalServer::processQueries()
|
||||
if (!parse_res.second)
|
||||
throw Exception("Cannot parse and execute the following part of query: " + String(parse_res.first), ErrorCodes::SYNTAX_ERROR);
|
||||
|
||||
/// we can't mutate global global_context (can lead to races, as it was already passed to some background threads)
|
||||
/// so we can't reuse it safely as a query context and need a copy here
|
||||
auto context = Context::createCopy(global_context);
|
||||
|
||||
context->makeSessionContext();
|
||||
context->makeQueryContext();
|
||||
|
||||
context->setUser("default", "", Poco::Net::SocketAddress{});
|
||||
context->setCurrentQueryId("");
|
||||
applyCmdSettings(context);
|
||||
|
||||
/// Use the same query_id (and thread group) for all queries
|
||||
CurrentThread::QueryScope query_scope_holder(context);
|
||||
|
||||
///Set progress show
|
||||
need_render_progress = config().getBool("progress", false);
|
||||
|
||||
if (need_render_progress)
|
||||
{
|
||||
context->setProgressCallback([&](const Progress & value)
|
||||
{
|
||||
/// Write progress only if progress was updated
|
||||
if (progress_indication.updateProgress(value))
|
||||
progress_indication.writeProgress();
|
||||
});
|
||||
}
|
||||
|
||||
bool echo_queries = config().hasOption("echo") || config().hasOption("verbose");
|
||||
|
||||
if (need_render_progress)
|
||||
progress_indication.setFileProgressCallback(context);
|
||||
|
||||
std::exception_ptr exception;
|
||||
|
||||
for (const auto & query : queries)
|
||||
{
|
||||
written_first_block = false;
|
||||
progress_indication.resetProgress();
|
||||
|
||||
ReadBufferFromString read_buf(query);
|
||||
WriteBufferFromFileDescriptor write_buf(STDOUT_FILENO);
|
||||
|
||||
if (echo_queries)
|
||||
{
|
||||
writeString(query, write_buf);
|
||||
writeChar('\n', write_buf);
|
||||
write_buf.next();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
executeQuery(read_buf, write_buf, /* allow_into_outfile = */ true, context, {});
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
if (!config().hasOption("ignore-error"))
|
||||
throw;
|
||||
|
||||
if (!exception)
|
||||
exception = std::current_exception();
|
||||
|
||||
std::cerr << getCurrentExceptionMessage(config().hasOption("stacktrace")) << '\n';
|
||||
}
|
||||
processQuery(query, exception);
|
||||
}
|
||||
|
||||
if (exception)
|
||||
@ -464,7 +476,6 @@ static const char * minimal_default_user_xml =
|
||||
" </quotas>"
|
||||
"</yandex>";
|
||||
|
||||
|
||||
static ConfigurationPtr getConfigurationFromXMLString(const char * xml_data)
|
||||
{
|
||||
std::stringstream ss{std::string{xml_data}}; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
|
||||
@ -472,7 +483,6 @@ static ConfigurationPtr getConfigurationFromXMLString(const char * xml_data)
|
||||
return {new Poco::Util::XMLConfiguration{&input_source}};
|
||||
}
|
||||
|
||||
|
||||
void LocalServer::setupUsers()
|
||||
{
|
||||
ConfigurationPtr users_config;
|
||||
@ -533,6 +543,12 @@ static std::string getHelpFooter()
|
||||
" BY mem_total DESC FORMAT PrettyCompact\"";
|
||||
}
|
||||
|
||||
bool LocalServer::isInteractive()
|
||||
{
|
||||
/// Nothing to process
|
||||
return stdin_is_a_tty && !config().has("query") && !config().has("table-structure") && queries_files.empty();
|
||||
}
|
||||
|
||||
void LocalServer::printHelpMessage(const OptionsDescription & options_description)
|
||||
{
|
||||
std::cout << getHelpHeader() << "\n";
|
||||
@ -569,7 +585,6 @@ void LocalServer::addOptions(OptionsDescription & options_description)
|
||||
("version,V", "print version information and exit")
|
||||
("progress", "print progress of queries execution")
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
void LocalServer::readArguments(int argc, char ** argv, Arguments & arguments, std::vector<Arguments> &)
|
||||
@ -621,6 +636,9 @@ void LocalServer::processOptions(const OptionsDescription &, const CommandLineOp
|
||||
config().setBool("ignore-error", true);
|
||||
if (options.count("no-system-tables"))
|
||||
config().setBool("no-system-tables", true);
|
||||
|
||||
if (options.count("queries-file"))
|
||||
queries_files = options["queries-file"].as<std::vector<std::string>>();
|
||||
}
|
||||
|
||||
void LocalServer::applyCmdOptions(ContextMutablePtr context)
|
||||
|
@ -23,8 +23,6 @@ public:
|
||||
|
||||
void initialize(Poco::Util::Application & self) override;
|
||||
|
||||
int main(const std::vector<std::string> & args) override;
|
||||
|
||||
~LocalServer() override;
|
||||
|
||||
private:
|
||||
@ -35,14 +33,35 @@ private:
|
||||
std::string getInitialCreateTableQuery();
|
||||
|
||||
void tryInitPath();
|
||||
|
||||
void applyCmdOptions(ContextMutablePtr context);
|
||||
|
||||
void applyCmdSettings(ContextMutablePtr context);
|
||||
|
||||
void processQueries();
|
||||
|
||||
void setupUsers();
|
||||
|
||||
void cleanup();
|
||||
|
||||
|
||||
protected:
|
||||
void processMainImplException(const Exception & e) override;
|
||||
|
||||
int childMainImpl() override;
|
||||
|
||||
bool isInteractive() override;
|
||||
|
||||
bool processQueryFromInteractive(const String & input) override
|
||||
{
|
||||
std::exception_ptr e;
|
||||
processQuery(input, e);
|
||||
|
||||
/// For clickhouse local it is ok, to return true here - i.e. interactive
|
||||
/// mode will only be stopped by exit command.
|
||||
return true;
|
||||
}
|
||||
|
||||
void printHelpMessage(const OptionsDescription & options_description) override;
|
||||
|
||||
void readArguments(int argc, char ** argv, Arguments & common_arguments, std::vector<Arguments> &) override;
|
||||
@ -53,16 +72,17 @@ protected:
|
||||
const CommandLineOptions & options,
|
||||
const std::vector<Arguments> &) override;
|
||||
|
||||
bool supportPasswordOption() override { return false; }
|
||||
|
||||
SharedContextHolder shared_context;
|
||||
ContextMutablePtr global_context;
|
||||
|
||||
bool need_render_progress = false;
|
||||
|
||||
bool written_first_block = false;
|
||||
|
||||
ProgressIndication progress_indication;
|
||||
|
||||
std::optional<std::filesystem::path> temporary_directory_to_delete;
|
||||
|
||||
private:
|
||||
ContextMutablePtr query_context;
|
||||
|
||||
void processQuery(const String & query, std::exception_ptr exception);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,9 +13,10 @@
|
||||
#include <common/scope_guard.h>
|
||||
|
||||
#if !defined(ARCADIA_BUILD)
|
||||
#include <readpassphrase.h> // Y_IGNORE
|
||||
#include <readpassphrase/readpassphrase.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include <Client/IClient.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
#if !defined(ARCADIA_BUILD)
|
||||
# include <Common/config_version.h>
|
||||
@ -12,6 +13,12 @@
|
||||
#include <Common/TerminalSize.h>
|
||||
#include <common/argsToConfig.h>
|
||||
#include <Common/clearPasswordFromCommandLine.h>
|
||||
#include <Common/filesystemHelpers.h>
|
||||
#include <common/LineReader.h>
|
||||
#include <Common/Config/configReadClient.h>
|
||||
#include <filesystem>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
|
||||
namespace DB
|
||||
@ -22,6 +29,7 @@ namespace ErrorCodes
|
||||
extern const int UNRECOGNIZED_ARGUMENTS;
|
||||
}
|
||||
|
||||
|
||||
/// Should we celebrate a bit?
|
||||
bool IClient::isNewYearMode()
|
||||
{
|
||||
@ -35,6 +43,7 @@ bool IClient::isNewYearMode()
|
||||
return (now.month() == 12 && now.day() >= 20) || (now.month() == 1 && now.day() <= 5);
|
||||
}
|
||||
|
||||
|
||||
bool IClient::isChineseNewYearMode(const String & local_tz)
|
||||
{
|
||||
/// Days of Dec. 20 in Chinese calendar starting from year 2019 to year 2105
|
||||
@ -104,6 +113,7 @@ bool IClient::isChineseNewYearMode(const String & local_tz)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
#if USE_REPLXX
|
||||
void IClient::highlight(const String & query, std::vector<replxx::Replxx::Color> & colors)
|
||||
{
|
||||
@ -176,6 +186,7 @@ void IClient::highlight(const String & query, std::vector<replxx::Replxx::Color>
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void IClient::clearTerminal()
|
||||
{
|
||||
/// Clear from cursor until end of screen.
|
||||
@ -186,11 +197,159 @@ void IClient::clearTerminal()
|
||||
"\033[?25h";
|
||||
}
|
||||
|
||||
|
||||
static void showClientVersion()
|
||||
{
|
||||
std::cout << DBMS_NAME << " client version " << VERSION_STRING << VERSION_OFFICIAL << "." << std::endl;
|
||||
}
|
||||
|
||||
|
||||
void IClient::runInteractive()
|
||||
{
|
||||
if (config().has("query_id"))
|
||||
throw Exception("query_id could be specified only in non-interactive mode", ErrorCodes::BAD_ARGUMENTS);
|
||||
if (print_time_to_stderr)
|
||||
throw Exception("time option could be specified only in non-interactive mode", ErrorCodes::BAD_ARGUMENTS);
|
||||
|
||||
/// Initialize DateLUT here to avoid counting time spent here as query execution time.
|
||||
const auto local_tz = DateLUT::instance().getTimeZone();
|
||||
|
||||
suggest.emplace();
|
||||
loadSuggestionDataIfPossible();
|
||||
|
||||
if (home_path.empty())
|
||||
{
|
||||
const char * home_path_cstr = getenv("HOME");
|
||||
if (home_path_cstr)
|
||||
home_path = home_path_cstr;
|
||||
|
||||
configReadClient(config(), home_path);
|
||||
}
|
||||
|
||||
/// Load command history if present.
|
||||
if (config().has("history_file"))
|
||||
history_file = config().getString("history_file");
|
||||
else
|
||||
{
|
||||
auto * history_file_from_env = getenv("CLICKHOUSE_HISTORY_FILE");
|
||||
if (history_file_from_env)
|
||||
history_file = history_file_from_env;
|
||||
else if (!home_path.empty())
|
||||
history_file = home_path + "/.clickhouse-client-history";
|
||||
}
|
||||
|
||||
if (!history_file.empty() && !fs::exists(history_file))
|
||||
{
|
||||
/// Avoid TOCTOU issue.
|
||||
try
|
||||
{
|
||||
FS::createFile(history_file);
|
||||
}
|
||||
catch (const ErrnoException & e)
|
||||
{
|
||||
if (e.getErrno() != EEXIST)
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
LineReader::Patterns query_extenders = {"\\"};
|
||||
LineReader::Patterns query_delimiters = {";", "\\G"};
|
||||
|
||||
#if USE_REPLXX
|
||||
replxx::Replxx::highlighter_callback_t highlight_callback{};
|
||||
if (config().getBool("highlight", true))
|
||||
highlight_callback = highlight;
|
||||
|
||||
ReplxxLineReader lr(*suggest, history_file, config().has("multiline"), query_extenders, query_delimiters, highlight_callback);
|
||||
|
||||
#elif defined(USE_READLINE) && USE_READLINE
|
||||
ReadlineLineReader lr(*suggest, history_file, config().has("multiline"), query_extenders, query_delimiters);
|
||||
#else
|
||||
LineReader lr(history_file, config().has("multiline"), query_extenders, query_delimiters);
|
||||
#endif
|
||||
|
||||
/// Enable bracketed-paste-mode only when multiquery is enabled and multiline is
|
||||
/// disabled, so that we are able to paste and execute multiline queries in a whole
|
||||
/// instead of erroring out, while be less intrusive.
|
||||
if (config().has("multiquery") && !config().has("multiline"))
|
||||
lr.enableBracketedPaste();
|
||||
|
||||
do
|
||||
{
|
||||
auto input = lr.readLine(prompt(), ":-] ");
|
||||
if (input.empty())
|
||||
break;
|
||||
|
||||
has_vertical_output_suffix = false;
|
||||
if (input.ends_with("\\G"))
|
||||
{
|
||||
input.resize(input.size() - 2);
|
||||
has_vertical_output_suffix = true;
|
||||
}
|
||||
|
||||
if (!processQueryFromInteractive(input))
|
||||
break;
|
||||
}
|
||||
while (true);
|
||||
|
||||
if (isNewYearMode())
|
||||
std::cout << "Happy new year." << std::endl;
|
||||
else if (isChineseNewYearMode(local_tz))
|
||||
std::cout << "Happy Chinese new year. 春节快乐!" << std::endl;
|
||||
else
|
||||
std::cout << "Bye." << std::endl;
|
||||
}
|
||||
|
||||
|
||||
int IClient::mainImpl()
|
||||
{
|
||||
if (isInteractive())
|
||||
is_interactive = true;
|
||||
|
||||
if (config().has("query") && !queries_files.empty())
|
||||
throw Exception("Specify either `query` or `queries-file` option", ErrorCodes::BAD_ARGUMENTS);
|
||||
|
||||
std::cout << std::fixed << std::setprecision(3);
|
||||
std::cerr << std::fixed << std::setprecision(3);
|
||||
|
||||
if (is_interactive)
|
||||
{
|
||||
|
||||
clearTerminal();
|
||||
showClientVersion();
|
||||
}
|
||||
else
|
||||
{
|
||||
need_render_progress = config().getBool("progress", false);
|
||||
echo_queries = config().getBool("echo", false);
|
||||
ignore_error = config().getBool("ignore-error", false);
|
||||
}
|
||||
|
||||
return childMainImpl();
|
||||
}
|
||||
|
||||
|
||||
int IClient::main(const std::vector<std::string> & /*args*/)
|
||||
{
|
||||
try
|
||||
{
|
||||
return mainImpl();
|
||||
}
|
||||
catch (const Exception & e)
|
||||
{
|
||||
processMainImplException(e);
|
||||
|
||||
/// If exception code isn't zero, we should return non-zero return code anyway.
|
||||
return e.code() ? e.code() : -1;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::cerr << getCurrentExceptionMessage(false) << std::endl;
|
||||
return getCurrentExceptionCode();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void IClient::init(int argc, char ** argv)
|
||||
{
|
||||
namespace po = boost::program_options;
|
||||
@ -250,6 +409,7 @@ void IClient::init(int argc, char ** argv)
|
||||
processOptions(options_description, options, external_tables_arguments);
|
||||
|
||||
argsToConfig(common_arguments, config(), 100);
|
||||
if (supportPasswordOption())
|
||||
clearPasswordFromCommandLine(argc, argv);
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,8 @@
|
||||
#include <Core/Names.h>
|
||||
#include <Core/Types.h>
|
||||
#include <Interpreters/Context.h>
|
||||
#include <Common/ProgressIndication.h>
|
||||
#include <Client/Suggest.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
@ -23,16 +25,26 @@ class IClient : public Poco::Util::Application
|
||||
public:
|
||||
using Arguments = std::vector<std::string>;
|
||||
|
||||
int main(const std::vector<std::string> & /*args*/) override;
|
||||
|
||||
int mainImpl();
|
||||
|
||||
void init(int argc, char ** argv);
|
||||
|
||||
protected:
|
||||
NameSet exit_strings{"exit", "quit", "logout", "учше", "йгше", "дщпщге", "exit;", "quit;", "logout;", "учшеж",
|
||||
"йгшеж", "дщпщгеж", "q", "й", "\\q", "\\Q", "\\й", "\\Й", ":q", "Жй"};
|
||||
|
||||
bool is_interactive = true; /// Use either interactive line editing interface or batch mode.
|
||||
bool is_interactive = false; /// Use either interactive line editing interface or batch mode.
|
||||
|
||||
bool need_render_progress = true;
|
||||
bool written_first_block = false;
|
||||
ProgressIndication progress_indication;
|
||||
|
||||
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.
|
||||
|
||||
bool stdin_is_a_tty = false; /// stdin is a terminal.
|
||||
bool stdout_is_a_tty = false; /// stdout is a terminal.
|
||||
|
||||
@ -41,7 +53,23 @@ protected:
|
||||
/// Settings specified via command line args
|
||||
Settings cmd_settings;
|
||||
|
||||
/// If not empty, queries will be read from these files
|
||||
std::vector<String> queries_files;
|
||||
|
||||
std::optional<Suggest> suggest;
|
||||
|
||||
/// Path to a file containing command history.
|
||||
String history_file;
|
||||
|
||||
String home_path;
|
||||
|
||||
bool has_vertical_output_suffix = false; /// Is \G present at the end of the query string?
|
||||
|
||||
String prompt_by_server_display_name;
|
||||
String server_display_name;
|
||||
|
||||
static bool isNewYearMode();
|
||||
|
||||
static bool isChineseNewYearMode(const String & local_tz);
|
||||
|
||||
#if USE_REPLXX
|
||||
@ -50,6 +78,18 @@ protected:
|
||||
|
||||
static void clearTerminal();
|
||||
|
||||
void runInteractive();
|
||||
|
||||
virtual int childMainImpl() = 0;
|
||||
|
||||
virtual void processMainImplException(const Exception & e) = 0;
|
||||
|
||||
virtual bool isInteractive() = 0;
|
||||
|
||||
virtual void loadSuggestionDataIfPossible() {}
|
||||
|
||||
virtual bool processQueryFromInteractive(const String & input) = 0;
|
||||
|
||||
virtual void readArguments(int argc, char ** argv,
|
||||
Arguments & common_arguments, std::vector<Arguments> &) = 0;
|
||||
|
||||
@ -69,6 +109,15 @@ protected:
|
||||
virtual void processOptions(const OptionsDescription & options_description,
|
||||
const CommandLineOptions & options,
|
||||
const std::vector<Arguments> & external_tables_arguments) = 0;
|
||||
|
||||
virtual bool supportPasswordOption() = 0;
|
||||
|
||||
private:
|
||||
inline String prompt() const
|
||||
{
|
||||
return boost::replace_all_copy(prompt_by_server_display_name, "{database}", config().getString("database", "default"));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user