From 59b0ce21e4bf81f6bd09d1bf3f4b268dbc9f0318 Mon Sep 17 00:00:00 2001 From: kssenii Date: Fri, 23 Jul 2021 00:27:26 +0300 Subject: [PATCH] Tiny refactoring --- programs/client/Client.cpp | 58 +++---- programs/client/Client.h | 6 +- programs/local/LocalServer.cpp | 15 +- programs/local/LocalServer.h | 5 +- src/Client/{IClient.cpp => ClientBase.cpp} | 192 ++------------------- src/Client/{IClient.h => ClientBase.h} | 183 +++++++++----------- src/Client/ClientBaseHelpers.h | 172 ++++++++++++++++++ 7 files changed, 320 insertions(+), 311 deletions(-) rename src/Client/{IClient.cpp => ClientBase.cpp} (76%) rename src/Client/{IClient.h => ClientBase.h} (89%) create mode 100644 src/Client/ClientBaseHelpers.h diff --git a/programs/client/Client.cpp b/programs/client/Client.cpp index b68ed9a134c..3c48e351291 100644 --- a/programs/client/Client.cpp +++ b/programs/client/Client.cpp @@ -281,6 +281,26 @@ int Client::childMainImpl() if (is_interactive) { + /// Load Warnings at the beginning of connection + if (!config().has("no-warnings")) + { + try + { + std::vector messages = loadWarningMessages(); + if (!messages.empty()) + { + std::cout << "Warnings:" << std::endl; + for (const auto & message : messages) + std::cout << " * " << message << std::endl; + std::cout << std::endl; + } + } + catch (...) + { + /// Ignore exception + } + } + runInteractive(); } else @@ -1766,9 +1786,6 @@ void Client::processOptions(const OptionsDescription & options_description, if (options.count("config-file") && options.count("config")) throw Exception("Two or more configuration files referenced in arguments", ErrorCodes::BAD_ARGUMENTS); - if (config().has("multiquery")) - is_multiquery = true; - query_processing_stage = QueryProcessingStage::fromString(options["stage"].as()); /// Save received data into the internal config. @@ -1851,18 +1868,21 @@ void Client::processOptions(const OptionsDescription & options_description, if (options.count("opentelemetry-traceparent")) { - std::string traceparent = options["opentelemetry-traceparent"].as(); - std::string error; + String traceparent = options["opentelemetry-traceparent"].as(); + String error; if (!global_context->getClientInfo().client_trace_context.parseTraceparentHeader(traceparent, error)) - { throw Exception(ErrorCodes::BAD_ARGUMENTS, "Cannot parse OpenTelemetry traceparent '{}': {}", traceparent, error); - } } if (options.count("opentelemetry-tracestate")) - { global_context->getClientInfo().client_trace_context.tracestate = options["opentelemetry-tracestate"].as(); - } +} + + +void Client::processConfig() +{ + if (config().has("multiquery")) + is_multiquery = true; is_default_format = !config().has("vertical") && !config().has("format"); if (config().has("vertical")) @@ -1883,26 +1903,6 @@ void Client::processOptions(const OptionsDescription & options_description, ClientInfo & client_info = global_context->getClientInfo(); client_info.setInitialQuery(); client_info.quota_key = config().getString("quota_key", ""); - - /// Load Warnings at the beginning of connection - if (!config().has("no-warnings")) - { - try - { - std::vector messages = loadWarningMessages(); - if (!messages.empty()) - { - std::cout << "Warnings:" << std::endl; - for (const auto & message : messages) - std::cout << " * " << message << std::endl; - std::cout << std::endl; - } - } - catch (...) - { - /// Ignore exception - } - } } } diff --git a/programs/client/Client.h b/programs/client/Client.h index 661d0d36e08..c3ee3835502 100644 --- a/programs/client/Client.h +++ b/programs/client/Client.h @@ -1,13 +1,13 @@ #pragma once -#include +#include #include namespace DB { -class Client : public IClient +class Client : public ClientBase { public: Client() = default; @@ -57,6 +57,8 @@ protected: const CommandLineOptions & options, const std::vector & external_tables_arguments) override; + void processConfig() override; + private: std::unique_ptr connection; /// Connection to DB. diff --git a/programs/local/LocalServer.cpp b/programs/local/LocalServer.cpp index 6569cc19744..a6d2409fb14 100644 --- a/programs/local/LocalServer.cpp +++ b/programs/local/LocalServer.cpp @@ -257,7 +257,7 @@ void LocalServer::executeParsedQueryImpl() try { - executeQuery(read_buf, write_buf, /* allow_into_outfile = */ true, query_context, {}, finalize_progress); + executeQuery(read_buf, write_buf, /* allow_into_outfile = */ true, query_context, {}, {}, finalize_progress); } catch (...) { @@ -372,11 +372,6 @@ int LocalServer::childMainImpl() 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 prompt_substitutions{{"display_name", server_display_name}}; @@ -667,6 +662,14 @@ void LocalServer::processOptions(const OptionsDescription &, const CommandLineOp queries_files = options["queries-file"].as>(); } + +void LocalServer::processConfig() +{ + 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()); +} + } diff --git a/programs/local/LocalServer.h b/programs/local/LocalServer.h index 37a1ded9bee..72cff7ca104 100644 --- a/programs/local/LocalServer.h +++ b/programs/local/LocalServer.h @@ -8,7 +8,7 @@ #include #include #include -#include +#include namespace DB { @@ -16,7 +16,7 @@ namespace DB /// Lightweight Application for clickhouse-local /// No networking, no extra configs and working directories, no pid and status files, no dictionaries, no logging. /// Quiet mode by default -class LocalServer : public IClient, public Loggers +class LocalServer : public ClientBase, public Loggers { public: LocalServer(); @@ -73,6 +73,7 @@ protected: void processOptions(const OptionsDescription & options_description, const CommandLineOptions & options, const std::vector &) override; + void processConfig() override; bool supportPasswordOption() const override { return false; } diff --git a/src/Client/IClient.cpp b/src/Client/ClientBase.cpp similarity index 76% rename from src/Client/IClient.cpp rename to src/Client/ClientBase.cpp index 9211b48e807..2217a9e350e 100644 --- a/src/Client/IClient.cpp +++ b/src/Client/ClientBase.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -21,6 +21,8 @@ #include #include +#include + #include #include #include @@ -50,165 +52,7 @@ namespace ErrorCodes } -/// Should we celebrate a bit? -bool IClient::isNewYearMode() -{ - time_t current_time = time(nullptr); - - /// It's bad to be intrusive. - if (current_time % 3 != 0) - return false; - - LocalDate now(current_time); - 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 - static constexpr UInt16 chineseNewYearIndicators[] - = {18275, 18659, 19014, 19368, 19752, 20107, 20491, 20845, 21199, 21583, 21937, 22292, 22676, 23030, 23414, 23768, 24122, 24506, - 24860, 25215, 25599, 25954, 26308, 26692, 27046, 27430, 27784, 28138, 28522, 28877, 29232, 29616, 29970, 30354, 30708, 31062, - 31446, 31800, 32155, 32539, 32894, 33248, 33632, 33986, 34369, 34724, 35078, 35462, 35817, 36171, 36555, 36909, 37293, 37647, - 38002, 38386, 38740, 39095, 39479, 39833, 40187, 40571, 40925, 41309, 41664, 42018, 42402, 42757, 43111, 43495, 43849, 44233, - 44587, 44942, 45326, 45680, 46035, 46418, 46772, 47126, 47510, 47865, 48249, 48604, 48958, 49342}; - - /// All time zone names are acquired from https://www.iana.org/time-zones - static constexpr const char * chineseNewYearTimeZoneIndicators[] = { - /// Time zones celebrating Chinese new year. - "Asia/Shanghai", - "Asia/Chongqing", - "Asia/Harbin", - "Asia/Urumqi", - "Asia/Hong_Kong", - "Asia/Chungking", - "Asia/Macao", - "Asia/Macau", - "Asia/Taipei", - "Asia/Singapore", - - /// Time zones celebrating Chinese new year but with different festival names. Let's not print the message for now. - // "Asia/Brunei", - // "Asia/Ho_Chi_Minh", - // "Asia/Hovd", - // "Asia/Jakarta", - // "Asia/Jayapura", - // "Asia/Kashgar", - // "Asia/Kuala_Lumpur", - // "Asia/Kuching", - // "Asia/Makassar", - // "Asia/Pontianak", - // "Asia/Pyongyang", - // "Asia/Saigon", - // "Asia/Seoul", - // "Asia/Ujung_Pandang", - // "Asia/Ulaanbaatar", - // "Asia/Ulan_Bator", - }; - static constexpr size_t M = sizeof(chineseNewYearTimeZoneIndicators) / sizeof(chineseNewYearTimeZoneIndicators[0]); - - time_t current_time = time(nullptr); - - if (chineseNewYearTimeZoneIndicators + M - == std::find_if(chineseNewYearTimeZoneIndicators, chineseNewYearTimeZoneIndicators + M, [&local_tz](const char * tz) - { - return tz == local_tz; - })) - return false; - - /// It's bad to be intrusive. - if (current_time % 3 != 0) - return false; - - auto days = DateLUT::instance().toDayNum(current_time).toUnderType(); - for (auto d : chineseNewYearIndicators) - { - /// Let's celebrate until Lantern Festival - if (d <= days && d + 25 >= days) - return true; - else if (d > days) - return false; - } - return false; -} - - -#if USE_REPLXX -void IClient::highlight(const String & query, std::vector & colors) -{ - using namespace replxx; - - static const std::unordered_map token_to_color - = {{TokenType::Whitespace, Replxx::Color::DEFAULT}, - {TokenType::Comment, Replxx::Color::GRAY}, - {TokenType::BareWord, Replxx::Color::DEFAULT}, - {TokenType::Number, Replxx::Color::GREEN}, - {TokenType::StringLiteral, Replxx::Color::CYAN}, - {TokenType::QuotedIdentifier, Replxx::Color::MAGENTA}, - {TokenType::OpeningRoundBracket, Replxx::Color::BROWN}, - {TokenType::ClosingRoundBracket, Replxx::Color::BROWN}, - {TokenType::OpeningSquareBracket, Replxx::Color::BROWN}, - {TokenType::ClosingSquareBracket, Replxx::Color::BROWN}, - {TokenType::DoubleColon, Replxx::Color::BROWN}, - {TokenType::OpeningCurlyBrace, Replxx::Color::INTENSE}, - {TokenType::ClosingCurlyBrace, Replxx::Color::INTENSE}, - - {TokenType::Comma, Replxx::Color::INTENSE}, - {TokenType::Semicolon, Replxx::Color::INTENSE}, - {TokenType::Dot, Replxx::Color::INTENSE}, - {TokenType::Asterisk, Replxx::Color::INTENSE}, - {TokenType::Plus, Replxx::Color::INTENSE}, - {TokenType::Minus, Replxx::Color::INTENSE}, - {TokenType::Slash, Replxx::Color::INTENSE}, - {TokenType::Percent, Replxx::Color::INTENSE}, - {TokenType::Arrow, Replxx::Color::INTENSE}, - {TokenType::QuestionMark, Replxx::Color::INTENSE}, - {TokenType::Colon, Replxx::Color::INTENSE}, - {TokenType::Equals, Replxx::Color::INTENSE}, - {TokenType::NotEquals, Replxx::Color::INTENSE}, - {TokenType::Less, Replxx::Color::INTENSE}, - {TokenType::Greater, Replxx::Color::INTENSE}, - {TokenType::LessOrEquals, Replxx::Color::INTENSE}, - {TokenType::GreaterOrEquals, Replxx::Color::INTENSE}, - {TokenType::Concatenation, Replxx::Color::INTENSE}, - {TokenType::At, Replxx::Color::INTENSE}, - {TokenType::DoubleAt, Replxx::Color::MAGENTA}, - - {TokenType::EndOfStream, Replxx::Color::DEFAULT}, - - {TokenType::Error, Replxx::Color::RED}, - {TokenType::ErrorMultilineCommentIsNotClosed, Replxx::Color::RED}, - {TokenType::ErrorSingleQuoteIsNotClosed, Replxx::Color::RED}, - {TokenType::ErrorDoubleQuoteIsNotClosed, Replxx::Color::RED}, - {TokenType::ErrorSinglePipeMark, Replxx::Color::RED}, - {TokenType::ErrorWrongNumber, Replxx::Color::RED}, - { TokenType::ErrorMaxQuerySizeExceeded, - Replxx::Color::RED }}; - - const Replxx::Color unknown_token_color = Replxx::Color::RED; - - Lexer lexer(query.data(), query.data() + query.size()); - size_t pos = 0; - - for (Token token = lexer.nextToken(); !token.isEnd(); token = lexer.nextToken()) - { - size_t utf8_len = UTF8::countCodePoints(reinterpret_cast(token.begin), token.size()); - for (size_t code_point_index = 0; code_point_index < utf8_len; ++code_point_index) - { - if (token_to_color.find(token.type) != token_to_color.end()) - colors[pos + code_point_index] = token_to_color.at(token.type); - else - colors[pos + code_point_index] = unknown_token_color; - } - - pos += utf8_len; - } -} -#endif - - -ASTPtr IClient::parseQuery(const char *& pos, const char * end, bool allow_multi_statements) const +ASTPtr ClientBase::parseQuery(const char *& pos, const char * end, bool allow_multi_statements) const { ParserQuery parser(end); ASTPtr res; @@ -295,7 +139,7 @@ static void adjustQueryEnd(const char *& this_query_end, const char * all_querie /// Flush all buffers. -void IClient::resetOutput() +void ClientBase::resetOutput() { block_out_stream.reset(); logs_out_stream.reset(); @@ -323,7 +167,7 @@ void IClient::resetOutput() } -void IClient::outputQueryInfo(bool echo_query_) +void ClientBase::outputQueryInfo(bool echo_query_) { if (echo_query_) { @@ -347,7 +191,7 @@ void IClient::outputQueryInfo(bool echo_query_) } -void IClient::prepareAndExecuteQuery(const String & query) +void ClientBase::prepareAndExecuteQuery(const String & query) { /* Parameters are in global variables: * 'parsed_query' -- the query AST, @@ -363,7 +207,7 @@ void IClient::prepareAndExecuteQuery(const String & query) } -void IClient::executeParsedQuery(std::optional echo_query_, bool report_error) +void ClientBase::executeParsedQuery(std::optional echo_query_, bool report_error) { have_error = false; processed_rows = 0; @@ -392,7 +236,7 @@ void IClient::executeParsedQuery(std::optional echo_query_, bool report_er } -bool IClient::processMultiQuery(const String & all_queries_text) +bool ClientBase::processMultiQuery(const String & all_queries_text) { // It makes sense not to base any control flow on this, so that it is // the same in tests and in normal usage. The only difference is that in @@ -612,7 +456,7 @@ bool IClient::processMultiQuery(const String & all_queries_text) } -bool IClient::processQueryText(const String & text) +bool ClientBase::processQueryText(const String & text) { if (exit_strings.end() != exit_strings.find(trim(text, [](char c) { return isWhitespaceASCII(c) || c == ';'; }))) return false; @@ -635,7 +479,7 @@ bool IClient::processQueryText(const String & text) } -void IClient::runInteractive() +void ClientBase::runInteractive() { if (config().has("query_id")) throw Exception("query_id could be specified only in non-interactive mode", ErrorCodes::BAD_ARGUMENTS); @@ -742,7 +586,7 @@ void IClient::runInteractive() } -void IClient::runNonInteractive() +void ClientBase::runNonInteractive() { String text; @@ -781,7 +625,7 @@ void IClient::runNonInteractive() } -void IClient::clearTerminal() +void ClientBase::clearTerminal() { /// Clear from cursor until end of screen. /// It is needed if garbage is left in terminal. @@ -798,7 +642,7 @@ static void showClientVersion() } -int IClient::mainImpl() +int ClientBase::mainImpl() { if (isInteractive()) is_interactive = true; @@ -806,6 +650,8 @@ int IClient::mainImpl() if (config().has("query") && !queries_files.empty()) throw Exception("Specify either `query` or `queries-file` option", ErrorCodes::BAD_ARGUMENTS); + processConfig(); + std::cout << std::fixed << std::setprecision(3); std::cerr << std::fixed << std::setprecision(3); @@ -825,14 +671,14 @@ int IClient::mainImpl() } -void IClient::initialize(Poco::Util::Application & self) +void ClientBase::initialize(Poco::Util::Application & self) { Poco::Util::Application::initialize(self); initializeChild(); } -int IClient::main(const std::vector & /*args*/) +int ClientBase::main(const std::vector & /*args*/) { try { @@ -853,7 +699,7 @@ int IClient::main(const std::vector & /*args*/) } -void IClient::init(int argc, char ** argv) +void ClientBase::init(int argc, char ** argv) { namespace po = boost::program_options; diff --git a/src/Client/IClient.h b/src/Client/ClientBase.h similarity index 89% rename from src/Client/IClient.h rename to src/Client/ClientBase.h index 5cbdacca9de..3d575bf819a 100644 --- a/src/Client/IClient.h +++ b/src/Client/ClientBase.h @@ -11,7 +11,6 @@ #include #include - #if USE_REPLXX # include #elif defined(USE_READLINE) && USE_READLINE @@ -24,11 +23,11 @@ namespace DB { -class IClient : public Poco::Util::Application +class ClientBase : public Poco::Util::Application { public: - using Arguments = std::vector; + using Arguments = std::vector; int main(const std::vector & /*args*/) override; @@ -39,106 +38,10 @@ public: void init(int argc, char ** argv); protected: - NameSet exit_strings{"exit", "quit", "logout", "учше", "йгше", "дщпщге", "exit;", "quit;", "logout;", "учшеж", - "йгшеж", "дщпщгеж", "q", "й", "\\q", "\\Q", "\\й", "\\Й", ":q", "Жй"}; - - bool is_interactive = false; /// Use either interactive line editing interface or batch mode. - bool is_multiquery = false; - - 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. - - uint64_t terminal_width = 0; - - /// Settings specified via command line args - Settings cmd_settings; - - SharedContextHolder shared_context; - ContextMutablePtr global_context; - - QueryFuzzer fuzzer; - int query_fuzzer_runs = 0; - - /// If not empty, queries will be read from these files - std::vector queries_files; - - std::optional suggest; - - /// Path to a file containing command history. - String history_file; - - /// If not empty, run queries from these files before processing every file from 'queries_files'. - std::vector interleave_queries_files; - - 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; - - /// Current query as it was given to the client. - String full_query; - /// Parsed query. Is used to determine some settings (e.g. format, output file). - ASTPtr parsed_query; - // Current query as it will be executed either on server on in clickhouse-local. - // It may differ from the full query for INSERT queries, for which the data that follows - // the query is stripped and sent separately. - String query_to_execute; - - /// If the last query resulted in exception. `server_exception` or - /// `client_exception` must be set. - bool have_error = false; - /// The last exception that was received from the server. Is used for the - /// return code in batch mode. - std::unique_ptr server_exception; - /// Likewise, the last exception that occurred on the client. - std::unique_ptr client_exception; - - /// Buffer that reads from stdin in batch mode. - ReadBufferFromFileDescriptor std_in{STDIN_FILENO}; - /// Console output. - WriteBufferFromFileDescriptor std_out{STDOUT_FILENO}; - std::unique_ptr pager_cmd; - /// The user can specify to redirect query output to a file. - std::optional out_file_buf; - BlockOutputStreamPtr block_out_stream; - /// The user could specify special file for server logs (stderr by default) - std::unique_ptr out_logs_buf; - String server_logs_file; - BlockOutputStreamPtr logs_out_stream; - - /// We will format query_id in interactive mode in various ways, the default is just to print Query id: ... - std::vector> query_id_formats; - QueryProcessingStage::Enum query_processing_stage; - - /// How many rows have been read or written. - size_t processed_rows = 0; - - String current_profile; - - static bool isNewYearMode(); - - static bool isChineseNewYearMode(const String & local_tz); - -#if USE_REPLXX - static void highlight(const String & query, std::vector & colors); -#endif - bool processMultiQuery(const String & all_queries_text); bool processQueryText(const String & text); - static void clearTerminal(); - void runInteractive(); void runNonInteractive(); @@ -194,6 +97,7 @@ protected: virtual void processOptions(const OptionsDescription & options_description, const CommandLineOptions & options, const std::vector & external_tables_arguments) = 0; + virtual void processConfig() = 0; virtual bool supportPasswordOption() const = 0; @@ -205,6 +109,7 @@ protected: virtual bool processFile(const String & file) = 0; private: + static void clearTerminal(); inline String prompt() const { @@ -212,6 +117,86 @@ private: } void outputQueryInfo(bool echo_query_); + + +protected: + bool is_interactive = false; /// Use either interactive line editing interface or batch mode. + bool is_multiquery = false; + + 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. + + String home_path; + std::vector queries_files; /// If not empty, queries will be read from these files + String history_file; /// Path to a file containing command history. + std::vector interleave_queries_files; /// If not empty, run queries from these files before processing every file from 'queries_files'. + + 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; + + ProgressIndication progress_indication; + bool need_render_progress = true; + bool written_first_block = false; + size_t processed_rows = 0; /// How many rows have been read or written. + + bool stdin_is_a_tty = false; /// stdin is a terminal. + bool stdout_is_a_tty = false; /// stdout is a terminal. + uint64_t terminal_width = 0; + + /// Settings specified via command line args + Settings cmd_settings; + + SharedContextHolder shared_context; + ContextMutablePtr global_context; + + QueryFuzzer fuzzer; + int query_fuzzer_runs = 0; + + std::optional suggest; + + /// Current query as it was given to the client. + String full_query; + /// Parsed query. Is used to determine some settings (e.g. format, output file). + ASTPtr parsed_query; + // Current query as it will be executed either on server on in clickhouse-local. + // It may differ from the full query for INSERT queries, for which the data that follows + // the query is stripped and sent separately. + String query_to_execute; + + /// If the last query resulted in exception. `server_exception` or + /// `client_exception` must be set. + bool have_error = false; + /// The last exception that was received from the server. Is used for the + /// return code in batch mode. + std::unique_ptr server_exception; + /// Likewise, the last exception that occurred on the client. + std::unique_ptr client_exception; + + /// Buffer that reads from stdin in batch mode. + ReadBufferFromFileDescriptor std_in{STDIN_FILENO}; + /// Console output. + WriteBufferFromFileDescriptor std_out{STDOUT_FILENO}; + std::unique_ptr pager_cmd; + /// The user can specify to redirect query output to a file. + std::optional out_file_buf; + BlockOutputStreamPtr block_out_stream; + /// The user could specify special file for server logs (stderr by default) + std::unique_ptr out_logs_buf; + String server_logs_file; + BlockOutputStreamPtr logs_out_stream; + + /// We will format query_id in interactive mode in various ways, the default is just to print Query id: ... + std::vector> query_id_formats; + QueryProcessingStage::Enum query_processing_stage; + + String current_profile; + +private: + NameSet exit_strings{"exit", "quit", "logout", "учше", "йгше", "дщпщге", "exit;", "quit;", "logout;", "учшеж", + "йгшеж", "дщпщгеж", "q", "й", "\\q", "\\Q", "\\й", "\\Й", ":q", "Жй"}; + }; } diff --git a/src/Client/ClientBaseHelpers.h b/src/Client/ClientBaseHelpers.h new file mode 100644 index 00000000000..0a58d8b9358 --- /dev/null +++ b/src/Client/ClientBaseHelpers.h @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include + +#if USE_REPLXX +# include +#endif + + +namespace DB +{ + +/// Should we celebrate a bit? +bool isNewYearMode() +{ + time_t current_time = time(nullptr); + + /// It's bad to be intrusive. + if (current_time % 3 != 0) + return false; + + LocalDate now(current_time); + return (now.month() == 12 && now.day() >= 20) || (now.month() == 1 && now.day() <= 5); +} + + +bool isChineseNewYearMode(const String & local_tz) +{ + /// Days of Dec. 20 in Chinese calendar starting from year 2019 to year 2105 + static constexpr UInt16 chineseNewYearIndicators[] + = {18275, 18659, 19014, 19368, 19752, 20107, 20491, 20845, 21199, 21583, 21937, 22292, 22676, 23030, 23414, 23768, 24122, 24506, + 24860, 25215, 25599, 25954, 26308, 26692, 27046, 27430, 27784, 28138, 28522, 28877, 29232, 29616, 29970, 30354, 30708, 31062, + 31446, 31800, 32155, 32539, 32894, 33248, 33632, 33986, 34369, 34724, 35078, 35462, 35817, 36171, 36555, 36909, 37293, 37647, + 38002, 38386, 38740, 39095, 39479, 39833, 40187, 40571, 40925, 41309, 41664, 42018, 42402, 42757, 43111, 43495, 43849, 44233, + 44587, 44942, 45326, 45680, 46035, 46418, 46772, 47126, 47510, 47865, 48249, 48604, 48958, 49342}; + + /// All time zone names are acquired from https://www.iana.org/time-zones + static constexpr const char * chineseNewYearTimeZoneIndicators[] = { + /// Time zones celebrating Chinese new year. + "Asia/Shanghai", + "Asia/Chongqing", + "Asia/Harbin", + "Asia/Urumqi", + "Asia/Hong_Kong", + "Asia/Chungking", + "Asia/Macao", + "Asia/Macau", + "Asia/Taipei", + "Asia/Singapore", + + /// Time zones celebrating Chinese new year but with different festival names. Let's not print the message for now. + // "Asia/Brunei", + // "Asia/Ho_Chi_Minh", + // "Asia/Hovd", + // "Asia/Jakarta", + // "Asia/Jayapura", + // "Asia/Kashgar", + // "Asia/Kuala_Lumpur", + // "Asia/Kuching", + // "Asia/Makassar", + // "Asia/Pontianak", + // "Asia/Pyongyang", + // "Asia/Saigon", + // "Asia/Seoul", + // "Asia/Ujung_Pandang", + // "Asia/Ulaanbaatar", + // "Asia/Ulan_Bator", + }; + static constexpr size_t M = sizeof(chineseNewYearTimeZoneIndicators) / sizeof(chineseNewYearTimeZoneIndicators[0]); + + time_t current_time = time(nullptr); + + if (chineseNewYearTimeZoneIndicators + M + == std::find_if(chineseNewYearTimeZoneIndicators, chineseNewYearTimeZoneIndicators + M, [&local_tz](const char * tz) + { + return tz == local_tz; + })) + return false; + + /// It's bad to be intrusive. + if (current_time % 3 != 0) + return false; + + auto days = DateLUT::instance().toDayNum(current_time).toUnderType(); + for (auto d : chineseNewYearIndicators) + { + /// Let's celebrate until Lantern Festival + if (d <= days && d + 25 >= days) + return true; + else if (d > days) + return false; + } + return false; +} + + +#if USE_REPLXX +static void highlight(const String & query, std::vector & colors) +{ + using namespace replxx; + + static const std::unordered_map token_to_color + = {{TokenType::Whitespace, Replxx::Color::DEFAULT}, + {TokenType::Comment, Replxx::Color::GRAY}, + {TokenType::BareWord, Replxx::Color::DEFAULT}, + {TokenType::Number, Replxx::Color::GREEN}, + {TokenType::StringLiteral, Replxx::Color::CYAN}, + {TokenType::QuotedIdentifier, Replxx::Color::MAGENTA}, + {TokenType::OpeningRoundBracket, Replxx::Color::BROWN}, + {TokenType::ClosingRoundBracket, Replxx::Color::BROWN}, + {TokenType::OpeningSquareBracket, Replxx::Color::BROWN}, + {TokenType::ClosingSquareBracket, Replxx::Color::BROWN}, + {TokenType::DoubleColon, Replxx::Color::BROWN}, + {TokenType::OpeningCurlyBrace, Replxx::Color::INTENSE}, + {TokenType::ClosingCurlyBrace, Replxx::Color::INTENSE}, + + {TokenType::Comma, Replxx::Color::INTENSE}, + {TokenType::Semicolon, Replxx::Color::INTENSE}, + {TokenType::Dot, Replxx::Color::INTENSE}, + {TokenType::Asterisk, Replxx::Color::INTENSE}, + {TokenType::Plus, Replxx::Color::INTENSE}, + {TokenType::Minus, Replxx::Color::INTENSE}, + {TokenType::Slash, Replxx::Color::INTENSE}, + {TokenType::Percent, Replxx::Color::INTENSE}, + {TokenType::Arrow, Replxx::Color::INTENSE}, + {TokenType::QuestionMark, Replxx::Color::INTENSE}, + {TokenType::Colon, Replxx::Color::INTENSE}, + {TokenType::Equals, Replxx::Color::INTENSE}, + {TokenType::NotEquals, Replxx::Color::INTENSE}, + {TokenType::Less, Replxx::Color::INTENSE}, + {TokenType::Greater, Replxx::Color::INTENSE}, + {TokenType::LessOrEquals, Replxx::Color::INTENSE}, + {TokenType::GreaterOrEquals, Replxx::Color::INTENSE}, + {TokenType::Concatenation, Replxx::Color::INTENSE}, + {TokenType::At, Replxx::Color::INTENSE}, + {TokenType::DoubleAt, Replxx::Color::MAGENTA}, + + {TokenType::EndOfStream, Replxx::Color::DEFAULT}, + + {TokenType::Error, Replxx::Color::RED}, + {TokenType::ErrorMultilineCommentIsNotClosed, Replxx::Color::RED}, + {TokenType::ErrorSingleQuoteIsNotClosed, Replxx::Color::RED}, + {TokenType::ErrorDoubleQuoteIsNotClosed, Replxx::Color::RED}, + {TokenType::ErrorSinglePipeMark, Replxx::Color::RED}, + {TokenType::ErrorWrongNumber, Replxx::Color::RED}, + { TokenType::ErrorMaxQuerySizeExceeded, + Replxx::Color::RED }}; + + const Replxx::Color unknown_token_color = Replxx::Color::RED; + + Lexer lexer(query.data(), query.data() + query.size()); + size_t pos = 0; + + for (Token token = lexer.nextToken(); !token.isEnd(); token = lexer.nextToken()) + { + size_t utf8_len = UTF8::countCodePoints(reinterpret_cast(token.begin), token.size()); + for (size_t code_point_index = 0; code_point_index < utf8_len; ++code_point_index) + { + if (token_to_color.find(token.type) != token_to_color.end()) + colors[pos + code_point_index] = token_to_color.at(token.type); + else + colors[pos + code_point_index] = unknown_token_color; + } + + pos += utf8_len; + } +} +#endif + +}