#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" namespace fs = std::filesystem; using namespace std::literals; namespace CurrentMetrics { extern const Metric MemoryTracking; } namespace DB { namespace ErrorCodes { extern const int BAD_ARGUMENTS; extern const int DEADLOCK_AVOIDED; extern const int CLIENT_OUTPUT_FORMAT_SPECIFIED; extern const int UNKNOWN_PACKET_FROM_SERVER; extern const int NO_DATA_TO_INSERT; extern const int UNEXPECTED_PACKET_FROM_SERVER; extern const int INVALID_USAGE_OF_INPUT; extern const int CANNOT_SET_SIGNAL_HANDLER; extern const int UNRECOGNIZED_ARGUMENTS; extern const int LOGICAL_ERROR; extern const int CANNOT_OPEN_FILE; extern const int FILE_ALREADY_EXISTS; extern const int USER_SESSION_LIMIT_EXCEEDED; extern const int NOT_IMPLEMENTED; extern const int CANNOT_READ_FROM_FILE_DESCRIPTOR; extern const int USER_EXPIRED; } } namespace ProfileEvents { extern const Event UserTimeMicroseconds; extern const Event SystemTimeMicroseconds; } namespace { constexpr UInt64 THREAD_GROUP_ID = 0; } namespace DB { ProgressOption toProgressOption(std::string progress) { boost::to_upper(progress); if (progress == "OFF" || progress == "FALSE" || progress == "0" || progress == "NO") return ProgressOption::OFF; if (progress == "TTY" || progress == "ON" || progress == "TRUE" || progress == "1" || progress == "YES") return ProgressOption::TTY; if (progress == "ERR") return ProgressOption::ERR; if (progress == "DEFAULT") return ProgressOption::DEFAULT; throw boost::program_options::validation_error(boost::program_options::validation_error::invalid_option_value); } std::istream& operator>> (std::istream & in, ProgressOption & progress) { std::string token; in >> token; progress = toProgressOption(token); return in; } static ClientInfo::QueryKind parseQueryKind(const String & query_kind) { if (query_kind == "initial_query") return ClientInfo::QueryKind::INITIAL_QUERY; if (query_kind == "secondary_query") return ClientInfo::QueryKind::SECONDARY_QUERY; if (query_kind == "no_query") return ClientInfo::QueryKind::NO_QUERY; throw Exception(ErrorCodes::BAD_ARGUMENTS, "Unknown query kind {}", query_kind); } static void incrementProfileEventsBlock(Block & dst, const Block & src) { if (!dst) { dst = src.cloneEmpty(); } assertBlocksHaveEqualStructure(src, dst, "ProfileEvents"); std::unordered_map name_pos; for (size_t i = 0; i < dst.columns(); ++i) name_pos[dst.getByPosition(i).name] = i; size_t dst_rows = dst.rows(); MutableColumns mutable_columns = dst.mutateColumns(); auto & dst_column_host_name = typeid_cast(*mutable_columns[name_pos["host_name"]]); auto & dst_array_current_time = typeid_cast(*mutable_columns[name_pos["current_time"]]).getData(); auto & dst_array_type = typeid_cast(*mutable_columns[name_pos["type"]]).getData(); auto & dst_column_name = typeid_cast(*mutable_columns[name_pos["name"]]); auto & dst_array_value = typeid_cast(*mutable_columns[name_pos["value"]]).getData(); const auto & src_column_host_name = typeid_cast(*src.getByName("host_name").column); const auto & src_array_current_time = typeid_cast(*src.getByName("current_time").column).getData(); const auto & src_array_thread_id = typeid_cast(*src.getByName("thread_id").column).getData(); const auto & src_column_name = typeid_cast(*src.getByName("name").column); const auto & src_array_value = typeid_cast(*src.getByName("value").column).getData(); struct Id { StringRef name; StringRef host_name; bool operator<(const Id & rhs) const { return std::tie(name, host_name) < std::tie(rhs.name, rhs.host_name); } }; std::map rows_by_name; for (size_t src_row = 0; src_row < src.rows(); ++src_row) { /// Filter out threads stats, use stats from thread group /// Exactly stats from thread group is stored to the table system.query_log /// The stats from threads are less useful. /// They take more records, they need to be combined, /// there even could be several records from one thread. /// Server doesn't send it any more to the clients, so this code left for compatible auto thread_id = src_array_thread_id[src_row]; if (thread_id != THREAD_GROUP_ID) continue; Id id{ src_column_name.getDataAt(src_row), src_column_host_name.getDataAt(src_row), }; rows_by_name[id] = src_row; } /// Merge src into dst. for (size_t dst_row = 0; dst_row < dst_rows; ++dst_row) { Id id{ dst_column_name.getDataAt(dst_row), dst_column_host_name.getDataAt(dst_row), }; if (auto it = rows_by_name.find(id); it != rows_by_name.end()) { size_t src_row = it->second; dst_array_current_time[dst_row] = src_array_current_time[src_row]; switch (static_cast(dst_array_type[dst_row])) { case ProfileEvents::Type::INCREMENT: dst_array_value[dst_row] += src_array_value[src_row]; break; case ProfileEvents::Type::GAUGE: dst_array_value[dst_row] = src_array_value[src_row]; break; } rows_by_name.erase(it); } } /// Copy rows from src that dst does not contains. for (const auto & [id, pos] : rows_by_name) { for (size_t col = 0; col < src.columns(); ++col) { mutable_columns[col]->insert((*src.getByPosition(col).column)[pos]); } } dst.setColumns(std::move(mutable_columns)); } std::atomic exit_after_signals = 0; class QueryInterruptHandler : private boost::noncopyable { public: /// Store how much interrupt signals can be before stopping the query /// by default stop after the first interrupt signal. static void start(Int32 signals_before_stop = 1) { exit_after_signals.store(signals_before_stop); } /// Set value not greater then 0 to mark the query as stopped. static void stop() { exit_after_signals.store(0); } /// Return true if the query was stopped. /// Query was stopped if it received at least "signals_before_stop" interrupt signals. static bool try_stop() { return exit_after_signals.fetch_sub(1) <= 0; } static bool cancelled() { return exit_after_signals.load() <= 0; } /// Return how much interrupt signals remain before stop. static Int32 cancelled_status() { return exit_after_signals.load(); } }; /// This signal handler is set for SIGINT and SIGQUIT. void interruptSignalHandler(int signum) { if (QueryInterruptHandler::try_stop()) safeExit(128 + signum); } /// To cancel the query on local format error. class LocalFormatError : public DB::Exception { public: using Exception::Exception; }; ClientBase::~ClientBase() { writeSignalIDtoSignalPipe(SignalListener::StopThread); signal_listener_thread.join(); HandledSignals::instance().reset(); } ClientBase::ClientBase( int in_fd_, int out_fd_, int err_fd_, std::istream & input_stream_, std::ostream & output_stream_, std::ostream & error_stream_ ) : std_in(in_fd_) , std_out(out_fd_) , progress_indication(output_stream_, in_fd_, err_fd_) , in_fd(in_fd_) , out_fd(out_fd_) , err_fd(err_fd_) , input_stream(input_stream_) , output_stream(output_stream_) , error_stream(error_stream_) { stdin_is_a_tty = isatty(in_fd); stdout_is_a_tty = isatty(out_fd); stderr_is_a_tty = isatty(err_fd); terminal_width = getTerminalWidth(in_fd, err_fd); } void ClientBase::setupSignalHandler() { QueryInterruptHandler::stop(); struct sigaction new_act; memset(&new_act, 0, sizeof(new_act)); new_act.sa_handler = interruptSignalHandler; new_act.sa_flags = 0; #if defined(OS_DARWIN) sigemptyset(&new_act.sa_mask); #else if (sigemptyset(&new_act.sa_mask)) throw ErrnoException(ErrorCodes::CANNOT_SET_SIGNAL_HANDLER, "Cannot set signal handler"); #endif if (sigaction(SIGINT, &new_act, nullptr)) throw ErrnoException(ErrorCodes::CANNOT_SET_SIGNAL_HANDLER, "Cannot set signal handler"); if (sigaction(SIGQUIT, &new_act, nullptr)) throw ErrnoException(ErrorCodes::CANNOT_SET_SIGNAL_HANDLER, "Cannot set signal handler"); } ASTPtr ClientBase::parseQuery(const char *& pos, const char * end, const Settings & settings, bool allow_multi_statements) { std::unique_ptr parser; ASTPtr res; size_t max_length = 0; if (!allow_multi_statements) max_length = settings.max_query_size; const Dialect & dialect = settings.dialect; if (dialect == Dialect::kusto) parser = std::make_unique(end, settings.allow_settings_after_format_in_insert); else if (dialect == Dialect::prql) parser = std::make_unique(max_length, settings.max_parser_depth, settings.max_parser_backtracks); else parser = std::make_unique(end, settings.allow_settings_after_format_in_insert); if (is_interactive || ignore_error) { String message; if (dialect == Dialect::kusto) res = tryParseKQLQuery(*parser, pos, end, message, true, "", allow_multi_statements, max_length, settings.max_parser_depth, settings.max_parser_backtracks, true); else res = tryParseQuery(*parser, pos, end, message, true, "", allow_multi_statements, max_length, settings.max_parser_depth, settings.max_parser_backtracks, true); if (!res) { error_stream << std::endl << message << std::endl << std::endl; return nullptr; } } else { if (dialect == Dialect::kusto) res = parseKQLQueryAndMovePosition(*parser, pos, end, "", allow_multi_statements, max_length, settings.max_parser_depth, settings.max_parser_backtracks); else res = parseQueryAndMovePosition(*parser, pos, end, "", allow_multi_statements, max_length, settings.max_parser_depth, settings.max_parser_backtracks); } if (is_interactive) { output_stream << std::endl; WriteBufferFromOStream res_buf(output_stream, 4096); formatAST(*res, res_buf); res_buf.finalize(); output_stream << std::endl << std::endl; } return res; } /// Consumes trailing semicolons and tries to consume the same-line trailing comment. void ClientBase::adjustQueryEnd(const char *& this_query_end, const char * all_queries_end, uint32_t max_parser_depth, uint32_t max_parser_backtracks) { // We have to skip the trailing semicolon that might be left // after VALUES parsing or just after a normal semicolon-terminated query. Tokens after_query_tokens(this_query_end, all_queries_end); IParser::Pos after_query_iterator(after_query_tokens, max_parser_depth, max_parser_backtracks); while (after_query_iterator.isValid() && after_query_iterator->type == TokenType::Semicolon) { this_query_end = after_query_iterator->end; ++after_query_iterator; } // Now we have to do some extra work to add the trailing // same-line comment to the query, but preserve the leading // comments of the next query. The trailing comment is important // because the test hints are usually written this way, e.g.: // select nonexistent_column; -- { serverError 12345 }. // The token iterator skips comments and whitespace, so we have // to find the newline in the string manually. If it's earlier // than the next significant token, it means that the text before // newline is some trailing whitespace or comment, and we should // add it to our query. There are also several special cases // that are described below. const auto * newline = find_first_symbols<'\n'>(this_query_end, all_queries_end); const char * next_query_begin = after_query_iterator->begin; // We include the entire line if the next query starts after // it. This is a generic case of trailing in-line comment. // The "equals" condition is for case of end of input (they both equal // all_queries_end); if (newline <= next_query_begin) { assert(newline >= this_query_end); this_query_end = newline; } else { // Many queries on one line, can't do anything. By the way, this // syntax is probably going to work as expected: // select nonexistent /* { serverError 12345 } */; select 1 } } /// Convert external tables to ExternalTableData and send them using the connection. void ClientBase::sendExternalTables(ASTPtr parsed_query) { const auto * select = parsed_query->as(); if (!select && !external_tables.empty()) throw Exception(ErrorCodes::BAD_ARGUMENTS, "External tables could be sent only with select query"); std::vector data; for (auto & table : external_tables) data.emplace_back(table.getData(global_context)); connection->sendExternalTablesData(data); } void ClientBase::onData(Block & block, ASTPtr parsed_query) { if (!block) return; processed_rows += block.rows(); /// Even if all blocks are empty, we still need to initialize the output stream to write empty resultset. initOutputFormat(block, parsed_query); /// The header block containing zero rows was used to initialize /// output_format, do not output it. /// Also do not output too much data if we're fuzzing. if (block.rows() == 0 || (query_fuzzer_runs != 0 && processed_rows >= 100)) return; /// If results are written INTO OUTFILE, we can avoid clearing progress to avoid flicker. if (need_render_progress && tty_buf && (!select_into_file || select_into_file_and_stdout)) progress_indication.clearProgressOutput(*tty_buf); try { output_format->write(materializeBlock(block)); written_first_block = true; } catch (const Exception &) { /// Catch client errors like NO_ROW_DELIMITER throw LocalFormatError(getCurrentExceptionMessageAndPattern(print_stack_trace), getCurrentExceptionCode()); } /// Received data block is immediately displayed to the user. output_format->flush(); /// Restore progress bar after data block. if (need_render_progress && tty_buf) { if (select_into_file && !select_into_file_and_stdout) error_stream << "\r"; progress_indication.writeProgress(*tty_buf); } } void ClientBase::onLogData(Block & block) { initLogsOutputStream(); if (need_render_progress && tty_buf) progress_indication.clearProgressOutput(*tty_buf); logs_out_stream->writeLogs(block); logs_out_stream->flush(); } void ClientBase::onTotals(Block & block, ASTPtr parsed_query) { initOutputFormat(block, parsed_query); output_format->setTotals(materializeBlock(block)); } void ClientBase::onExtremes(Block & block, ASTPtr parsed_query) { initOutputFormat(block, parsed_query); output_format->setExtremes(materializeBlock(block)); } void ClientBase::onReceiveExceptionFromServer(std::unique_ptr && e) { have_error = true; server_exception = std::move(e); resetOutput(); } void ClientBase::onProfileInfo(const ProfileInfo & profile_info) { if (profile_info.hasAppliedLimit() && output_format) output_format->setRowsBeforeLimit(profile_info.getRowsBeforeLimit()); } void ClientBase::initOutputFormat(const Block & block, ASTPtr parsed_query) try { if (!output_format) { /// Ignore all results when fuzzing as they can be huge. if (query_fuzzer_runs) { output_format = std::make_shared(block); return; } WriteBuffer * out_buf = nullptr; if (!pager.empty()) { if (SIG_ERR == signal(SIGPIPE, SIG_IGN)) throw ErrnoException(ErrorCodes::CANNOT_SET_SIGNAL_HANDLER, "Cannot set signal handler for SIGPIPE"); /// We need to reset signals that had been installed in the /// setupSignalHandler() since terminal will send signals to both /// processes and so signals will be delivered to the /// clickhouse-client/local as well, which will be terminated when /// signal will be delivered second time. if (SIG_ERR == signal(SIGINT, SIG_IGN)) throw ErrnoException(ErrorCodes::CANNOT_SET_SIGNAL_HANDLER, "Cannot set signal handler for SIGINT"); if (SIG_ERR == signal(SIGQUIT, SIG_IGN)) throw ErrnoException(ErrorCodes::CANNOT_SET_SIGNAL_HANDLER, "Cannot set signal handler for SIGQUIT"); ShellCommand::Config config(pager); config.pipe_stdin_only = true; pager_cmd = ShellCommand::execute(config); out_buf = &pager_cmd->in; } else { out_buf = &std_out; } String current_format = default_output_format; select_into_file = false; select_into_file_and_stdout = false; /// The query can specify output format or output file. if (const auto * query_with_output = dynamic_cast(parsed_query.get())) { String out_file; if (query_with_output->out_file) { select_into_file = true; const auto & out_file_node = query_with_output->out_file->as(); out_file = out_file_node.value.safeGet(); std::string compression_method_string; if (query_with_output->compression) { const auto & compression_method_node = query_with_output->compression->as(); compression_method_string = compression_method_node.value.safeGet(); } CompressionMethod compression_method = chooseCompressionMethod(out_file, compression_method_string); UInt64 compression_level = 3; if (query_with_output->compression_level) { const auto & compression_level_node = query_with_output->compression_level->as(); compression_level_node.value.tryGet(compression_level); } auto flags = O_WRONLY | O_EXCL; auto file_exists = fs::exists(out_file); if (file_exists && query_with_output->is_outfile_append) flags |= O_APPEND; else if (file_exists && query_with_output->is_outfile_truncate) flags |= O_TRUNC; else flags |= O_CREAT; out_file_buf = wrapWriteBufferWithCompressionMethod( std::make_unique(out_file, DBMS_DEFAULT_BUFFER_SIZE, flags), compression_method, static_cast(compression_level) ); if (query_with_output->is_into_outfile_with_stdout) { select_into_file_and_stdout = true; out_file_buf = std::make_unique(std::vector{std::move(out_file_buf), std::make_shared(STDOUT_FILENO)}); } // We are writing to file, so default format is the same as in non-interactive mode. if (is_interactive && is_default_format) current_format = "TabSeparated"; } if (query_with_output->format != nullptr) { if (has_vertical_output_suffix) throw Exception(ErrorCodes::CLIENT_OUTPUT_FORMAT_SPECIFIED, "Output format already specified"); const auto & id = query_with_output->format->as(); current_format = id.name(); } else if (query_with_output->out_file) { auto format_name = FormatFactory::instance().tryGetFormatFromFileName(out_file); if (format_name) current_format = *format_name; } } if (has_vertical_output_suffix) current_format = "Vertical"; bool logs_into_stdout = server_logs_file == "-"; bool extras_into_stdout = need_render_progress || logs_into_stdout; bool select_only_into_file = select_into_file && !select_into_file_and_stdout; if (!out_file_buf && default_output_compression_method != CompressionMethod::None) out_file_buf = wrapWriteBufferWithCompressionMethod(out_buf, default_output_compression_method, 3, 0); /// It is not clear how to write progress and logs /// intermixed with data with parallel formatting. /// It may increase code complexity significantly. if (!extras_into_stdout || select_only_into_file) output_format = global_context->getOutputFormatParallelIfPossible( current_format, out_file_buf ? *out_file_buf : *out_buf, block); else output_format = global_context->getOutputFormat( current_format, out_file_buf ? *out_file_buf : *out_buf, block); output_format->setAutoFlush(); } } catch (...) { throw LocalFormatError(getCurrentExceptionMessageAndPattern(print_stack_trace), getCurrentExceptionCode()); } void ClientBase::initLogsOutputStream() { if (!logs_out_stream) { WriteBuffer * wb = out_logs_buf.get(); bool color_logs = false; if (!out_logs_buf) { if (server_logs_file.empty()) { /// Use stderr by default out_logs_buf = std::make_unique(STDERR_FILENO); wb = out_logs_buf.get(); color_logs = stderr_is_a_tty; } else if (server_logs_file == "-") { /// Use stdout if --server_logs_file=- specified wb = &std_out; color_logs = stdout_is_a_tty; } else { out_logs_buf = std::make_unique(server_logs_file, DBMS_DEFAULT_BUFFER_SIZE, O_WRONLY | O_APPEND | O_CREAT); wb = out_logs_buf.get(); } } logs_out_stream = std::make_unique(*wb, color_logs); } } void ClientBase::adjustSettings() { Settings settings = global_context->getSettings(); /// NOTE: Do not forget to set changed=false to avoid sending it to the server (to avoid breakage read only profiles) /// In case of multi-query we allow data after semicolon since it will be /// parsed by the client and interpreted as new query if (is_multiquery && !global_context->getSettingsRef().input_format_values_allow_data_after_semicolon.changed) { settings.input_format_values_allow_data_after_semicolon = true; settings.input_format_values_allow_data_after_semicolon.changed = false; } /// Do not limit pretty format output in case of --pager specified or in case of stdout is not a tty. if (!pager.empty() || !stdout_is_a_tty) { if (!global_context->getSettingsRef().output_format_pretty_max_rows.changed) { settings.output_format_pretty_max_rows = std::numeric_limits::max(); settings.output_format_pretty_max_rows.changed = false; } if (!global_context->getSettingsRef().output_format_pretty_max_value_width.changed) { settings.output_format_pretty_max_value_width = std::numeric_limits::max(); settings.output_format_pretty_max_value_width.changed = false; } } global_context->setSettings(settings); } bool ClientBase::isRegularFile(int fd) { struct stat file_stat; return fstat(fd, &file_stat) == 0 && S_ISREG(file_stat.st_mode); } void ClientBase::setDefaultFormatsAndCompressionFromConfiguration() { if (getClientConfiguration().has("output-format")) { default_output_format = getClientConfiguration().getString("output-format"); is_default_format = false; } else if (getClientConfiguration().has("format")) { default_output_format = getClientConfiguration().getString("format"); is_default_format = false; } else if (getClientConfiguration().has("vertical")) { default_output_format = "Vertical"; is_default_format = false; } else if (isRegularFile(STDOUT_FILENO)) { std::optional format_from_file_name = FormatFactory::instance().tryGetFormatFromFileDescriptor(STDOUT_FILENO); if (format_from_file_name) default_output_format = *format_from_file_name; else default_output_format = "TSV"; std::optional file_name = tryGetFileNameFromFileDescriptor(STDOUT_FILENO); if (file_name) default_output_compression_method = chooseCompressionMethod(*file_name, ""); } else if (is_interactive) { default_output_format = "PrettyCompact"; } else { default_output_format = "TSV"; } if (getClientConfiguration().has("input-format")) { default_input_format = getClientConfiguration().getString("input-format"); } else if (getClientConfiguration().has("format")) { default_input_format = getClientConfiguration().getString("format"); } else if (getClientConfiguration().getString("table-file", "-") != "-") { auto file_name = getClientConfiguration().getString("table-file"); std::optional format_from_file_name = FormatFactory::instance().tryGetFormatFromFileName(file_name); if (format_from_file_name) default_input_format = *format_from_file_name; else default_input_format = "TSV"; } else { std::optional format_from_file_name = FormatFactory::instance().tryGetFormatFromFileDescriptor(STDIN_FILENO); if (format_from_file_name) default_input_format = *format_from_file_name; else default_input_format = "TSV"; } format_max_block_size = getClientConfiguration().getUInt64("format_max_block_size", global_context->getSettingsRef().max_block_size); /// Setting value from cmd arg overrides one from config if (global_context->getSettingsRef().max_insert_block_size.changed) { insert_format_max_block_size = global_context->getSettingsRef().max_insert_block_size; } else { insert_format_max_block_size = getClientConfiguration().getUInt64("insert_format_max_block_size", global_context->getSettingsRef().max_insert_block_size); } } void ClientBase::initTTYBuffer(ProgressOption progress) { if (tty_buf) return; if (progress == ProgressOption::OFF || (!is_interactive && progress == ProgressOption::DEFAULT)) { need_render_progress = false; return; } static constexpr auto tty_file_name = "/dev/tty"; /// Output all progress bar commands to terminal at once to avoid flicker. /// This size is usually greater than the window size. static constexpr size_t buf_size = 1024; if (is_interactive || progress == ProgressOption::TTY) { std::error_code ec; std::filesystem::file_status tty = std::filesystem::status(tty_file_name, ec); if (!ec && exists(tty) && is_character_file(tty) && (tty.permissions() & std::filesystem::perms::others_write) != std::filesystem::perms::none) { try { tty_buf = std::make_unique(tty_file_name, buf_size); /// It is possible that the terminal file has writeable permissions /// but we cannot write anything there. Check it with invisible character. tty_buf->write('\0'); tty_buf->next(); return; } catch (const Exception & e) { if (tty_buf) tty_buf.reset(); if (e.code() != ErrorCodes::CANNOT_OPEN_FILE) throw; /// It is normal if file exists, indicated as writeable but still cannot be opened. /// Fallback to other options. } } } if (stderr_is_a_tty || progress == ProgressOption::ERR) { tty_buf = std::make_unique(STDERR_FILENO, buf_size); } else need_render_progress = false; } void ClientBase::updateSuggest(const ASTPtr & ast) { std::vector new_words; if (auto * create = ast->as()) { if (create->database) new_words.push_back(create->getDatabase()); new_words.push_back(create->getTable()); if (create->columns_list && create->columns_list->columns) { for (const auto & elem : create->columns_list->columns->children) { if (const auto * column = elem->as()) new_words.push_back(column->name); } } } if (const auto * create_function = ast->as()) { new_words.push_back(create_function->getFunctionName()); } if (!new_words.empty()) suggest->addWords(std::move(new_words)); } bool ClientBase::isSyncInsertWithData(const ASTInsertQuery & insert_query, const ContextPtr & context) { if (!insert_query.data) return false; auto settings = context->getSettings(); if (insert_query.settings_ast) settings.applyChanges(insert_query.settings_ast->as()->changes); return !settings.async_insert; } void ClientBase::processTextAsSingleQuery(const String & full_query) { /// Some parts of a query (result output and formatting) are executed /// client-side. Thus we need to parse the query. const char * begin = full_query.data(); auto parsed_query = parseQuery(begin, begin + full_query.size(), global_context->getSettingsRef(), /*allow_multi_statements=*/ false); if (!parsed_query) return; 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. /// /// Do not update suggest, until suggestion will be ready /// (this will avoid extra complexity) if (suggest) updateSuggest(parsed_query); /// An INSERT query may have the data that follows query text. /// Send part of the query without data, because data will be sent separately. /// But for asynchronous inserts we don't extract data, because it's needed /// to be done on server side in that case (for coalescing the data from multiple inserts on server side). const auto * insert = parsed_query->as(); if (insert && isSyncInsertWithData(*insert, global_context)) query_to_execute = full_query.substr(0, insert->data - full_query.data()); else query_to_execute = full_query; try { processParsedSingleQuery(full_query, query_to_execute, parsed_query, echo_queries); } catch (Exception & e) { if (!is_interactive) e.addMessage("(in query: {})", full_query); throw; } if (have_error) processError(full_query); } void ClientBase::processOrdinaryQuery(const String & query_to_execute, ASTPtr parsed_query) { auto query = query_to_execute; /// Rewrite query only when we have query parameters. /// Note that if query is rewritten, comments in query are lost. /// But the user often wants to see comments in server logs, query log, processlist, etc. /// For recent versions of the server query parameters will be transferred by network and applied on the server side. if (!query_parameters.empty() && connection->getServerRevision(connection_parameters.timeouts) < DBMS_MIN_PROTOCOL_VERSION_WITH_PARAMETERS) { /// Replace ASTQueryParameter with ASTLiteral for prepared statements. ReplaceQueryParameterVisitor visitor(query_parameters); visitor.visit(parsed_query); /// Get new query after substitutions. if (visitor.getNumberOfReplacedParameters()) query = serializeAST(*parsed_query); chassert(!query.empty()); } if (allow_merge_tree_settings && parsed_query->as()) { /// Rewrite query if new settings were added. if (addMergeTreeSettings(*parsed_query->as())) { /// Replace query parameters because AST cannot be serialized otherwise. if (!query_parameters.empty()) { ReplaceQueryParameterVisitor visitor(query_parameters); visitor.visit(parsed_query); } query = serializeAST(*parsed_query); } } // Run some local checks to make sure queries into output file will work before sending to server. if (const auto * query_with_output = dynamic_cast(parsed_query.get())) { String out_file; if (query_with_output->out_file) { const auto & out_file_node = query_with_output->out_file->as(); out_file = out_file_node.value.safeGet(); std::string compression_method_string; if (query_with_output->compression) { const auto & compression_method_node = query_with_output->compression->as(); compression_method_string = compression_method_node.value.safeGet(); } CompressionMethod compression_method = chooseCompressionMethod(out_file, compression_method_string); UInt64 compression_level = 3; if (query_with_output->is_outfile_append && query_with_output->is_outfile_truncate) { throw Exception( ErrorCodes::BAD_ARGUMENTS, "Cannot use INTO OUTFILE with APPEND and TRUNCATE simultaneously."); } if (query_with_output->is_outfile_append && compression_method != CompressionMethod::None) { throw Exception( ErrorCodes::BAD_ARGUMENTS, "Cannot append to compressed file. Please use uncompressed file or remove APPEND keyword."); } if (query_with_output->compression_level) { const auto & compression_level_node = query_with_output->compression_level->as(); bool res = compression_level_node.value.tryGet(compression_level); auto range = getCompressionLevelRange(compression_method); if (!res || compression_level < range.first || compression_level > range.second) throw Exception( ErrorCodes::BAD_ARGUMENTS, "Invalid compression level, must be positive integer in range {}-{}", range.first, range.second); } if (fs::exists(out_file)) { if (!query_with_output->is_outfile_append && !query_with_output->is_outfile_truncate) { throw Exception( ErrorCodes::FILE_ALREADY_EXISTS, "File {} exists, consider using APPEND or TRUNCATE.", out_file); } } } } const auto & settings = global_context->getSettingsRef(); const Int32 signals_before_stop = settings.partial_result_on_first_cancel ? 2 : 1; int retries_left = 10; while (retries_left) { try { QueryInterruptHandler::start(signals_before_stop); SCOPE_EXIT({ QueryInterruptHandler::stop(); }); connection->sendQuery( connection_parameters.timeouts, query, query_parameters, global_context->getCurrentQueryId(), query_processing_stage, &global_context->getSettingsRef(), &global_context->getClientInfo(), true, [&](const Progress & progress) { onProgress(progress); }); if (send_external_tables) sendExternalTables(parsed_query); receiveResult(parsed_query, signals_before_stop, settings.partial_result_on_first_cancel); break; } catch (const Exception & e) { /// Retry when the server said "Client should retry" and no rows /// has been received yet. if (processed_rows == 0 && e.code() == ErrorCodes::DEADLOCK_AVOIDED && --retries_left) { error_stream << "Got a transient error from the server, will" << " retry (" << retries_left << " retries left)"; } else { throw; } } } assert(retries_left > 0); } /// Receives and processes packets coming from server. /// Also checks if query execution should be cancelled. void ClientBase::receiveResult(ASTPtr parsed_query, Int32 signals_before_stop, bool partial_result_on_first_cancel) { // TODO: get the poll_interval from commandline. const auto receive_timeout = connection_parameters.timeouts.receive_timeout; constexpr size_t default_poll_interval = 1000000; /// in microseconds constexpr size_t min_poll_interval = 5000; /// in microseconds const size_t poll_interval = std::max(min_poll_interval, std::min(receive_timeout.totalMicroseconds(), default_poll_interval)); bool break_on_timeout = connection->getConnectionType() != IServerConnection::Type::LOCAL; std::exception_ptr local_format_error; while (true) { Stopwatch receive_watch(CLOCK_MONOTONIC_COARSE); while (true) { /// Has the Ctrl+C been pressed and thus the query should be cancelled? /// If this is the case, inform the server about it and receive the remaining packets /// to avoid losing sync. if (!cancelled) { if (partial_result_on_first_cancel && QueryInterruptHandler::cancelled_status() == signals_before_stop - 1) { connection->sendCancel(); /// First cancel reading request was sent. Next requests will only be with a full cancel partial_result_on_first_cancel = false; } else if (QueryInterruptHandler::cancelled()) { cancelQuery(); } else { double elapsed = receive_watch.elapsedSeconds(); if (break_on_timeout && elapsed > receive_timeout.totalSeconds()) { output_stream << "Timeout exceeded while receiving data from server." << " Waited for " << static_cast(elapsed) << " seconds," << " timeout is " << receive_timeout.totalSeconds() << " seconds." << std::endl; cancelQuery(); } } } /// Poll for changes after a cancellation check, otherwise it never reached /// because of progress updates from server. if (connection->poll(poll_interval)) break; } try { if (!receiveAndProcessPacket(parsed_query, cancelled)) break; } catch (const LocalFormatError &) { /// Remember the first exception. if (!local_format_error) local_format_error = std::current_exception(); connection->sendCancel(); } } if (local_format_error) std::rethrow_exception(local_format_error); if (cancelled && is_interactive && !cancelled_printed.exchange(true)) output_stream << "Query was cancelled." << std::endl; } /// Receive a part of the result, or progress info or an exception and process it. /// Returns true if one should continue receiving packets. /// Output of result is suppressed if query was cancelled. bool ClientBase::receiveAndProcessPacket(ASTPtr parsed_query, bool cancelled_) { Packet packet = connection->receivePacket(); switch (packet.type) { case Protocol::Server::PartUUIDs: return true; case Protocol::Server::Data: if (!cancelled_) onData(packet.block, parsed_query); return true; case Protocol::Server::Progress: onProgress(packet.progress); return true; case Protocol::Server::ProfileInfo: onProfileInfo(packet.profile_info); return true; case Protocol::Server::Totals: if (!cancelled_) onTotals(packet.block, parsed_query); return true; case Protocol::Server::Extremes: if (!cancelled_) onExtremes(packet.block, parsed_query); return true; case Protocol::Server::Exception: onReceiveExceptionFromServer(std::move(packet.exception)); return false; case Protocol::Server::Log: onLogData(packet.block); return true; case Protocol::Server::EndOfStream: onEndOfStream(); return false; case Protocol::Server::ProfileEvents: onProfileEvents(packet.block); return true; case Protocol::Server::TimezoneUpdate: onTimezoneUpdate(packet.server_timezone); return true; default: throw Exception( ErrorCodes::UNKNOWN_PACKET_FROM_SERVER, "Unknown packet {} from server {}", packet.type, connection->getDescription()); } } void ClientBase::onProgress(const Progress & value) { if (!progress_indication.updateProgress(value)) { // Just a keep-alive update. return; } if (output_format) output_format->onProgress(value); if (need_render_progress && tty_buf) progress_indication.writeProgress(*tty_buf); } void ClientBase::onTimezoneUpdate(const String & tz) { global_context->setSetting("session_timezone", tz); } void ClientBase::onEndOfStream() { if (need_render_progress && tty_buf) progress_indication.clearProgressOutput(*tty_buf); if (output_format) { /// Do our best to estimate the start of the query so the output format matches the one reported by the server bool is_running = false; output_format->setStartTime( clock_gettime_ns(CLOCK_MONOTONIC) - static_cast(progress_indication.elapsedSeconds() * 1000000000), is_running); try { output_format->finalize(); } catch (...) { /// Format should be reset to make it work for subsequent query /// (otherwise it will throw again in resetOutput()) output_format.reset(); throw; } } resetOutput(); if (is_interactive) { if (cancelled && !cancelled_printed.exchange(true)) output_stream << "Query was cancelled." << std::endl; else if (!written_first_block) output_stream << "Ok." << std::endl; } } void ClientBase::onProfileEvents(Block & block) { const auto rows = block.rows(); if (rows == 0) return; if (getName() == "local" || server_revision >= DBMS_MIN_PROTOCOL_VERSION_WITH_INCREMENTAL_PROFILE_EVENTS) { const auto & array_thread_id = typeid_cast(*block.getByName("thread_id").column).getData(); const auto & names = typeid_cast(*block.getByName("name").column); const auto & host_names = typeid_cast(*block.getByName("host_name").column); const auto & array_values = typeid_cast(*block.getByName("value").column).getData(); const auto * user_time_name = ProfileEvents::getName(ProfileEvents::UserTimeMicroseconds); const auto * system_time_name = ProfileEvents::getName(ProfileEvents::SystemTimeMicroseconds); HostToTimesMap thread_times; for (size_t i = 0; i < rows; ++i) { auto thread_id = array_thread_id[i]; auto host_name = host_names.getDataAt(i).toString(); /// In ProfileEvents packets thread id 0 specifies common profiling information /// for all threads executing current query on specific host. So instead of summing per thread /// consumption it's enough to look for data with thread id 0. if (thread_id != THREAD_GROUP_ID) continue; auto event_name = names.getDataAt(i); auto value = array_values[i]; /// Ignore negative time delta or memory usage just in case. if (value < 0) continue; if (event_name == user_time_name) thread_times[host_name].user_ms = value; else if (event_name == system_time_name) thread_times[host_name].system_ms = value; else if (event_name == MemoryTracker::USAGE_EVENT_NAME) thread_times[host_name].memory_usage = value; else if (event_name == MemoryTracker::PEAK_USAGE_EVENT_NAME) thread_times[host_name].peak_memory_usage = value; } progress_indication.updateThreadEventData(thread_times); if (need_render_progress && tty_buf) progress_indication.writeProgress(*tty_buf); if (profile_events.print) { if (profile_events.watch.elapsedMilliseconds() >= profile_events.delay_ms) { /// We need to restart the watch each time we flushed these events profile_events.watch.restart(); initLogsOutputStream(); if (need_render_progress && tty_buf) progress_indication.clearProgressOutput(*tty_buf); logs_out_stream->writeProfileEvents(block); logs_out_stream->flush(); profile_events.last_block = {}; } else { incrementProfileEventsBlock(profile_events.last_block, block); } } } } /// Flush all buffers. void ClientBase::resetOutput() { /// Order is important: format, compression, file if (output_format) output_format->finalize(); output_format.reset(); logs_out_stream.reset(); if (out_file_buf) { out_file_buf->finalize(); out_file_buf.reset(); } if (pager_cmd) { pager_cmd->in.close(); pager_cmd->wait(); if (SIG_ERR == signal(SIGPIPE, SIG_DFL)) throw ErrnoException(ErrorCodes::CANNOT_SET_SIGNAL_HANDLER, "Cannot set signal handler for SIGPIPE"); if (SIG_ERR == signal(SIGINT, SIG_DFL)) throw ErrnoException(ErrorCodes::CANNOT_SET_SIGNAL_HANDLER, "Cannot set signal handler for SIGINT"); if (SIG_ERR == signal(SIGQUIT, SIG_DFL)) throw ErrnoException(ErrorCodes::CANNOT_SET_SIGNAL_HANDLER, "Cannot set signal handler for SIGQUIT"); setupSignalHandler(); } pager_cmd = nullptr; if (out_logs_buf) { out_logs_buf->finalize(); out_logs_buf.reset(); } std_out.next(); } /// Receive the block that serves as an example of the structure of table where data will be inserted. bool ClientBase::receiveSampleBlock(Block & out, ColumnsDescription & columns_description, ASTPtr parsed_query) { while (true) { Packet packet = connection->receivePacket(); switch (packet.type) { case Protocol::Server::Data: out = packet.block; return true; case Protocol::Server::Exception: onReceiveExceptionFromServer(std::move(packet.exception)); return false; case Protocol::Server::Log: onLogData(packet.block); break; case Protocol::Server::TableColumns: columns_description = ColumnsDescription::parse(packet.multistring_message[1]); return receiveSampleBlock(out, columns_description, parsed_query); case Protocol::Server::TimezoneUpdate: onTimezoneUpdate(packet.server_timezone); break; default: throw NetException(ErrorCodes::UNEXPECTED_PACKET_FROM_SERVER, "Unexpected packet from server (expected Data, Exception, Log or TimezoneUpdate, got {})", String(Protocol::Server::toString(packet.type))); } } } void ClientBase::setInsertionTable(const ASTInsertQuery & insert_query) { if (!global_context->hasInsertionTable() && insert_query.table) { String table = insert_query.table->as().shortName(); if (!table.empty()) { String database = insert_query.database ? insert_query.database->as().shortName() : ""; global_context->setInsertionTable(StorageID(database, table)); } } } void ClientBase::addMultiquery(std::string_view query, Arguments & common_arguments) const { common_arguments.emplace_back("--multiquery"); common_arguments.emplace_back("-q"); common_arguments.emplace_back(query); } namespace { bool isStdinNotEmptyAndValid(ReadBufferFromFileDescriptor & std_in) { try { return !std_in.eof(); } catch (const Exception & e) { if (e.code() == ErrorCodes::CANNOT_READ_FROM_FILE_DESCRIPTOR) return false; throw; } } } void ClientBase::processInsertQuery(const String & query_to_execute, ASTPtr parsed_query) { auto query = query_to_execute; if (!query_parameters.empty() && connection->getServerRevision(connection_parameters.timeouts) < DBMS_MIN_PROTOCOL_VERSION_WITH_PARAMETERS) { /// Replace ASTQueryParameter with ASTLiteral for prepared statements. ReplaceQueryParameterVisitor visitor(query_parameters); visitor.visit(parsed_query); /// Get new query after substitutions. if (visitor.getNumberOfReplacedParameters()) query = serializeAST(*parsed_query); chassert(!query.empty()); } /// Process the query that requires transferring data blocks to the server. const auto & parsed_insert_query = parsed_query->as(); if ((!parsed_insert_query.data && !parsed_insert_query.infile) && (is_interactive || (!stdin_is_a_tty && !isStdinNotEmptyAndValid(std_in)))) { const auto & settings = global_context->getSettingsRef(); if (settings.throw_if_no_data_to_insert) throw Exception(ErrorCodes::NO_DATA_TO_INSERT, "No data to insert"); else return; } QueryInterruptHandler::start(); SCOPE_EXIT({ QueryInterruptHandler::stop(); }); connection->sendQuery( connection_parameters.timeouts, query, query_parameters, global_context->getCurrentQueryId(), query_processing_stage, &global_context->getSettingsRef(), &global_context->getClientInfo(), true, [&](const Progress & progress) { onProgress(progress); }); if (send_external_tables) sendExternalTables(parsed_query); /// Receive description of table structure. Block sample; ColumnsDescription columns_description; if (receiveSampleBlock(sample, columns_description, parsed_query)) { /// If structure was received (thus, server has not thrown an exception), /// send our data with that structure. setInsertionTable(parsed_insert_query); sendData(sample, columns_description, parsed_query); receiveEndOfQuery(); } } void ClientBase::sendData(Block & sample, const ColumnsDescription & columns_description, ASTPtr parsed_query) { /// Get columns description from variable or (if it was empty) create it from sample. auto columns_description_for_query = columns_description.empty() ? ColumnsDescription(sample.getNamesAndTypesList()) : columns_description; if (columns_description_for_query.empty()) { throw Exception(ErrorCodes::LOGICAL_ERROR, "Column description is empty and it can't be built from sample from table. " "Cannot execute query."); } /// If INSERT data must be sent. auto * parsed_insert_query = parsed_query->as(); if (!parsed_insert_query) return; bool have_data_in_stdin = !is_interactive && !stdin_is_a_tty && isStdinNotEmptyAndValid(std_in); if (need_render_progress) { /// Set total_bytes_to_read for current fd. FileProgress file_progress(0, std_in.getFileSize()); progress_indication.updateProgress(Progress(file_progress)); /// Set callback to be called on file progress. if (tty_buf) progress_indication.setFileProgressCallback(global_context, *tty_buf); } /// If data fetched from file (maybe compressed file) if (parsed_insert_query->infile) { /// Get name of this file (path to file) const auto & in_file_node = parsed_insert_query->infile->as(); const auto in_file = in_file_node.value.safeGet(); std::string compression_method; /// Compression method can be specified in query if (parsed_insert_query->compression) { const auto & compression_method_node = parsed_insert_query->compression->as(); compression_method = compression_method_node.value.safeGet(); } String current_format = parsed_insert_query->format; if (current_format.empty()) current_format = FormatFactory::instance().getFormatFromFileName(in_file); /// Create temporary storage file, to support globs and parallel reading /// StorageFile doesn't support ephemeral/materialized/alias columns. /// We should change ephemeral columns to ordinary and ignore materialized/alias columns. ColumnsDescription columns_for_storage_file; for (const auto & [name, _] : columns_description_for_query.getInsertable()) { ColumnDescription column = columns_description_for_query.get(name); column.default_desc.kind = ColumnDefaultKind::Default; columns_for_storage_file.add(std::move(column)); } StorageFile::CommonArguments args{ WithContext(global_context), parsed_insert_query->table_id, current_format, getFormatSettings(global_context), compression_method, columns_for_storage_file, ConstraintsDescription{}, String{}, {}, String{}, }; StoragePtr storage = std::make_shared(in_file, global_context->getUserFilesPath(), args); storage->startup(); SelectQueryInfo query_info; try { auto metadata = storage->getInMemoryMetadataPtr(); QueryPlan plan; storage->read( plan, sample.getNames(), storage->getStorageSnapshot(metadata, global_context), query_info, global_context, {}, global_context->getSettingsRef().max_block_size, getNumberOfPhysicalCPUCores()); auto builder = plan.buildQueryPipeline( QueryPlanOptimizationSettings::fromContext(global_context), BuildQueryPipelineSettings::fromContext(global_context)); QueryPlanResourceHolder resources; auto pipe = QueryPipelineBuilder::getPipe(std::move(*builder), resources); sendDataFromPipe( std::move(pipe), parsed_query, have_data_in_stdin); } catch (Exception & e) { e.addMessage("data for INSERT was parsed from file"); throw; } if (have_data_in_stdin && !cancelled) sendDataFromStdin(sample, columns_description_for_query, parsed_query); } else if (parsed_insert_query->data) { /// Send data contained in the query. ReadBufferFromMemory data_in(parsed_insert_query->data, parsed_insert_query->end - parsed_insert_query->data); try { sendDataFrom(data_in, sample, columns_description_for_query, parsed_query, have_data_in_stdin); if (have_data_in_stdin && !cancelled) sendDataFromStdin(sample, columns_description_for_query, parsed_query); } catch (Exception & e) { /// The following query will use data from input // "INSERT INTO data FORMAT TSV\n " < data.csv // And may be pretty hard to debug, so add information about data source to make it easier. e.addMessage("data for INSERT was parsed from query"); throw; } // Remember where the data ended. We use this info later to determine // where the next query begins. parsed_insert_query->end = parsed_insert_query->data + data_in.count(); } else if (!is_interactive) { sendDataFromStdin(sample, columns_description_for_query, parsed_query); } else throw Exception(ErrorCodes::NO_DATA_TO_INSERT, "No data to insert"); } void ClientBase::sendDataFrom(ReadBuffer & buf, Block & sample, const ColumnsDescription & columns_description, ASTPtr parsed_query, bool have_more_data) { String current_format = "Values"; /// Data format can be specified in the INSERT query. if (const auto * insert = parsed_query->as()) { if (!insert->format.empty()) current_format = insert->format; } auto source = global_context->getInputFormat(current_format, buf, sample, insert_format_max_block_size); Pipe pipe(source); if (columns_description.hasDefaults()) { pipe.addSimpleTransform([&](const Block & header) { return std::make_shared(header, columns_description, *source, global_context); }); } sendDataFromPipe(std::move(pipe), parsed_query, have_more_data); } void ClientBase::sendDataFromPipe(Pipe&& pipe, ASTPtr parsed_query, bool have_more_data) try { QueryPipeline pipeline(std::move(pipe)); PullingAsyncPipelineExecutor executor(pipeline); if (need_render_progress) { pipeline.setProgressCallback([this](const Progress & progress){ onProgress(progress); }); } Block block; while (executor.pull(block)) { if (!cancelled && QueryInterruptHandler::cancelled()) { cancelQuery(); executor.cancel(); return; } /// Check if server send Log packet receiveLogsAndProfileEvents(parsed_query); /// Check if server send Exception packet auto packet_type = connection->checkPacket(0); if (packet_type && *packet_type == Protocol::Server::Exception) { /** * We're exiting with error, so it makes sense to kill the * input stream without waiting for it to complete. */ executor.cancel(); return; } if (block) { connection->sendData(block, /* name */"", /* scalar */false); processed_rows += block.rows(); } } if (!have_more_data) connection->sendData({}, "", false); } catch (...) { connection->sendCancel(); receiveEndOfQuery(); throw; } void ClientBase::sendDataFromStdin(Block & sample, const ColumnsDescription & columns_description, ASTPtr parsed_query) { /// Send data read from stdin. try { sendDataFrom(std_in, sample, columns_description, parsed_query); } catch (Exception & e) { e.addMessage("data for INSERT was parsed from stdin"); throw; } } /// Process Log packets, used when inserting data by blocks void ClientBase::receiveLogsAndProfileEvents(ASTPtr parsed_query) { auto packet_type = connection->checkPacket(0); while (packet_type && (*packet_type == Protocol::Server::Log || *packet_type == Protocol::Server::ProfileEvents || *packet_type == Protocol::Server::TimezoneUpdate)) { receiveAndProcessPacket(parsed_query, false); packet_type = connection->checkPacket(0); } } /// Process Log packets, exit when receive Exception or EndOfStream bool ClientBase::receiveEndOfQuery() { while (true) { Packet packet = connection->receivePacket(); switch (packet.type) { case Protocol::Server::EndOfStream: onEndOfStream(); return true; case Protocol::Server::Exception: onReceiveExceptionFromServer(std::move(packet.exception)); return false; case Protocol::Server::Log: onLogData(packet.block); break; case Protocol::Server::Progress: onProgress(packet.progress); break; case Protocol::Server::ProfileEvents: onProfileEvents(packet.block); break; case Protocol::Server::TimezoneUpdate: onTimezoneUpdate(packet.server_timezone); break; default: throw NetException(ErrorCodes::UNEXPECTED_PACKET_FROM_SERVER, "Unexpected packet from server (expected Exception, EndOfStream, Log, Progress or ProfileEvents. Got {})", String(Protocol::Server::toString(packet.type))); } } } void ClientBase::cancelQuery() { connection->sendCancel(); if (need_render_progress && tty_buf) progress_indication.clearProgressOutput(*tty_buf); if (is_interactive) output_stream << "Cancelling query." << std::endl; cancelled = true; } void ClientBase::processParsedSingleQuery(const String & full_query, const String & query_to_execute, ASTPtr parsed_query, std::optional echo_query_, bool report_error) { resetOutput(); have_error = false; cancelled = false; cancelled_printed = false; client_exception.reset(); server_exception.reset(); if (echo_query_ && *echo_query_) { writeString(full_query, std_out); writeChar('\n', std_out); std_out.next(); } if (is_interactive) { global_context->setCurrentQueryId(""); // Generate a new query_id for (const auto & query_id_format : query_id_formats) { writeString(query_id_format.first, std_out); writeString(fmt::format(fmt::runtime(query_id_format.second), fmt::arg("query_id", global_context->getCurrentQueryId())), std_out); writeChar('\n', std_out); std_out.next(); } } if (const auto * set_query = parsed_query->as()) { const auto * logs_level_field = set_query->changes.tryGet(std::string_view{"send_logs_level"}); if (logs_level_field) { auto logs_level = logs_level_field->safeGet(); /// Check that setting value is correct before updating logger level. SettingFieldLogsLevelTraits::fromString(logs_level); updateLoggerLevel(logs_level); } } if (const auto * create_user_query = parsed_query->as()) { if (!create_user_query->attach && create_user_query->auth_data) { if (const auto * auth_data = create_user_query->auth_data->as()) { auto password = auth_data->getPassword(); if (password) global_context->getAccessControl().checkPasswordComplexityRules(*password); } } } processed_rows = 0; written_first_block = false; progress_indication.resetProgress(); profile_events.watch.restart(); { /// Temporarily apply query settings to context. std::optional old_settings; SCOPE_EXIT_SAFE({ if (old_settings) global_context->setSettings(*old_settings); }); auto apply_query_settings = [&](const IAST & settings_ast) { if (!old_settings) old_settings.emplace(global_context->getSettingsRef()); global_context->applySettingsChanges(settings_ast.as()->changes); global_context->resetSettingsToDefaultValue(settings_ast.as()->default_settings); }; const auto * insert = parsed_query->as(); if (const auto * select = parsed_query->as(); select && select->settings()) apply_query_settings(*select->settings()); else if (const auto * select_with_union = parsed_query->as()) { const ASTs & children = select_with_union->list_of_selects->children; if (!children.empty()) { // On the client it is enough to apply settings only for the // last SELECT, since the only thing that is important to apply // on the client is format settings. const auto * last_select = children.back()->as(); if (last_select && last_select->settings()) { apply_query_settings(*last_select->settings()); } } } else if (const auto * query_with_output = parsed_query->as(); query_with_output && query_with_output->settings_ast) apply_query_settings(*query_with_output->settings_ast); else if (insert && insert->settings_ast) apply_query_settings(*insert->settings_ast); if (!connection->checkConnected(connection_parameters.timeouts)) connect(); ASTPtr input_function; if (insert && insert->select) insert->tryFindInputFunction(input_function); bool is_async_insert_with_inlined_data = global_context->getSettingsRef().async_insert && insert && insert->hasInlinedData(); if (is_async_insert_with_inlined_data) { bool have_data_in_stdin = !is_interactive && !stdin_is_a_tty && isStdinNotEmptyAndValid(std_in); bool have_external_data = have_data_in_stdin || insert->infile; if (have_external_data) throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Processing async inserts with both inlined and external data (from stdin or infile) is not supported"); } /// INSERT query for which data transfer is needed (not an INSERT SELECT or input()) is processed separately. if (insert && (!insert->select || input_function) && !is_async_insert_with_inlined_data) { if (input_function && insert->format.empty()) throw Exception(ErrorCodes::INVALID_USAGE_OF_INPUT, "FORMAT must be specified for function input()"); processInsertQuery(query_to_execute, parsed_query); } else processOrdinaryQuery(query_to_execute, parsed_query); } /// Do not change context (current DB, settings) in case of an exception. if (!have_error) { if (const auto * set_query = parsed_query->as()) { /// Save all changes in settings to avoid losing them if the connection is lost. for (const auto & change : set_query->changes) { if (change.name == "profile") current_profile = change.value.safeGet(); else global_context->applySettingChange(change); } global_context->resetSettingsToDefaultValue(set_query->default_settings); /// Query parameters inside SET queries should be also saved on the client side /// to override their previous definitions set with --param_* arguments /// and for substitutions to work inside INSERT ... VALUES queries for (const auto & [name, value] : set_query->query_parameters) query_parameters.insert_or_assign(name, value); global_context->addQueryParameters(NameToNameMap{set_query->query_parameters.begin(), set_query->query_parameters.end()}); } if (const auto * use_query = parsed_query->as()) { const String & new_database = use_query->getDatabase(); /// If the client initiates the reconnection, it takes the settings from the config. getClientConfiguration().setString("database", new_database); /// If the connection initiates the reconnection, it uses its variable. connection->setDefaultDatabase(new_database); } } /// Always print last block (if it was not printed already) if (profile_events.last_block) { initLogsOutputStream(); if (need_render_progress && tty_buf) progress_indication.clearProgressOutput(*tty_buf); logs_out_stream->writeProfileEvents(profile_events.last_block); logs_out_stream->flush(); profile_events.last_block = {}; } if (is_interactive) { output_stream << std::endl; if (!server_exception || processed_rows != 0) output_stream << processed_rows << " row" << (processed_rows == 1 ? "" : "s") << " in set. "; output_stream << "Elapsed: " << progress_indication.elapsedSeconds() << " sec. "; progress_indication.writeFinalProgress(); output_stream << std::endl << std::endl; } else if (getClientConfiguration().getBool("print-time-to-stderr", false)) { error_stream << progress_indication.elapsedSeconds() << "\n"; } if (!is_interactive && getClientConfiguration().getBool("print-num-processed-rows", false)) { output_stream << "Processed rows: " << processed_rows << "\n"; } if (have_error && report_error) processError(full_query); } MultiQueryProcessingStage ClientBase::analyzeMultiQueryText( const char *& this_query_begin, const char *& this_query_end, const char * all_queries_end, String & query_to_execute, ASTPtr & parsed_query, const String & all_queries_text, std::unique_ptr & current_exception) { if (!is_interactive && cancelled) return MultiQueryProcessingStage::QUERIES_END; if (this_query_begin >= all_queries_end) return MultiQueryProcessingStage::QUERIES_END; // Remove leading empty newlines and other whitespace, because they // are annoying to filter in the query log. This is mostly relevant for // the tests. while (this_query_begin < all_queries_end && isWhitespaceASCII(*this_query_begin)) ++this_query_begin; if (this_query_begin >= all_queries_end) return MultiQueryProcessingStage::QUERIES_END; unsigned max_parser_depth = static_cast(global_context->getSettingsRef().max_parser_depth); unsigned max_parser_backtracks = static_cast(global_context->getSettingsRef().max_parser_backtracks); // If there are only comments left until the end of file, we just // stop. The parser can't handle this situation because it always // expects that there is some query that it can parse. // We can get into this situation because the parser also doesn't // skip the trailing comments after parsing a query. This is because // they may as well be the leading comments for the next query, // and it makes more sense to treat them as such. { Tokens tokens(this_query_begin, all_queries_end); IParser::Pos token_iterator(tokens, max_parser_depth, max_parser_backtracks); if (!token_iterator.isValid()) return MultiQueryProcessingStage::QUERIES_END; } this_query_end = this_query_begin; try { parsed_query = parseQuery(this_query_end, all_queries_end, global_context->getSettingsRef(), /*allow_multi_statements=*/ true); } catch (const Exception & e) { current_exception.reset(e.clone()); return MultiQueryProcessingStage::PARSING_EXCEPTION; } if (!parsed_query) { if (ignore_error) { Tokens tokens(this_query_begin, all_queries_end); IParser::Pos token_iterator(tokens, max_parser_depth, max_parser_backtracks); while (token_iterator->type != TokenType::Semicolon && token_iterator.isValid()) ++token_iterator; this_query_begin = token_iterator->end; return MultiQueryProcessingStage::CONTINUE_PARSING; } return MultiQueryProcessingStage::PARSING_FAILED; } // INSERT queries may have the inserted data in the query text // that follow the query itself, e.g. "insert into t format CSV 1;2". // They need special handling. First of all, here we find where the // inserted data ends. In multi-query mode, it is delimited by a // newline. // The VALUES format needs even more handling - we also allow the // data to be delimited by semicolon. This case is handled later by // the format parser itself. // We can't do multiline INSERTs with inline data, because most // row input formats (e.g. TSV) can't tell when the input stops, // unlike VALUES. auto * insert_ast = parsed_query->as(); const char * query_to_execute_end = this_query_end; if (insert_ast && insert_ast->data) { this_query_end = find_first_symbols<'\n'>(insert_ast->data, all_queries_end); insert_ast->end = this_query_end; query_to_execute_end = isSyncInsertWithData(*insert_ast, global_context) ? insert_ast->data : this_query_end; } query_to_execute = all_queries_text.substr(this_query_begin - all_queries_text.data(), query_to_execute_end - this_query_begin); // Try to include the trailing comment with test hints. It is just // a guess for now, because we don't yet know where the query ends // if it is an INSERT query with inline data. We will do it again // after we have processed the query. But even this guess is // beneficial so that we see proper trailing comments in "echo" and // server log. adjustQueryEnd(this_query_end, all_queries_end, max_parser_depth, max_parser_backtracks); return MultiQueryProcessingStage::EXECUTE_QUERY; } bool ClientBase::executeMultiQuery(const String & all_queries_text) { bool echo_query = echo_queries; { /// disable logs if expects errors TestHint test_hint(all_queries_text); if (test_hint.hasClientErrors() || test_hint.hasServerErrors()) processTextAsSingleQuery("SET send_logs_level = 'fatal'"); } /// Test tags are started with "--" so they are interpreted as comments anyway. /// But if the echo is enabled we have to remove the test tags from `all_queries_text` /// because we don't want test tags to be echoed. size_t test_tags_length = getTestTagsLength(all_queries_text); /// Several queries separated by ';'. /// INSERT data is ended by the end of line, not ';'. /// An exception is VALUES format where we also support semicolon in /// addition to end of line. const char * this_query_begin = all_queries_text.data() + test_tags_length; const char * this_query_end; const char * all_queries_end = all_queries_text.data() + all_queries_text.size(); String full_query; // full_query is the query + inline INSERT data + trailing comments (the latter is our best guess for now). String query_to_execute; ASTPtr parsed_query; std::unique_ptr current_exception; while (true) { auto stage = analyzeMultiQueryText(this_query_begin, this_query_end, all_queries_end, query_to_execute, parsed_query, all_queries_text, current_exception); switch (stage) { case MultiQueryProcessingStage::QUERIES_END: case MultiQueryProcessingStage::PARSING_FAILED: { return true; } case MultiQueryProcessingStage::CONTINUE_PARSING: { continue; } case MultiQueryProcessingStage::PARSING_EXCEPTION: { this_query_end = find_first_symbols<'\n'>(this_query_end, all_queries_end); // Try to find test hint for syntax error. We don't know where // the query ends because we failed to parse it, so we consume // the entire line. TestHint hint(String(this_query_begin, this_query_end - this_query_begin)); if (hint.hasServerErrors()) { // Syntax errors are considered as client errors current_exception->addMessage("\nExpected server error: {}.", hint.serverErrors()); current_exception->rethrow(); } if (!hint.hasExpectedClientError(current_exception->code())) { if (hint.hasClientErrors()) current_exception->addMessage("\nExpected client error: {}.", hint.clientErrors()); current_exception->rethrow(); } /// It's expected syntax error, skip the line this_query_begin = this_query_end; current_exception.reset(); continue; } case MultiQueryProcessingStage::EXECUTE_QUERY: { full_query = all_queries_text.substr(this_query_begin - all_queries_text.data(), this_query_end - this_query_begin); if (query_fuzzer_runs) { if (!processWithFuzzing(full_query)) return false; this_query_begin = this_query_end; continue; } // Now we know for sure where the query ends. // Look for the hint in the text of query + insert data + trailing // comments, e.g. insert into t format CSV 'a' -- { serverError 123 }. // Use the updated query boundaries we just calculated. TestHint test_hint(full_query); // Echo all queries if asked; makes for a more readable reference file. echo_query = test_hint.echoQueries().value_or(echo_query); try { processParsedSingleQuery(full_query, query_to_execute, parsed_query, echo_query, false); } catch (...) { // Surprisingly, this is a client error. A server error would // have been reported without throwing (see onReceiveExceptionFromServer()). client_exception = std::make_unique(getCurrentExceptionMessageAndPattern(print_stack_trace), getCurrentExceptionCode()); have_error = true; } // Check whether the error (or its absence) matches the test hints // (or their absence). bool error_matches_hint = true; if (have_error) { if (test_hint.hasServerErrors()) { if (!server_exception) { error_matches_hint = false; fmt::print(stderr, "Expected server error code '{}' but got no server error (query: {}).\n", test_hint.serverErrors(), full_query); } else if (!test_hint.hasExpectedServerError(server_exception->code())) { error_matches_hint = false; fmt::print(stderr, "Expected server error code: {} but got: {} (query: {}).\n", test_hint.serverErrors(), server_exception->code(), full_query); } } if (test_hint.hasClientErrors()) { if (!client_exception) { error_matches_hint = false; fmt::print(stderr, "Expected client error code '{}' but got no client error (query: {}).\n", test_hint.clientErrors(), full_query); } else if (!test_hint.hasExpectedClientError(client_exception->code())) { error_matches_hint = false; fmt::print(stderr, "Expected client error code '{}' but got '{}' (query: {}).\n", test_hint.clientErrors(), client_exception->code(), full_query); } } if (!test_hint.hasClientErrors() && !test_hint.hasServerErrors()) { // No error was expected but it still occurred. This is the // default case without test hint, doesn't need additional // diagnostics. error_matches_hint = false; } } else { if (test_hint.hasClientErrors()) { error_matches_hint = false; fmt::print(stderr, "The query succeeded but the client error '{}' was expected (query: {}).\n", test_hint.clientErrors(), full_query); } if (test_hint.hasServerErrors()) { error_matches_hint = false; fmt::print(stderr, "The query succeeded but the server error '{}' was expected (query: {}).\n", test_hint.serverErrors(), full_query); } } // If the error is expected, force reconnect and ignore it. if (have_error && error_matches_hint) { client_exception.reset(); server_exception.reset(); have_error = false; if (!connection->checkConnected(connection_parameters.timeouts)) connect(); } // For INSERTs with inline data: use the end of inline data as // reported by the format parser (it is saved in sendData()). // This allows us to handle queries like: // insert into t values (1); select 1 // , where the inline data is delimited by semicolon and not by a // newline. auto * insert_ast = parsed_query->as(); if (insert_ast && isSyncInsertWithData(*insert_ast, global_context)) { this_query_end = insert_ast->end; adjustQueryEnd( this_query_end, all_queries_end, static_cast(global_context->getSettingsRef().max_parser_depth), static_cast(global_context->getSettingsRef().max_parser_backtracks)); } // Report error. if (have_error) processError(full_query); // Stop processing queries if needed. if (have_error && !ignore_error) return is_interactive; this_query_begin = this_query_end; break; } } } } bool ClientBase::processQueryText(const String & text) { auto trimmed_input = trim(text, [](char c) { return isWhitespaceASCII(c) || c == ';'; }); if (exit_strings.end() != exit_strings.find(trimmed_input)) return false; if (trimmed_input.starts_with("\\i")) { size_t skip_prefix_size = std::strlen("\\i"); auto file_name = trim( trimmed_input.substr(skip_prefix_size, trimmed_input.size() - skip_prefix_size), [](char c) { return isWhitespaceASCII(c); }); return processMultiQueryFromFile(file_name); } if (!is_multiquery) { assert(!query_fuzzer_runs); processTextAsSingleQuery(text); return true; } if (query_fuzzer_runs) { processWithFuzzing(text); return true; } return executeMultiQuery(text); } String ClientBase::prompt() const { return prompt_by_server_display_name; } void ClientBase::initQueryIdFormats() { if (!query_id_formats.empty()) return; /// Initialize query_id_formats if any if (getClientConfiguration().has("query_id_formats")) { Poco::Util::AbstractConfiguration::Keys keys; getClientConfiguration().keys("query_id_formats", keys); for (const auto & name : keys) query_id_formats.emplace_back(name + ":", getClientConfiguration().getString("query_id_formats." + name)); } if (query_id_formats.empty()) query_id_formats.emplace_back("Query id:", " {query_id}\n"); } bool ClientBase::addMergeTreeSettings(ASTCreateQuery & ast_create) { if (ast_create.attach || !ast_create.storage || !ast_create.storage->isExtendedStorageDefinition() || !ast_create.storage->engine || ast_create.storage->engine->name.find("MergeTree") == std::string::npos) return false; auto all_changed = cmd_merge_tree_settings.allChanged(); if (all_changed.begin() == all_changed.end()) return false; if (!ast_create.storage->settings) { auto settings_ast = std::make_shared(); settings_ast->is_standalone = false; ast_create.storage->set(ast_create.storage->settings, settings_ast); } auto & storage_settings = *ast_create.storage->settings; bool added_new_setting = false; for (const auto & setting : all_changed) { if (!storage_settings.changes.tryGet(setting.getName())) { storage_settings.changes.emplace_back(setting.getName(), setting.getValue()); added_new_setting = true; } } return added_new_setting; } void ClientBase::runInteractive() { if (getClientConfiguration().has("query_id")) throw Exception(ErrorCodes::BAD_ARGUMENTS, "query_id could be specified only in non-interactive mode"); if (getClientConfiguration().getBool("print-time-to-stderr", false)) throw Exception(ErrorCodes::BAD_ARGUMENTS, "time option could be specified only in non-interactive mode"); initQueryIdFormats(); /// Initialize DateLUT here to avoid counting time spent here as query execution time. const auto local_tz = DateLUT::instance().getTimeZone(); suggest.emplace(); if (load_suggestions) { /// Load suggestion data from the server. if (global_context->getApplicationType() == Context::ApplicationType::CLIENT) suggest->load(global_context, connection_parameters, getClientConfiguration().getInt("suggestion_limit"), wait_for_suggestions_to_load); else if (global_context->getApplicationType() == Context::ApplicationType::LOCAL) suggest->load(global_context, connection_parameters, getClientConfiguration().getInt("suggestion_limit"), wait_for_suggestions_to_load); } if (home_path.empty()) { const char * home_path_cstr = getenv("HOME"); // NOLINT(concurrency-mt-unsafe) if (home_path_cstr) home_path = home_path_cstr; } /// Load command history if present. if (getClientConfiguration().has("history_file")) history_file = getClientConfiguration().getString("history_file"); else { auto * history_file_from_env = getenv("CLICKHOUSE_HISTORY_FILE"); // NOLINT(concurrency-mt-unsafe) 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) { error_stream << getCurrentExceptionMessage(false) << '\n'; } } } LineReader::Patterns query_extenders = {"\\"}; LineReader::Patterns query_delimiters = {";", "\\G", "\\G;"}; char word_break_characters[] = " \t\v\f\a\b\r\n`~!@#$%^&*()-=+[{]}\\|;:'\",<.>/?"; #if USE_REPLXX replxx::Replxx::highlighter_callback_t highlight_callback{}; if (getClientConfiguration().getBool("highlight", true)) highlight_callback = highlight; ReplxxLineReader lr( *suggest, history_file, getClientConfiguration().has("multiline"), query_extenders, query_delimiters, word_break_characters, highlight_callback); #else LineReader lr( history_file, getClientConfiguration().has("multiline"), query_extenders, query_delimiters, word_break_characters); #endif static const std::initializer_list> backslash_aliases = { { "\\l", "SHOW DATABASES" }, { "\\d", "SHOW TABLES" }, { "\\c", "USE" }, }; static const std::initializer_list repeat_last_input_aliases = { ".", /// Vim shortcut "/" /// Oracle SQL Plus shortcut }; String last_input; do { String input; { /// Enable bracketed-paste-mode so that we are able to paste multiline queries as a whole. /// But keep it disabled outside of query input, because it breaks password input /// (e.g. if we need to reconnect and show a password prompt). /// (Alternatively, we could make the password input ignore the control sequences.) lr.enableBracketedPaste(); SCOPE_EXIT({ lr.disableBracketedPaste(); }); input = lr.readLine(prompt(), ":-] "); } if (input.empty()) break; has_vertical_output_suffix = false; if (input.ends_with("\\G") || input.ends_with("\\G;")) { if (input.ends_with("\\G")) input.resize(input.size() - 2); else if (input.ends_with("\\G;")) input.resize(input.size() - 3); has_vertical_output_suffix = true; } for (const auto & [alias, command] : backslash_aliases) { auto it = std::search(input.begin(), input.end(), alias.begin(), alias.end()); if (it != input.end() && std::all_of(input.begin(), it, isWhitespaceASCII)) { it += alias.size(); if (it == input.end() || isWhitespaceASCII(*it)) { String new_input = command; // append the rest of input to the command // for parameters support, e.g. \c db_name -> USE db_name new_input.append(it, input.end()); input = std::move(new_input); break; } } } for (const auto & alias : repeat_last_input_aliases) { if (input == alias) { input = last_input; break; } } if (suggest && suggest->getLastError() == ErrorCodes::USER_SESSION_LIMIT_EXCEEDED) { // If a separate connection loading suggestions failed to open a new session, // use the main session to receive them. suggest->load(*connection, connection_parameters.timeouts, getClientConfiguration().getInt("suggestion_limit"), global_context->getClientInfo()); } try { if (!processQueryText(input)) break; last_input = input; } catch (const Exception & e) { if (e.code() == ErrorCodes::USER_EXPIRED) break; /// We don't need to handle the test hints in the interactive mode. error_stream << "Exception on client:" << std::endl << getExceptionMessage(e, print_stack_trace, true) << std::endl << std::endl; client_exception.reset(e.clone()); } if (client_exception) { /// client_exception may have been set above or elsewhere. /// Client-side exception during query execution can result in the loss of /// sync in the connection protocol. /// So we reconnect and allow to enter the next query. if (!connection->checkConnected(connection_parameters.timeouts)) connect(); } } while (true); if (isNewYearMode()) output_stream << "Happy new year." << std::endl; else if (isChineseNewYearMode(local_tz)) output_stream << "Happy Chinese new year. 春节快乐!" << std::endl; else output_stream << "Bye." << std::endl; } bool ClientBase::processMultiQueryFromFile(const String & file_name) { String queries_from_file; ReadBufferFromFile in(file_name); readStringUntilEOF(queries_from_file, in); if (!getClientConfiguration().has("log_comment")) { Settings settings = global_context->getSettings(); /// NOTE: cannot use even weakly_canonical() since it fails for /dev/stdin due to resolving of "pipe:[X]" settings.log_comment = fs::absolute(fs::path(file_name)); global_context->setSettings(settings); } return executeMultiQuery(queries_from_file); } void ClientBase::runNonInteractive() { if (delayed_interactive) initQueryIdFormats(); if (!queries_files.empty()) { for (const auto & queries_file : queries_files) { for (const auto & interleave_file : interleave_queries_files) if (!processMultiQueryFromFile(interleave_file)) return; if (!processMultiQueryFromFile(queries_file)) return; } return; } if (!queries.empty()) { for (const auto & query : queries) { if (query_fuzzer_runs) { if (!processWithFuzzing(query)) return; } else { if (!processQueryText(query)) return; } } } else { /// If 'query' parameter is not set, read a query from stdin. /// The query is read entirely into memory (streaming is disabled). ReadBufferFromFileDescriptor in(STDIN_FILENO); String text; readStringUntilEOF(text, in); if (query_fuzzer_runs) processWithFuzzing(text); else processQueryText(text); } } #if defined(FUZZING_MODE) extern "C" int LLVMFuzzerRunDriver(int * argc, char *** argv, int (*callback)(const uint8_t * data, size_t size)); ClientBase * app; void ClientBase::runLibFuzzer() { app = this; std::vector fuzzer_args_holder; if (const char * fuzzer_args_env = getenv("FUZZER_ARGS")) // NOLINT(concurrency-mt-unsafe) boost::split(fuzzer_args_holder, fuzzer_args_env, isWhitespaceASCII, boost::token_compress_on); std::vector fuzzer_args; fuzzer_args.push_back(argv0); for (auto & arg : fuzzer_args_holder) fuzzer_args.emplace_back(arg.data()); int fuzzer_argc = fuzzer_args.size(); char ** fuzzer_argv = fuzzer_args.data(); LLVMFuzzerRunDriver(&fuzzer_argc, &fuzzer_argv, [](const uint8_t * data, size_t size) { try { String query(reinterpret_cast(data), size); app->processQueryText(query); } catch (...) { return -1; } return 0; }); } #else void ClientBase::runLibFuzzer() {} #endif void ClientBase::clearTerminal() { /// Clear from cursor until end of screen. /// It is needed if garbage is left in terminal. /// Show cursor. It can be left hidden by invocation of previous programs. /// A test for this feature: perl -e 'print "x"x100000'; echo -ne '\033[0;0H\033[?25l'; clickhouse-client output_stream << "\033[0J" "\033[?25h"; } void ClientBase::showClientVersion() { output_stream << VERSION_NAME << " " + getName() + " version " << VERSION_STRING << VERSION_OFFICIAL << "." << std::endl; } namespace { /// Define transparent hash to we can use /// std::string_view with the containers struct TransparentStringHash { using is_transparent = void; size_t operator()(std::string_view txt) const { return std::hash{}(txt); } }; /* * This functor is used to parse command line arguments and replace dashes with underscores, * allowing options to be specified using either dashes or underscores. */ class OptionsAliasParser { public: explicit OptionsAliasParser(const boost::program_options::options_description& options) { options_names.reserve(options.options().size()); for (const auto& option : options.options()) options_names.insert(option->long_name()); } /* * Parses arguments by replacing dashes with underscores, and matches the resulting name with known options * Implements boost::program_options::ext_parser logic */ std::pair operator()(const std::string & token) const { if (!token.starts_with("--")) return {}; std::string arg = token.substr(2); // divide token by '=' to separate key and value if options style=long_allow_adjacent auto pos_eq = arg.find('='); std::string key = arg.substr(0, pos_eq); if (options_names.contains(key)) // option does not require any changes, because it is already correct return {}; std::replace(key.begin(), key.end(), '-', '_'); if (!options_names.contains(key)) // after replacing '-' with '_' argument is still unknown return {}; std::string value; if (pos_eq != std::string::npos && pos_eq < arg.size()) value = arg.substr(pos_eq + 1); return {key, value}; } private: std::unordered_set options_names; }; } /// Enable optimizations even in debug builds because otherwise options parsing becomes extremely slow affecting .sh tests #if defined(__clang__) #pragma clang optimize on #endif void ClientBase::parseAndCheckOptions(OptionsDescription & options_description, po::variables_map & options, Arguments & arguments) { if (allow_repeated_settings) addProgramOptionsAsMultitokens(cmd_settings, options_description.main_description.value()); else addProgramOptions(cmd_settings, options_description.main_description.value()); if (allow_merge_tree_settings) { /// Add merge tree settings manually, because names of some settings /// may clash. Query settings have higher priority and we just /// skip ambiguous merge tree settings. auto & main_options = options_description.main_description.value(); std::unordered_set> main_option_names; for (const auto & option : main_options.options()) main_option_names.insert(option->long_name()); for (const auto & setting : cmd_merge_tree_settings.all()) { const auto add_setting = [&](const std::string_view name) { if (auto it = main_option_names.find(name); it != main_option_names.end()) return; if (allow_repeated_settings) addProgramOptionAsMultitoken(cmd_merge_tree_settings, main_options, name, setting); else addProgramOption(cmd_merge_tree_settings, main_options, name, setting); }; const auto & setting_name = setting.getName(); add_setting(setting_name); const auto & settings_to_aliases = MergeTreeSettings::Traits::settingsToAliases(); if (auto it = settings_to_aliases.find(setting_name); it != settings_to_aliases.end()) { for (const auto alias : it->second) { add_setting(alias); } } } } /// Parse main commandline options. auto parser = po::command_line_parser(arguments) .options(options_description.main_description.value()) .extra_parser(OptionsAliasParser(options_description.main_description.value())) .allow_unregistered(); po::parsed_options parsed = parser.run(); /// Check unrecognized options without positional options. auto unrecognized_options = po::collect_unrecognized(parsed.options, po::collect_unrecognized_mode::exclude_positional); if (!unrecognized_options.empty()) { auto hints = this->getHints(unrecognized_options[0]); if (!hints.empty()) throw Exception(ErrorCodes::UNRECOGNIZED_ARGUMENTS, "Unrecognized option '{}'. Maybe you meant {}", unrecognized_options[0], toString(hints)); throw Exception(ErrorCodes::UNRECOGNIZED_ARGUMENTS, "Unrecognized option '{}'", unrecognized_options[0]); } /// Check positional options. for (const auto & op : parsed.options) { if (!op.unregistered && op.string_key.empty() && !op.original_tokens[0].starts_with("--") && !op.original_tokens[0].empty() && !op.value.empty()) { /// Two special cases for better usability: /// - if the option contains a whitespace, it might be a query: clickhouse "SELECT 1" /// These are relevant for interactive usage - user-friendly, but questionable in general. /// In case of ambiguity or for scripts, prefer using proper options. const auto & token = op.original_tokens[0]; po::variable_value value(boost::any(op.value), false); const char * option; if (token.contains(' ')) option = "query"; else throw Exception(ErrorCodes::BAD_ARGUMENTS, "Positional option `{}` is not supported.", token); if (!options.emplace(option, value).second) throw Exception(ErrorCodes::BAD_ARGUMENTS, "Positional option `{}` is not supported.", token); } } po::store(parsed, options); } void ClientBase::init(int argc, char ** argv) { namespace po = boost::program_options; /// Don't parse options with Poco library, we prefer neat boost::program_options. stopOptionsProcessing(); stdin_is_a_tty = isatty(STDIN_FILENO); stdout_is_a_tty = isatty(STDOUT_FILENO); stderr_is_a_tty = isatty(STDERR_FILENO); terminal_width = getTerminalWidth(); std::vector external_tables_arguments; Arguments common_arguments = {""}; /// 0th argument is ignored. std::vector hosts_and_ports_arguments; if (argc) argv0 = argv[0]; readArguments(argc, argv, common_arguments, external_tables_arguments, hosts_and_ports_arguments); /// Support for Unicode dashes /// Interpret Unicode dashes as default double-hyphen for (auto & arg : common_arguments) { // replace em-dash(U+2014) boost::replace_all(arg, "—", "--"); // replace en-dash(U+2013) boost::replace_all(arg, "–", "--"); // replace mathematical minus(U+2212) boost::replace_all(arg, "−", "--"); } OptionsDescription options_description; options_description.main_description.emplace(createOptionsDescription("Main options", terminal_width)); /// Common options for clickhouse-client and clickhouse-local. options_description.main_description->add_options() ("help", "print usage summary, combine with --verbose to display all options") ("verbose", "print query and other debugging info") ("version,V", "print version information and exit") ("version-clean", "print version in machine-readable format and exit") ("config-file,C", po::value(), "config-file path") ("query,q", po::value>()->multitoken(), R"(query; can be specified multiple times (--query "SELECT 1" --query "SELECT 2"...))") ("queries-file", po::value>()->multitoken(), "file path with queries to execute; multiple files can be specified (--queries-file file1 file2...)") ("multiquery,n", "If specified, multiple queries separated by semicolons can be listed after --query. For convenience, it is also possible to omit --query and pass the queries directly after --multiquery.") ("multiline,m", "If specified, allow multiline queries (do not send the query on Enter)") ("database,d", po::value(), "database") ("query_kind", po::value()->default_value("initial_query"), "One of initial_query/secondary_query/no_query") ("query_id", po::value(), "query_id") ("history_file", po::value(), "path to history file") ("stage", po::value()->default_value("complete"), "Request query processing up to specified stage: complete,fetch_columns,with_mergeable_state,with_mergeable_state_after_aggregation,with_mergeable_state_after_aggregation_and_limit") ("progress", po::value()->implicit_value(ProgressOption::TTY, "tty")->default_value(ProgressOption::DEFAULT, "default"), "Print progress of queries execution - to TTY: tty|on|1|true|yes; to STDERR non-interactive mode: err; OFF: off|0|false|no; DEFAULT - interactive to TTY, non-interactive is off") ("disable_suggestion,A", "Disable loading suggestion data. Note that suggestion data is loaded asynchronously through a second connection to ClickHouse server. Also it is reasonable to disable suggestion if you want to paste a query with TAB characters. Shorthand option -A is for those who get used to mysql client.") ("wait_for_suggestions_to_load", "Load suggestion data synchonously.") ("time,t", "print query execution time to stderr in non-interactive mode (for benchmarks)") ("echo", "in batch mode, print query before execution") ("log-level", po::value(), "log level") ("server_logs_file", po::value(), "put server logs into specified file") ("suggestion_limit", po::value()->default_value(10000), "Suggestion limit for how many databases, tables and columns to fetch.") ("format,f", po::value(), "default output format (and input format for clickhouse-local)") ("output-format", po::value(), "default output format (this option has preference over --format)") ("vertical,E", "vertical output format, same as --format=Vertical or FORMAT Vertical or \\G at end of command") ("highlight", po::value()->default_value(true), "enable or disable basic syntax highlight in interactive command line") ("ignore-error", "do not stop processing in multiquery mode") ("stacktrace", "print stack traces of exceptions") ("hardware-utilization", "print hardware utilization information in progress bar") ("print-profile-events", po::value(&profile_events.print)->zero_tokens(), "Printing ProfileEvents packets") ("profile-events-delay-ms", po::value()->default_value(profile_events.delay_ms), "Delay between printing `ProfileEvents` packets (-1 - print only totals, 0 - print every single packet)") ("processed-rows", "print the number of locally processed rows") ("interactive", "Process queries-file or --query query and start interactive mode") ("pager", po::value(), "Pipe all output into this command (less or similar)") ("max_memory_usage_in_client", po::value(), "Set memory limit in client/local server") ("fuzzer-args", po::value(), "Command line arguments for the LLVM's libFuzzer driver. Only relevant if the application is compiled with libFuzzer.") ("client_logs_file", po::value(), "Path to a file for writing client logs. Currently we only have fatal logs (when the client crashes)") ; addOptions(options_description); OptionsDescription options_description_non_verbose = options_description; auto getter = [](const auto & op) { String op_long_name = op->long_name(); return "--" + String(op_long_name); }; if (options_description.main_description) { const auto & main_options = options_description.main_description->options(); std::transform(main_options.begin(), main_options.end(), std::back_inserter(cmd_options), getter); } if (options_description.external_description) { const auto & external_options = options_description.external_description->options(); std::transform(external_options.begin(), external_options.end(), std::back_inserter(cmd_options), getter); } po::variables_map options; parseAndCheckOptions(options_description, options, common_arguments); po::notify(options); if (options.count("version") || options.count("V")) { showClientVersion(); exit(0); // NOLINT(concurrency-mt-unsafe) } if (options.count("version-clean")) { output_stream << VERSION_STRING; exit(0); // NOLINT(concurrency-mt-unsafe) } if (options.count("verbose")) getClientConfiguration().setBool("verbose", true); /// Output of help message. if (options.count("help") || (options.count("host") && options["host"].as() == "elp")) /// If user writes -help instead of --help. { if (getClientConfiguration().getBool("verbose", false)) printHelpMessage(options_description, true); else printHelpMessage(options_description_non_verbose, false); exit(0); // NOLINT(concurrency-mt-unsafe) } /// Common options for clickhouse-client and clickhouse-local. /// Output execution time to stderr in batch mode. if (options.count("time")) getClientConfiguration().setBool("print-time-to-stderr", true); if (options.count("query")) queries = options["query"].as>(); if (options.count("query_id")) getClientConfiguration().setString("query_id", options["query_id"].as()); if (options.count("database")) getClientConfiguration().setString("database", options["database"].as()); if (options.count("config-file")) getClientConfiguration().setString("config-file", options["config-file"].as()); if (options.count("queries-file")) queries_files = options["queries-file"].as>(); if (options.count("multiline")) getClientConfiguration().setBool("multiline", true); if (options.count("multiquery")) getClientConfiguration().setBool("multiquery", true); if (options.count("ignore-error")) getClientConfiguration().setBool("ignore-error", true); if (options.count("format")) getClientConfiguration().setString("format", options["format"].as()); if (options.count("output-format")) getClientConfiguration().setString("output-format", options["output-format"].as()); if (options.count("vertical")) getClientConfiguration().setBool("vertical", true); if (options.count("stacktrace")) getClientConfiguration().setBool("stacktrace", true); if (options.count("print-profile-events")) getClientConfiguration().setBool("print-profile-events", true); if (options.count("profile-events-delay-ms")) getClientConfiguration().setUInt64("profile-events-delay-ms", options["profile-events-delay-ms"].as()); /// Whether to print the number of processed rows at if (options.count("processed-rows")) getClientConfiguration().setBool("print-num-processed-rows", true); if (options.count("progress")) { switch (options["progress"].as()) { case DEFAULT: getClientConfiguration().setString("progress", "default"); break; case OFF: getClientConfiguration().setString("progress", "off"); break; case TTY: getClientConfiguration().setString("progress", "tty"); break; case ERR: getClientConfiguration().setString("progress", "err"); break; } } if (options.count("echo")) getClientConfiguration().setBool("echo", true); if (options.count("disable_suggestion")) getClientConfiguration().setBool("disable_suggestion", true); if (options.count("wait_for_suggestions_to_load")) getClientConfiguration().setBool("wait_for_suggestions_to_load", true); if (options.count("suggestion_limit")) getClientConfiguration().setInt("suggestion_limit", options["suggestion_limit"].as()); if (options.count("highlight")) getClientConfiguration().setBool("highlight", options["highlight"].as()); if (options.count("history_file")) getClientConfiguration().setString("history_file", options["history_file"].as()); if (options.count("interactive")) getClientConfiguration().setBool("interactive", true); if (options.count("pager")) getClientConfiguration().setString("pager", options["pager"].as()); if (options.count("log-level")) Poco::Logger::root().setLevel(options["log-level"].as()); if (options.count("server_logs_file")) server_logs_file = options["server_logs_file"].as(); query_processing_stage = QueryProcessingStage::fromString(options["stage"].as()); query_kind = parseQueryKind(options["query_kind"].as()); profile_events.print = options.count("print-profile-events"); profile_events.delay_ms = options["profile-events-delay-ms"].as(); processOptions(options_description, options, external_tables_arguments, hosts_and_ports_arguments); { std::unordered_set alias_names; alias_names.reserve(options_description.main_description->options().size()); for (const auto& option : options_description.main_description->options()) alias_names.insert(option->long_name()); argsToConfig(common_arguments, getClientConfiguration(), 100, &alias_names); } clearPasswordFromCommandLine(argc, argv); /// Limit on total memory usage std::string max_client_memory_usage = getClientConfiguration().getString("max_memory_usage_in_client", "0" /*default value*/); if (max_client_memory_usage != "0") { UInt64 max_client_memory_usage_int = parseWithSizeSuffix(max_client_memory_usage.c_str(), max_client_memory_usage.length()); total_memory_tracker.setHardLimit(max_client_memory_usage_int); total_memory_tracker.setDescription("(total)"); total_memory_tracker.setMetric(CurrentMetrics::MemoryTracking); } /// Print stacktrace in case of crash HandledSignals::instance().setupTerminateHandler(); HandledSignals::instance().setupCommonDeadlySignalHandlers(); /// We don't setup signal handlers for SIGINT, SIGQUIT, SIGTERM because we don't /// have an option for client to shutdown gracefully. fatal_channel_ptr = new Poco::SplitterChannel; fatal_console_channel_ptr = new Poco::ConsoleChannel; fatal_channel_ptr->addChannel(fatal_console_channel_ptr); if (options.count("client_logs_file")) { fatal_file_channel_ptr = new Poco::SimpleFileChannel(options["client_logs_file"].as()); fatal_channel_ptr->addChannel(fatal_file_channel_ptr); } fatal_log = createLogger("ClientBase", fatal_channel_ptr.get(), Poco::Message::PRIO_FATAL); signal_listener = std::make_unique(nullptr, fatal_log); signal_listener_thread.start(*signal_listener); } }