2012-05-08 05:42:05 +00:00
|
|
|
|
#include <unistd.h>
|
2012-03-25 03:47:13 +00:00
|
|
|
|
#include <stdlib.h>
|
2012-03-26 02:48:08 +00:00
|
|
|
|
#include <fcntl.h>
|
2012-05-09 08:16:09 +00:00
|
|
|
|
#include <signal.h>
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
|
|
|
|
#include <readline/readline.h>
|
2012-03-25 07:52:31 +00:00
|
|
|
|
#include <readline/history.h>
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
|
|
|
|
#include <iostream>
|
2012-03-26 02:48:08 +00:00
|
|
|
|
#include <fstream>
|
|
|
|
|
#include <iomanip>
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
2014-01-08 16:33:28 +00:00
|
|
|
|
#include <unordered_set>
|
2014-12-05 13:10:55 +00:00
|
|
|
|
#include <algorithm>
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
2014-03-05 13:48:45 +00:00
|
|
|
|
#include <boost/program_options.hpp>
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
|
|
|
|
#include <Poco/File.h>
|
|
|
|
|
#include <Poco/SharedPtr.h>
|
|
|
|
|
#include <Poco/Util/Application.h>
|
|
|
|
|
|
2015-09-29 19:19:54 +00:00
|
|
|
|
#include <common/Revision.h>
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
2015-10-05 00:44:40 +00:00
|
|
|
|
#include <DB/Common/Stopwatch.h>
|
2012-03-26 02:48:08 +00:00
|
|
|
|
|
2015-10-05 01:35:28 +00:00
|
|
|
|
#include <DB/Common/Exception.h>
|
2012-03-25 03:47:13 +00:00
|
|
|
|
#include <DB/Core/Types.h>
|
2012-05-09 13:12:38 +00:00
|
|
|
|
#include <DB/Core/QueryProcessingStage.h>
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
2012-05-08 11:19:00 +00:00
|
|
|
|
#include <DB/IO/ReadBufferFromFileDescriptor.h>
|
2012-03-25 07:52:31 +00:00
|
|
|
|
#include <DB/IO/WriteBufferFromFileDescriptor.h>
|
2013-02-01 18:55:31 +00:00
|
|
|
|
#include <DB/IO/WriteBufferFromString.h>
|
2012-03-25 03:47:13 +00:00
|
|
|
|
#include <DB/IO/ReadHelpers.h>
|
|
|
|
|
#include <DB/IO/WriteHelpers.h>
|
2013-02-01 18:55:31 +00:00
|
|
|
|
#include <DB/IO/copyData.h>
|
2014-03-12 13:14:16 +00:00
|
|
|
|
#include <DB/IO/ReadBufferFromIStream.h>
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
2012-09-24 07:04:04 +00:00
|
|
|
|
#include <DB/DataStreams/AsynchronousBlockInputStream.h>
|
2015-03-29 07:13:38 +00:00
|
|
|
|
#include <DB/DataStreams/BlockInputStreamFromRowInputStream.h>
|
|
|
|
|
#include <DB/DataStreams/TabSeparatedRowInputStream.h>
|
2012-09-24 07:04:04 +00:00
|
|
|
|
|
2012-03-26 04:17:17 +00:00
|
|
|
|
#include <DB/Parsers/ParserQuery.h>
|
2015-03-02 01:39:42 +00:00
|
|
|
|
#include <DB/Parsers/ASTSetQuery.h>
|
|
|
|
|
#include <DB/Parsers/ASTUseQuery.h>
|
|
|
|
|
#include <DB/Parsers/ASTInsertQuery.h>
|
|
|
|
|
#include <DB/Parsers/ASTSelectQuery.h>
|
|
|
|
|
#include <DB/Parsers/ASTQueryWithOutput.h>
|
|
|
|
|
#include <DB/Parsers/ASTIdentifier.h>
|
2012-03-26 04:17:17 +00:00
|
|
|
|
#include <DB/Parsers/formatAST.h>
|
2015-04-11 03:10:23 +00:00
|
|
|
|
#include <DB/Parsers/parseQuery.h>
|
2012-03-26 04:17:17 +00:00
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
#include <DB/Interpreters/Context.h>
|
|
|
|
|
|
2012-05-16 18:03:00 +00:00
|
|
|
|
#include <DB/Client/Connection.h>
|
|
|
|
|
|
2014-03-10 09:33:18 +00:00
|
|
|
|
#include "InterruptListener.h"
|
|
|
|
|
|
2014-04-08 13:43:20 +00:00
|
|
|
|
#include <DB/Common/ExternalTable.h>
|
2014-10-25 20:27:37 +00:00
|
|
|
|
#include <DB/Common/UnicodeBar.h>
|
|
|
|
|
#include <DB/Common/formatReadable.h>
|
2015-03-29 08:44:04 +00:00
|
|
|
|
#include <DB/Columns/ColumnString.h>
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
2015-10-05 01:26:43 +00:00
|
|
|
|
#include <DB/Common/NetException.h>
|
2014-08-15 00:08:15 +00:00
|
|
|
|
|
|
|
|
|
/// http://en.wikipedia.org/wiki/ANSI_escape_code
|
|
|
|
|
#define SAVE_CURSOR_POSITION "\033[s"
|
|
|
|
|
#define RESTORE_CURSOR_POSITION "\033[u"
|
|
|
|
|
#define CLEAR_TO_END_OF_LINE "\033[K"
|
|
|
|
|
/// Эти коды, возможно, поддерживаются не везде.
|
|
|
|
|
#define DISABLE_LINE_WRAPPING "\033[?7l"
|
|
|
|
|
#define ENABLE_LINE_WRAPPING "\033[?7h"
|
|
|
|
|
|
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
/** Клиент командной строки СУБД ClickHouse.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace DB
|
|
|
|
|
{
|
|
|
|
|
|
2012-03-25 07:52:31 +00:00
|
|
|
|
using Poco::SharedPtr;
|
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
class Client : public Poco::Util::Application
|
|
|
|
|
{
|
|
|
|
|
public:
|
2014-08-15 00:08:15 +00:00
|
|
|
|
Client() {}
|
2014-01-08 16:33:28 +00:00
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
private:
|
2014-01-08 16:33:28 +00:00
|
|
|
|
typedef std::unordered_set<String> StringSet;
|
2014-05-19 18:50:50 +00:00
|
|
|
|
StringSet exit_strings {
|
|
|
|
|
"exit", "quit", "logout",
|
|
|
|
|
"учше", "йгше", "дщпщге",
|
|
|
|
|
"exit;", "quit;", "logout;",
|
|
|
|
|
"учшеж", "йгшеж", "дщпщгеж",
|
|
|
|
|
"q", "й", "\\q", "\\Q", "\\й", "\\Й", ":q", "Жй"
|
|
|
|
|
};
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
2014-08-15 00:08:15 +00:00
|
|
|
|
bool is_interactive = true; /// Использовать readline интерфейс или batch режим.
|
2015-06-28 07:05:42 +00:00
|
|
|
|
bool need_render_progress = true; /// Рисовать прогресс выполнения запроса.
|
2015-03-27 18:43:09 +00:00
|
|
|
|
bool print_time_to_stderr = false; /// В неинтерактивном режиме, выводить время выполнения в stderr.
|
2014-08-15 00:08:15 +00:00
|
|
|
|
bool stdin_is_not_tty = false; /// stdin - не терминал.
|
2012-05-08 05:42:05 +00:00
|
|
|
|
|
2014-10-25 20:27:37 +00:00
|
|
|
|
winsize terminal_size {}; /// Размер терминала - для вывода прогресс-бара.
|
|
|
|
|
|
2012-05-16 18:03:00 +00:00
|
|
|
|
SharedPtr<Connection> connection; /// Соединение с БД.
|
2012-03-25 03:47:13 +00:00
|
|
|
|
String query; /// Текущий запрос.
|
|
|
|
|
|
|
|
|
|
String format; /// Формат вывода результата в консоль.
|
2014-08-15 00:08:15 +00:00
|
|
|
|
size_t format_max_block_size = 0; /// Максимальный размер блока при выводе в консоль.
|
2012-05-08 11:19:00 +00:00
|
|
|
|
String insert_format; /// Формат данных для INSERT-а при чтении их из stdin в batch режиме
|
2014-08-15 00:08:15 +00:00
|
|
|
|
size_t insert_format_max_block_size = 0; /// Максимальный размер блока при чтении данных INSERT-а.
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
2014-12-19 23:02:18 +00:00
|
|
|
|
bool has_vertical_output_suffix = false; /// \G указан в конце команды?
|
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
Context context;
|
|
|
|
|
|
2012-05-08 11:19:00 +00:00
|
|
|
|
/// Чтение из stdin для batch режима
|
2014-08-15 00:08:15 +00:00
|
|
|
|
ReadBufferFromFileDescriptor std_in {STDIN_FILENO};
|
2012-05-08 11:19:00 +00:00
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
/// Вывод в консоль
|
2014-08-15 00:08:15 +00:00
|
|
|
|
WriteBufferFromFileDescriptor std_out {STDOUT_FILENO};
|
2012-03-25 03:47:13 +00:00
|
|
|
|
BlockOutputStreamPtr block_std_out;
|
2012-03-26 02:48:08 +00:00
|
|
|
|
|
|
|
|
|
String home_path;
|
2014-04-18 14:32:30 +00:00
|
|
|
|
|
|
|
|
|
String current_profile;
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2012-03-26 02:48:08 +00:00
|
|
|
|
/// Путь к файлу истории команд.
|
|
|
|
|
String history_file;
|
2012-03-26 04:17:17 +00:00
|
|
|
|
|
2012-05-21 06:49:05 +00:00
|
|
|
|
/// Строк прочитано или записано.
|
2014-08-15 00:08:15 +00:00
|
|
|
|
size_t processed_rows = 0;
|
2012-03-26 04:17:17 +00:00
|
|
|
|
|
|
|
|
|
/// Распарсенный запрос. Оттуда берутся некоторые настройки (формат).
|
|
|
|
|
ASTPtr parsed_query;
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2015-02-27 20:35:26 +00:00
|
|
|
|
/// Последнее полученное от сервера исключение. Для кода возврата в неинтерактивном режиме.
|
2015-10-05 06:12:54 +00:00
|
|
|
|
Poco::SharedPtr<DB::Exception> last_exception;
|
2012-05-09 08:16:09 +00:00
|
|
|
|
|
2015-02-27 20:35:26 +00:00
|
|
|
|
/// Было ли в последнем запросе исключение.
|
|
|
|
|
bool got_exception = false;
|
|
|
|
|
|
2012-05-10 07:47:13 +00:00
|
|
|
|
Stopwatch watch;
|
|
|
|
|
|
2014-10-25 18:33:52 +00:00
|
|
|
|
/// С сервера периодически приходит информация, о том, сколько прочитано данных за прошедшее время.
|
|
|
|
|
Progress progress;
|
2014-10-25 20:27:37 +00:00
|
|
|
|
bool show_progress_bar = false;
|
2014-10-25 18:33:52 +00:00
|
|
|
|
|
2014-08-15 00:08:15 +00:00
|
|
|
|
size_t written_progress_chars = 0;
|
|
|
|
|
bool written_first_block = false;
|
2012-05-09 15:15:45 +00:00
|
|
|
|
|
2014-03-06 11:37:30 +00:00
|
|
|
|
/// Информация о внешних таблицах
|
2014-04-22 18:35:40 +00:00
|
|
|
|
std::list<ExternalTable> external_tables;
|
2014-03-06 11:37:30 +00:00
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
|
|
|
|
void initialize(Poco::Util::Application & self)
|
|
|
|
|
{
|
|
|
|
|
Poco::Util::Application::initialize(self);
|
|
|
|
|
|
2012-03-26 02:48:08 +00:00
|
|
|
|
const char * home_path_cstr = getenv("HOME");
|
2014-02-05 13:40:20 +00:00
|
|
|
|
if (home_path_cstr)
|
2012-03-26 02:48:08 +00:00
|
|
|
|
home_path = home_path_cstr;
|
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
if (config().has("config-file"))
|
|
|
|
|
loadConfiguration(config().getString("config-file"));
|
|
|
|
|
else if (Poco::File("./clickhouse-client.xml").exists())
|
|
|
|
|
loadConfiguration("./clickhouse-client.xml");
|
2014-02-05 13:40:20 +00:00
|
|
|
|
else if (!home_path.empty() && Poco::File(home_path + "/.clickhouse-client/config.xml").exists())
|
2012-03-26 02:48:08 +00:00
|
|
|
|
loadConfiguration(home_path + "/.clickhouse-client/config.xml");
|
2012-03-25 03:47:13 +00:00
|
|
|
|
else if (Poco::File("/etc/clickhouse-client/config.xml").exists())
|
|
|
|
|
loadConfiguration("/etc/clickhouse-client/config.xml");
|
2014-04-15 13:36:32 +00:00
|
|
|
|
|
|
|
|
|
/// settings и limits могли так же быть указаны в кофигурационном файле, но уже записанные настройки имеют больший приоритет.
|
|
|
|
|
#define EXTRACT_SETTING(TYPE, NAME, DEFAULT) \
|
|
|
|
|
if (config().has(#NAME) && !context.getSettingsRef().NAME.changed) \
|
|
|
|
|
context.setSetting(#NAME, config().getString(#NAME));
|
|
|
|
|
APPLY_FOR_SETTINGS(EXTRACT_SETTING)
|
|
|
|
|
#undef EXTRACT_SETTING
|
|
|
|
|
|
|
|
|
|
#define EXTRACT_LIMIT(TYPE, NAME, DEFAULT) \
|
|
|
|
|
if (config().has(#NAME) && !context.getSettingsRef().limits.NAME.changed) \
|
|
|
|
|
context.setSetting(#NAME, config().getString(#NAME));
|
|
|
|
|
APPLY_FOR_LIMITS(EXTRACT_LIMIT)
|
|
|
|
|
#undef EXTRACT_LIMIT
|
2012-03-25 03:47:13 +00:00
|
|
|
|
}
|
2012-03-26 02:48:08 +00:00
|
|
|
|
|
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
int main(const std::vector<std::string> & args)
|
|
|
|
|
{
|
2012-03-26 02:48:08 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return mainImpl(args);
|
|
|
|
|
}
|
2013-10-26 03:20:51 +00:00
|
|
|
|
catch (const Exception & e)
|
2012-03-26 02:48:08 +00:00
|
|
|
|
{
|
2015-04-03 19:06:16 +00:00
|
|
|
|
bool print_stack_trace = config().getBool("stacktrace", false);
|
|
|
|
|
|
2013-08-10 09:04:45 +00:00
|
|
|
|
std::string text = e.displayText();
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2015-04-03 19:06:16 +00:00
|
|
|
|
/** Если эксепшен пришёл с сервера, то стек трейс будет расположен внутри текста.
|
|
|
|
|
* Если эксепшен на клиенте, то стек трейс расположен отдельно.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
auto embedded_stack_trace_pos = text.find("Stack trace");
|
|
|
|
|
if (std::string::npos != embedded_stack_trace_pos && !print_stack_trace)
|
|
|
|
|
text.resize(embedded_stack_trace_pos);
|
|
|
|
|
|
2013-08-10 09:04:45 +00:00
|
|
|
|
std::cerr << "Code: " << e.code() << ". " << text << std::endl << std::endl;
|
|
|
|
|
|
|
|
|
|
/// Если есть стек-трейс на сервере, то не будем писать стек-трейс на клиенте.
|
2014-10-03 19:09:15 +00:00
|
|
|
|
/// Также не будем писать стек-трейс в случае сетевых ошибок.
|
2015-04-03 19:06:16 +00:00
|
|
|
|
if (print_stack_trace
|
|
|
|
|
&& e.code() != ErrorCodes::NETWORK_ERROR
|
|
|
|
|
&& std::string::npos == embedded_stack_trace_pos)
|
2014-10-03 19:09:15 +00:00
|
|
|
|
{
|
2013-08-10 09:04:45 +00:00
|
|
|
|
std::cerr << "Stack trace:" << std::endl
|
|
|
|
|
<< e.getStackTrace().toString();
|
2014-10-03 19:09:15 +00:00
|
|
|
|
}
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2014-04-09 19:21:50 +00:00
|
|
|
|
/// В случае нулевого кода исключения, надо всё-равно вернуть ненулевой код возврата.
|
|
|
|
|
return e.code() ? e.code() : -1;
|
2012-03-26 02:48:08 +00:00
|
|
|
|
}
|
|
|
|
|
catch (const Poco::Exception & e)
|
|
|
|
|
{
|
|
|
|
|
std::cerr << "Poco::Exception: " << e.displayText() << std::endl;
|
2013-02-21 10:56:04 +00:00
|
|
|
|
return ErrorCodes::POCO_EXCEPTION;
|
2012-03-26 02:48:08 +00:00
|
|
|
|
}
|
|
|
|
|
catch (const std::exception & e)
|
|
|
|
|
{
|
|
|
|
|
std::cerr << "std::exception: " << e.what() << std::endl;
|
2013-02-21 10:40:18 +00:00
|
|
|
|
return ErrorCodes::STD_EXCEPTION;
|
2012-03-26 02:48:08 +00:00
|
|
|
|
}
|
|
|
|
|
catch (...)
|
|
|
|
|
{
|
|
|
|
|
std::cerr << "Unknown exception" << std::endl;
|
2013-02-21 10:40:18 +00:00
|
|
|
|
return ErrorCodes::UNKNOWN_EXCEPTION;
|
2012-03-26 02:48:08 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2012-03-26 02:48:08 +00:00
|
|
|
|
|
2015-12-13 09:19:32 +00:00
|
|
|
|
/// Стоит ли сделать хоть что-нибудь ради праздника.
|
|
|
|
|
bool isNewYearMode()
|
|
|
|
|
{
|
|
|
|
|
time_t current_time = time(0);
|
|
|
|
|
|
|
|
|
|
/// Плохо быть навязчивым.
|
|
|
|
|
if (current_time % 3 != 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
mysqlxx::Date now(current_time);
|
|
|
|
|
return (now.month() == 12 && now.day() >= 20)
|
|
|
|
|
|| (now.month() == 1 && now.day() <= 5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-03-26 02:48:08 +00:00
|
|
|
|
int mainImpl(const std::vector<std::string> & args)
|
|
|
|
|
{
|
2012-05-08 05:42:05 +00:00
|
|
|
|
/** Будем работать в batch режиме, если выполнено одно из следующих условий:
|
|
|
|
|
* - задан параметр -e (--query)
|
|
|
|
|
* (в этом случае - запрос или несколько запросов берём оттуда;
|
|
|
|
|
* а если при этом stdin не терминал, то берём оттуда данные для INSERT-а первого запроса).
|
|
|
|
|
* - stdin - не терминал (в этом случае, считываем оттуда запросы);
|
|
|
|
|
*/
|
|
|
|
|
stdin_is_not_tty = !isatty(STDIN_FILENO);
|
|
|
|
|
if (stdin_is_not_tty || config().has("query"))
|
|
|
|
|
is_interactive = false;
|
2012-05-09 08:16:09 +00:00
|
|
|
|
|
2012-03-26 04:17:17 +00:00
|
|
|
|
std::cout << std::fixed << std::setprecision(3);
|
2012-05-08 05:42:05 +00:00
|
|
|
|
std::cerr << std::fixed << std::setprecision(3);
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2012-05-08 05:42:05 +00:00
|
|
|
|
if (is_interactive)
|
|
|
|
|
std::cout << "ClickHouse client version " << DBMS_VERSION_MAJOR
|
|
|
|
|
<< "." << DBMS_VERSION_MINOR
|
|
|
|
|
<< "." << Revision::get()
|
|
|
|
|
<< "." << std::endl;
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
2014-12-19 23:02:18 +00:00
|
|
|
|
if (config().has("vertical"))
|
|
|
|
|
format = config().getString("format", "Vertical");
|
2014-12-18 15:48:49 +00:00
|
|
|
|
else
|
2014-12-19 23:02:18 +00:00
|
|
|
|
format = config().getString("format", is_interactive ? "PrettyCompact" : "TabSeparated");
|
2014-12-24 17:31:42 +00:00
|
|
|
|
|
2015-06-05 19:33:52 +00:00
|
|
|
|
format_max_block_size = config().getInt("format_max_block_size", context.getSettingsRef().max_block_size);
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
2012-05-21 06:49:05 +00:00
|
|
|
|
insert_format = "Values";
|
2015-06-05 19:33:52 +00:00
|
|
|
|
insert_format_max_block_size = config().getInt("insert_format_max_block_size", context.getSettingsRef().max_insert_block_size);
|
2012-05-21 06:49:05 +00:00
|
|
|
|
|
2015-06-28 07:05:42 +00:00
|
|
|
|
if (!is_interactive)
|
|
|
|
|
need_render_progress = config().getBool("progress", false);
|
|
|
|
|
|
2012-05-16 18:03:00 +00:00
|
|
|
|
connect();
|
|
|
|
|
|
2012-05-08 05:42:05 +00:00
|
|
|
|
if (is_interactive)
|
2012-03-26 02:48:08 +00:00
|
|
|
|
{
|
2015-03-27 18:43:09 +00:00
|
|
|
|
if (print_time_to_stderr)
|
|
|
|
|
throw Exception("time option could be specified only in non-interactive mode", ErrorCodes::BAD_ARGUMENTS);
|
|
|
|
|
|
2012-05-08 05:42:05 +00:00
|
|
|
|
/// Отключаем tab completion.
|
|
|
|
|
rl_bind_key('\t', rl_insert);
|
|
|
|
|
|
|
|
|
|
/// Загружаем историю команд, если есть.
|
2014-02-05 13:40:20 +00:00
|
|
|
|
if (config().has("history_file"))
|
|
|
|
|
history_file = config().getString("history_file");
|
|
|
|
|
else if (!home_path.empty())
|
|
|
|
|
history_file = home_path + "/.clickhouse-client-history";
|
|
|
|
|
|
|
|
|
|
if (!history_file.empty())
|
2012-05-08 05:42:05 +00:00
|
|
|
|
{
|
2014-02-05 13:40:20 +00:00
|
|
|
|
if (Poco::File(history_file).exists())
|
|
|
|
|
{
|
|
|
|
|
int res = read_history(history_file.c_str());
|
|
|
|
|
if (res)
|
|
|
|
|
throwFromErrno("Cannot read history from file " + history_file, ErrorCodes::CANNOT_READ_HISTORY);
|
|
|
|
|
}
|
|
|
|
|
else /// Создаём файл с историей.
|
|
|
|
|
Poco::File(history_file).createFile();
|
2012-05-08 05:42:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-08-31 18:44:05 +00:00
|
|
|
|
/// Инициализируем DateLUT, чтобы потраченное время не отображалось, как время, потраченное на запрос.
|
2014-07-08 23:52:53 +00:00
|
|
|
|
DateLUT::instance();
|
2012-08-31 18:44:05 +00:00
|
|
|
|
|
2012-05-08 05:42:05 +00:00
|
|
|
|
loop();
|
|
|
|
|
|
2015-12-13 09:19:32 +00:00
|
|
|
|
std::cout << (isNewYearMode() ? "Happy new year." : "Bye.") << std::endl;
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2013-02-21 10:40:18 +00:00
|
|
|
|
return 0;
|
2012-03-26 02:48:08 +00:00
|
|
|
|
}
|
2012-05-08 05:42:05 +00:00
|
|
|
|
else
|
2013-02-21 10:40:18 +00:00
|
|
|
|
{
|
2012-05-08 05:42:05 +00:00
|
|
|
|
nonInteractive();
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2013-02-21 10:40:18 +00:00
|
|
|
|
if (last_exception)
|
|
|
|
|
return last_exception->code();
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2013-02-21 10:40:18 +00:00
|
|
|
|
return 0;
|
|
|
|
|
}
|
2012-03-25 03:47:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-05-09 13:12:38 +00:00
|
|
|
|
void connect()
|
|
|
|
|
{
|
|
|
|
|
String host = config().getString("host", "localhost");
|
2012-07-06 18:12:52 +00:00
|
|
|
|
UInt16 port = config().getInt("port", DBMS_DEFAULT_PORT);
|
2012-05-30 06:46:57 +00:00
|
|
|
|
String default_database = config().getString("database", "");
|
2013-08-10 09:04:45 +00:00
|
|
|
|
String user = config().getString("user", "");
|
|
|
|
|
String password = config().getString("password", "");
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2012-05-16 18:03:00 +00:00
|
|
|
|
Protocol::Compression::Enum compression = config().getBool("compression", true)
|
|
|
|
|
? Protocol::Compression::Enable
|
|
|
|
|
: Protocol::Compression::Disable;
|
2012-05-09 13:12:38 +00:00
|
|
|
|
|
|
|
|
|
if (is_interactive)
|
2013-08-10 09:04:45 +00:00
|
|
|
|
std::cout << "Connecting to "
|
|
|
|
|
<< (!default_database.empty() ? "database " + default_database + " at " : "")
|
|
|
|
|
<< host << ":" << port
|
|
|
|
|
<< (!user.empty() ? " as user " + user : "")
|
|
|
|
|
<< "." << std::endl;
|
2012-05-09 13:12:38 +00:00
|
|
|
|
|
2015-05-28 03:49:28 +00:00
|
|
|
|
connection = new Connection(host, port, default_database, user, password, "client", compression,
|
2012-07-26 20:16:57 +00:00
|
|
|
|
Poco::Timespan(config().getInt("connect_timeout", DBMS_DEFAULT_CONNECT_TIMEOUT_SEC), 0),
|
|
|
|
|
Poco::Timespan(config().getInt("receive_timeout", DBMS_DEFAULT_RECEIVE_TIMEOUT_SEC), 0),
|
|
|
|
|
Poco::Timespan(config().getInt("send_timeout", DBMS_DEFAULT_SEND_TIMEOUT_SEC), 0));
|
2012-05-16 18:20:45 +00:00
|
|
|
|
|
2012-05-21 06:49:05 +00:00
|
|
|
|
if (is_interactive)
|
|
|
|
|
{
|
|
|
|
|
String server_name;
|
|
|
|
|
UInt64 server_version_major = 0;
|
|
|
|
|
UInt64 server_version_minor = 0;
|
|
|
|
|
UInt64 server_revision = 0;
|
2012-05-16 18:20:45 +00:00
|
|
|
|
|
2012-05-21 06:49:05 +00:00
|
|
|
|
connection->getServerVersion(server_name, server_version_major, server_version_minor, server_revision);
|
2012-05-16 18:20:45 +00:00
|
|
|
|
|
|
|
|
|
std::cout << "Connected to " << server_name
|
|
|
|
|
<< " server version " << server_version_major
|
|
|
|
|
<< "." << server_version_minor
|
|
|
|
|
<< "." << server_revision
|
|
|
|
|
<< "." << std::endl << std::endl;
|
2012-05-21 06:49:05 +00:00
|
|
|
|
}
|
2012-05-09 13:12:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2013-06-01 01:57:05 +00:00
|
|
|
|
static bool isWhitespace(char c)
|
2013-02-28 10:24:09 +00:00
|
|
|
|
{
|
|
|
|
|
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f';
|
|
|
|
|
}
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2012-05-09 13:12:38 +00:00
|
|
|
|
|
2015-07-25 05:55:40 +00:00
|
|
|
|
/** Проверка для случая, когда в терминал вставляется многострочный запрос из буфера обмена.
|
|
|
|
|
* Позволяет не начинать выполнение одной строчки запроса, пока весь запрос не будет вставлен.
|
|
|
|
|
*/
|
|
|
|
|
static bool hasDataInSTDIN()
|
|
|
|
|
{
|
|
|
|
|
timeval timeout = { 0, 0 };
|
|
|
|
|
fd_set fds;
|
|
|
|
|
FD_ZERO(&fds);
|
|
|
|
|
FD_SET(STDIN_FILENO, &fds);
|
|
|
|
|
return select(1, &fds, 0, 0, &timeout) == 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
void loop()
|
|
|
|
|
{
|
2013-02-28 10:24:09 +00:00
|
|
|
|
String query;
|
2013-08-10 01:07:12 +00:00
|
|
|
|
String prev_query;
|
2013-02-28 10:33:48 +00:00
|
|
|
|
while (char * line_ = readline(query.empty() ? ":) " : ":-] "))
|
2012-03-25 03:47:13 +00:00
|
|
|
|
{
|
2013-02-28 10:24:09 +00:00
|
|
|
|
String line = line_;
|
2012-03-25 03:47:13 +00:00
|
|
|
|
free(line_);
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2013-02-28 10:24:09 +00:00
|
|
|
|
size_t ws = line.size();
|
2013-06-01 01:57:05 +00:00
|
|
|
|
while (ws > 0 && isWhitespace(line[ws - 1]))
|
2013-02-28 10:24:09 +00:00
|
|
|
|
--ws;
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2013-02-28 10:28:38 +00:00
|
|
|
|
if (ws == 0 && query.empty())
|
2012-03-26 04:17:17 +00:00
|
|
|
|
continue;
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2013-02-28 10:24:09 +00:00
|
|
|
|
bool ends_with_semicolon = line[ws - 1] == ';';
|
|
|
|
|
bool ends_with_backslash = line[ws - 1] == '\\';
|
2015-03-02 01:39:42 +00:00
|
|
|
|
|
2014-12-19 23:02:18 +00:00
|
|
|
|
has_vertical_output_suffix = (ws >= 2) && (line[ws - 2] == '\\') && (line[ws - 1] == 'G');
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2013-02-28 10:24:09 +00:00
|
|
|
|
if (ends_with_backslash)
|
|
|
|
|
line = line.substr(0, ws - 1);
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2013-02-28 10:24:09 +00:00
|
|
|
|
query += line;
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2015-07-25 05:55:40 +00:00
|
|
|
|
if (!ends_with_backslash && (ends_with_semicolon || has_vertical_output_suffix || (!config().has("multiline") && !hasDataInSTDIN())))
|
2013-02-28 10:24:09 +00:00
|
|
|
|
{
|
2013-08-10 01:07:12 +00:00
|
|
|
|
if (query != prev_query)
|
|
|
|
|
{
|
2014-12-18 09:44:57 +00:00
|
|
|
|
// Заменяем переводы строк на пробелы, а то возникает следуцющая проблема.
|
|
|
|
|
// Каждая строчка многострочного запроса сохраняется в истории отдельно. Если
|
2015-03-02 01:39:42 +00:00
|
|
|
|
// выйти из клиента и войти заново, то при нажатии клавиши "вверх" выводится не
|
2014-12-18 09:44:57 +00:00
|
|
|
|
// весь многострочный запрос, а каждая его строчка по-отдельности.
|
2014-12-05 13:10:55 +00:00
|
|
|
|
std::string logged_query = query;
|
|
|
|
|
std::replace(logged_query.begin(), logged_query.end(), '\n', ' ');
|
|
|
|
|
add_history(logged_query.c_str());
|
2013-08-10 01:07:12 +00:00
|
|
|
|
|
2014-02-05 13:40:20 +00:00
|
|
|
|
if (!history_file.empty() && append_history(1, history_file.c_str()))
|
2013-08-10 01:07:12 +00:00
|
|
|
|
throwFromErrno("Cannot append history to file " + history_file, ErrorCodes::CANNOT_APPEND_HISTORY);
|
|
|
|
|
|
|
|
|
|
prev_query = query;
|
|
|
|
|
}
|
2014-12-24 17:31:42 +00:00
|
|
|
|
|
2014-12-19 23:02:18 +00:00
|
|
|
|
if (has_vertical_output_suffix)
|
2014-12-18 15:48:49 +00:00
|
|
|
|
query = query.substr(0, query.length() - 2);
|
2013-06-01 01:57:05 +00:00
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2014-10-25 20:27:37 +00:00
|
|
|
|
/// Выясняем размер терминала.
|
|
|
|
|
ioctl(0, TIOCGWINSZ, &terminal_size);
|
|
|
|
|
|
2013-06-01 01:57:05 +00:00
|
|
|
|
if (!process(query))
|
|
|
|
|
break;
|
|
|
|
|
}
|
2013-10-26 03:20:51 +00:00
|
|
|
|
catch (const Exception & e)
|
2013-06-01 01:57:05 +00:00
|
|
|
|
{
|
|
|
|
|
std::cerr << std::endl
|
|
|
|
|
<< "Exception on client:" << std::endl
|
|
|
|
|
<< "Code: " << e.code() << ". " << e.displayText() << std::endl
|
|
|
|
|
<< std::endl;
|
|
|
|
|
|
|
|
|
|
/** Эксепшен на клиенте в процессе обработки запроса может привести к рассинхронизации соединения.
|
|
|
|
|
* Установим соединение заново и позволим ввести следующий запрос.
|
|
|
|
|
*/
|
|
|
|
|
connect();
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-28 10:24:09 +00:00
|
|
|
|
query = "";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
query += '\n';
|
|
|
|
|
}
|
2012-03-25 03:47:13 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-05-08 05:42:05 +00:00
|
|
|
|
void nonInteractive()
|
|
|
|
|
{
|
2014-03-28 13:45:24 +00:00
|
|
|
|
String line;
|
2012-05-08 05:42:05 +00:00
|
|
|
|
if (config().has("query"))
|
2014-03-28 13:45:24 +00:00
|
|
|
|
line = config().getString("query");
|
2012-05-08 05:42:05 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
2013-02-01 18:55:31 +00:00
|
|
|
|
/** В случае, если параметр query не задан, то запрос будет читаться из stdin.
|
|
|
|
|
* При этом, запрос будет читаться не потоково (целиком в оперативку).
|
|
|
|
|
* Поддерживается только один запрос в stdin.
|
|
|
|
|
*/
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2014-03-28 13:45:24 +00:00
|
|
|
|
ReadBufferFromFileDescriptor in(STDIN_FILENO);
|
|
|
|
|
WriteBufferFromString out(line);
|
|
|
|
|
copyData(in, out);
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-25 10:58:41 +00:00
|
|
|
|
process(line);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool process(const String & line)
|
|
|
|
|
{
|
2014-03-28 13:45:24 +00:00
|
|
|
|
if (config().has("multiquery"))
|
|
|
|
|
{
|
|
|
|
|
/// Несколько запросов, разделенных ';'.
|
|
|
|
|
/// Данные для INSERT заканчиваются переводом строки, а не ';'.
|
2013-02-01 18:55:31 +00:00
|
|
|
|
|
2014-03-28 13:45:24 +00:00
|
|
|
|
String query;
|
|
|
|
|
|
|
|
|
|
const char * begin = line.data();
|
|
|
|
|
const char * end = begin + line.size();
|
|
|
|
|
|
|
|
|
|
while (begin < end)
|
2013-02-01 18:55:31 +00:00
|
|
|
|
{
|
2014-03-28 13:45:24 +00:00
|
|
|
|
const char * pos = begin;
|
|
|
|
|
ASTPtr ast = parseQuery(pos, end);
|
2014-06-26 00:58:14 +00:00
|
|
|
|
ASTInsertQuery * insert = typeid_cast<ASTInsertQuery *>(&*ast);
|
2014-03-28 13:45:24 +00:00
|
|
|
|
|
|
|
|
|
if (insert && insert->data)
|
|
|
|
|
{
|
|
|
|
|
pos = insert->data;
|
|
|
|
|
while (*pos && *pos != '\n')
|
|
|
|
|
++pos;
|
|
|
|
|
insert->end = pos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
query = line.substr(begin - line.data(), pos - begin);
|
|
|
|
|
|
|
|
|
|
begin = pos;
|
|
|
|
|
while (isWhitespace(*begin) || *begin == ';')
|
|
|
|
|
++begin;
|
2013-02-01 18:55:31 +00:00
|
|
|
|
|
2015-12-24 19:27:14 +00:00
|
|
|
|
if (!processSingleQuery(query, ast) || got_exception)
|
2015-07-25 10:58:41 +00:00
|
|
|
|
return false;
|
2014-03-28 13:45:24 +00:00
|
|
|
|
}
|
2015-07-25 10:58:41 +00:00
|
|
|
|
|
|
|
|
|
return true;
|
2014-03-28 13:45:24 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-07-25 10:58:41 +00:00
|
|
|
|
return processSingleQuery(line);
|
2012-05-08 05:42:05 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-07-25 10:58:41 +00:00
|
|
|
|
bool processSingleQuery(const String & line, ASTPtr parsed_query_ = nullptr)
|
2012-03-25 03:47:13 +00:00
|
|
|
|
{
|
|
|
|
|
if (exit_strings.end() != exit_strings.find(line))
|
|
|
|
|
return false;
|
|
|
|
|
|
2014-08-14 20:00:19 +00:00
|
|
|
|
resetOutput();
|
2015-02-27 20:35:26 +00:00
|
|
|
|
got_exception = false;
|
2012-05-16 18:03:00 +00:00
|
|
|
|
|
2012-05-10 07:47:13 +00:00
|
|
|
|
watch.restart();
|
2012-03-26 02:48:08 +00:00
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
query = line;
|
2012-03-26 04:17:17 +00:00
|
|
|
|
|
|
|
|
|
/// Некоторые части запроса выполняются на стороне клиента (форматирование результата). Поэтому, распарсим запрос.
|
2014-03-28 13:45:24 +00:00
|
|
|
|
parsed_query = parsed_query_;
|
|
|
|
|
|
|
|
|
|
if (!parsed_query)
|
|
|
|
|
{
|
|
|
|
|
const char * begin = query.data();
|
|
|
|
|
parsed_query = parseQuery(begin, begin + query.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!parsed_query)
|
2012-03-26 04:17:17 +00:00
|
|
|
|
return true;
|
2012-05-09 08:16:09 +00:00
|
|
|
|
|
2012-05-21 06:49:05 +00:00
|
|
|
|
processed_rows = 0;
|
2014-10-25 18:33:52 +00:00
|
|
|
|
progress.reset();
|
2014-10-25 20:27:37 +00:00
|
|
|
|
show_progress_bar = false;
|
2012-05-09 16:34:41 +00:00
|
|
|
|
written_progress_chars = 0;
|
|
|
|
|
written_first_block = false;
|
2012-05-09 13:12:38 +00:00
|
|
|
|
|
2014-06-26 00:58:14 +00:00
|
|
|
|
const ASTSetQuery * set_query = typeid_cast<const ASTSetQuery *>(&*parsed_query);
|
|
|
|
|
const ASTUseQuery * use_query = typeid_cast<const ASTUseQuery *>(&*parsed_query);
|
2014-04-23 13:33:12 +00:00
|
|
|
|
/// Запрос INSERT (но только тот, что требует передачи данных - не INSERT SELECT), обрабатывается отдельным способом.
|
2014-06-26 00:58:14 +00:00
|
|
|
|
const ASTInsertQuery * insert = typeid_cast<const ASTInsertQuery *>(&*parsed_query);
|
2014-04-23 13:33:12 +00:00
|
|
|
|
|
|
|
|
|
if (insert && !insert->select)
|
|
|
|
|
processInsertQuery();
|
|
|
|
|
else
|
|
|
|
|
processOrdinaryQuery();
|
|
|
|
|
|
2015-02-27 20:35:26 +00:00
|
|
|
|
/// В случае исключения, не будем менять контекст (текущая БД, настройки) на клиенте.
|
|
|
|
|
if (!got_exception)
|
2014-04-18 13:59:39 +00:00
|
|
|
|
{
|
2015-02-27 20:35:26 +00:00
|
|
|
|
if (set_query)
|
2014-04-18 14:32:30 +00:00
|
|
|
|
{
|
2015-02-27 20:35:26 +00:00
|
|
|
|
/// Запоминаем все изменения в настройках, чтобы не потерять их при разрыве соединения.
|
|
|
|
|
for (ASTSetQuery::Changes::const_iterator it = set_query->changes.begin(); it != set_query->changes.end(); ++it)
|
|
|
|
|
{
|
|
|
|
|
if (it->name == "profile")
|
|
|
|
|
current_profile = it->value.safeGet<String>();
|
|
|
|
|
else
|
|
|
|
|
context.setSetting(it->name, it->value);
|
|
|
|
|
}
|
2014-04-18 14:32:30 +00:00
|
|
|
|
}
|
2014-04-18 13:59:39 +00:00
|
|
|
|
|
2015-02-27 20:35:26 +00:00
|
|
|
|
if (use_query)
|
|
|
|
|
{
|
|
|
|
|
const String & new_database = use_query->database;
|
|
|
|
|
/// Если клиент инициирует пересоединение, он берет настройки из конфига
|
|
|
|
|
config().setString("database", new_database);
|
|
|
|
|
/// Если connection инициирует пересоединение, он использует свою переменную
|
|
|
|
|
connection->setDefaultDatabase(new_database);
|
|
|
|
|
}
|
2014-04-18 13:59:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-05-08 05:42:05 +00:00
|
|
|
|
if (is_interactive)
|
2013-11-03 01:12:10 +00:00
|
|
|
|
{
|
2012-05-08 05:42:05 +00:00
|
|
|
|
std::cout << std::endl
|
2013-11-03 01:12:10 +00:00
|
|
|
|
<< processed_rows << " rows in set. Elapsed: " << watch.elapsedSeconds() << " sec. ";
|
|
|
|
|
|
2014-10-25 18:33:52 +00:00
|
|
|
|
if (progress.rows >= 1000)
|
2013-11-03 01:12:10 +00:00
|
|
|
|
writeFinalProgress();
|
|
|
|
|
|
2013-11-03 01:13:37 +00:00
|
|
|
|
std::cout << std::endl << std::endl;
|
2013-11-03 01:12:10 +00:00
|
|
|
|
}
|
2015-03-27 18:43:09 +00:00
|
|
|
|
else if (print_time_to_stderr)
|
|
|
|
|
{
|
|
|
|
|
std::cerr << watch.elapsedSeconds() << "\n";
|
|
|
|
|
}
|
2012-03-26 02:48:08 +00:00
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-03-14 15:42:30 +00:00
|
|
|
|
/// Преобразовать внешние таблицы к ExternalTableData и переслать через connection
|
2014-03-06 14:02:20 +00:00
|
|
|
|
void sendExternalTables()
|
|
|
|
|
{
|
2014-06-26 00:58:14 +00:00
|
|
|
|
const ASTSelectQuery * select = typeid_cast<const ASTSelectQuery *>(&*parsed_query);
|
2014-03-12 13:14:16 +00:00
|
|
|
|
if (!select && !external_tables.empty())
|
|
|
|
|
throw Exception("External tables could be sent only with select query", ErrorCodes::BAD_ARGUMENTS);
|
2014-03-14 15:42:30 +00:00
|
|
|
|
|
2014-03-06 14:02:20 +00:00
|
|
|
|
std::vector<ExternalTableData> data;
|
2014-04-22 18:35:40 +00:00
|
|
|
|
for (auto & table : external_tables)
|
|
|
|
|
data.emplace_back(table.getData(context));
|
|
|
|
|
|
2014-03-14 15:42:30 +00:00
|
|
|
|
connection->sendExternalTablesData(data);
|
2014-03-06 14:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-05-21 06:49:05 +00:00
|
|
|
|
/// Обработать запрос, который не требует передачи блоков данных на сервер.
|
|
|
|
|
void processOrdinaryQuery()
|
|
|
|
|
{
|
2014-04-15 14:01:48 +00:00
|
|
|
|
connection->sendQuery(query, "", QueryProcessingStage::Complete, &context.getSettingsRef(), true);
|
2014-03-06 14:02:20 +00:00
|
|
|
|
sendExternalTables();
|
2012-05-21 06:49:05 +00:00
|
|
|
|
receiveResult();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Обработать запрос, который требует передачи блоков данных на сервер.
|
|
|
|
|
void processInsertQuery()
|
|
|
|
|
{
|
|
|
|
|
/// Отправляем часть запроса - без данных, так как данные будут отправлены отдельно.
|
2014-06-26 00:58:14 +00:00
|
|
|
|
const ASTInsertQuery & parsed_insert_query = typeid_cast<const ASTInsertQuery &>(*parsed_query);
|
2012-05-21 06:49:05 +00:00
|
|
|
|
String query_without_data = parsed_insert_query.data
|
|
|
|
|
? query.substr(0, parsed_insert_query.data - query.data())
|
|
|
|
|
: query;
|
|
|
|
|
|
2014-03-28 12:43:41 +00:00
|
|
|
|
if (!parsed_insert_query.data && (is_interactive || (stdin_is_not_tty && std_in.eof())))
|
2012-05-28 19:57:44 +00:00
|
|
|
|
throw Exception("No data to insert", ErrorCodes::NO_DATA_TO_INSERT);
|
|
|
|
|
|
2014-04-15 14:01:48 +00:00
|
|
|
|
connection->sendQuery(query_without_data, "", QueryProcessingStage::Complete, &context.getSettingsRef(), true);
|
2014-03-06 14:02:20 +00:00
|
|
|
|
sendExternalTables();
|
2012-05-21 06:49:05 +00:00
|
|
|
|
|
2014-12-18 12:11:08 +00:00
|
|
|
|
/// Получаем структуру таблицы.
|
|
|
|
|
Block sample;
|
|
|
|
|
if (receiveSampleBlock(sample))
|
|
|
|
|
{
|
|
|
|
|
/// Если была получена структура, т.е. сервер не выкинул исключения,
|
|
|
|
|
/// отправляем эту структуру вместе с данными.
|
|
|
|
|
sendData(sample);
|
|
|
|
|
receivePacket();
|
|
|
|
|
}
|
2012-05-21 06:49:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-04-11 03:10:23 +00:00
|
|
|
|
ASTPtr parseQuery(IParser::Pos & pos, const char * end)
|
2012-03-26 04:17:17 +00:00
|
|
|
|
{
|
|
|
|
|
ParserQuery parser;
|
2014-03-28 13:45:24 +00:00
|
|
|
|
ASTPtr res;
|
2012-03-26 04:17:17 +00:00
|
|
|
|
|
2015-04-11 03:10:23 +00:00
|
|
|
|
if (is_interactive)
|
2012-03-26 04:17:17 +00:00
|
|
|
|
{
|
2015-04-11 03:10:23 +00:00
|
|
|
|
String message;
|
2015-04-11 04:15:14 +00:00
|
|
|
|
res = tryParseQuery(parser, pos, end, message, true, "");
|
2012-03-26 04:17:17 +00:00
|
|
|
|
|
2015-04-11 03:10:23 +00:00
|
|
|
|
if (!res)
|
|
|
|
|
{
|
2015-04-11 04:15:14 +00:00
|
|
|
|
std::cerr << std::endl << message << std::endl << std::endl;
|
2015-04-11 03:10:23 +00:00
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2012-03-26 04:17:17 +00:00
|
|
|
|
}
|
2015-04-11 03:10:23 +00:00
|
|
|
|
else
|
2015-04-14 20:46:34 +00:00
|
|
|
|
res = DB::parseQueryAndMovePosition(parser, pos, end, "");
|
2012-03-26 04:17:17 +00:00
|
|
|
|
|
2012-05-08 05:42:05 +00:00
|
|
|
|
if (is_interactive)
|
|
|
|
|
{
|
|
|
|
|
std::cout << std::endl;
|
2014-03-28 13:45:24 +00:00
|
|
|
|
formatAST(*res, std::cout);
|
2012-05-08 05:42:05 +00:00
|
|
|
|
std::cout << std::endl << std::endl;
|
|
|
|
|
}
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2014-03-28 13:45:24 +00:00
|
|
|
|
return res;
|
2012-03-26 04:17:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-05-21 06:49:05 +00:00
|
|
|
|
void sendData(Block & sample)
|
2012-05-08 11:19:00 +00:00
|
|
|
|
{
|
|
|
|
|
/// Если нужно отправить данные INSERT-а.
|
2014-06-26 00:58:14 +00:00
|
|
|
|
const ASTInsertQuery * parsed_insert_query = typeid_cast<const ASTInsertQuery *>(&*parsed_query);
|
2012-05-08 11:19:00 +00:00
|
|
|
|
if (!parsed_insert_query)
|
|
|
|
|
return;
|
2012-05-28 19:34:55 +00:00
|
|
|
|
|
2012-05-08 11:19:00 +00:00
|
|
|
|
if (parsed_insert_query->data)
|
|
|
|
|
{
|
|
|
|
|
/// Отправляем данные из запроса.
|
2012-05-21 06:49:05 +00:00
|
|
|
|
ReadBuffer data_in(const_cast<char *>(parsed_insert_query->data), parsed_insert_query->end - parsed_insert_query->data, 0);
|
|
|
|
|
sendDataFrom(data_in, sample);
|
2012-05-08 11:19:00 +00:00
|
|
|
|
}
|
2013-10-24 23:02:02 +00:00
|
|
|
|
else if (!is_interactive)
|
2012-05-08 11:19:00 +00:00
|
|
|
|
{
|
|
|
|
|
/// Отправляем данные из stdin.
|
2012-05-21 06:49:05 +00:00
|
|
|
|
sendDataFrom(std_in, sample);
|
2012-05-08 11:19:00 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
throw Exception("No data to insert", ErrorCodes::NO_DATA_TO_INSERT);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-05-21 06:49:05 +00:00
|
|
|
|
void sendDataFrom(ReadBuffer & buf, Block & sample)
|
2012-05-08 11:19:00 +00:00
|
|
|
|
{
|
2012-05-21 06:49:05 +00:00
|
|
|
|
String current_format = insert_format;
|
|
|
|
|
|
|
|
|
|
/// Формат может быть указан в INSERT запросе.
|
2014-06-26 00:58:14 +00:00
|
|
|
|
if (ASTInsertQuery * insert = typeid_cast<ASTInsertQuery *>(&*parsed_query))
|
2012-05-21 06:49:05 +00:00
|
|
|
|
if (!insert->format.empty())
|
|
|
|
|
current_format = insert->format;
|
|
|
|
|
|
2015-03-29 07:13:38 +00:00
|
|
|
|
BlockInputStreamPtr block_input = context.getFormatFactory().getInput(
|
2015-05-28 03:49:28 +00:00
|
|
|
|
current_format, buf, sample, insert_format_max_block_size);
|
2012-05-21 06:49:05 +00:00
|
|
|
|
|
2015-03-29 07:13:38 +00:00
|
|
|
|
BlockInputStreamPtr async_block_input = new AsynchronousBlockInputStream(block_input);
|
|
|
|
|
|
2015-03-29 09:02:24 +00:00
|
|
|
|
async_block_input->readPrefix();
|
2015-03-29 07:13:38 +00:00
|
|
|
|
|
2015-03-29 09:02:24 +00:00
|
|
|
|
while (true)
|
2015-03-29 07:13:38 +00:00
|
|
|
|
{
|
2015-03-29 09:02:24 +00:00
|
|
|
|
Block block = async_block_input->read();
|
|
|
|
|
connection->sendData(block);
|
|
|
|
|
processed_rows += block.rows();
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2015-03-29 09:02:24 +00:00
|
|
|
|
if (!block)
|
|
|
|
|
break;
|
2015-03-29 07:13:38 +00:00
|
|
|
|
}
|
2015-03-29 09:02:24 +00:00
|
|
|
|
|
|
|
|
|
async_block_input->readSuffix();
|
2012-05-08 11:19:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-08-14 20:00:19 +00:00
|
|
|
|
/** Сбросить все данные, что ещё остались в буферах. */
|
|
|
|
|
void resetOutput()
|
|
|
|
|
{
|
|
|
|
|
block_std_out = nullptr;
|
|
|
|
|
std_out.next();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-05-16 18:03:00 +00:00
|
|
|
|
/** Получает и обрабатывает пакеты из сервера.
|
|
|
|
|
* Также следит, не требуется ли прервать выполнение запроса.
|
|
|
|
|
*/
|
2012-03-25 03:47:13 +00:00
|
|
|
|
void receiveResult()
|
|
|
|
|
{
|
2012-05-09 08:16:09 +00:00
|
|
|
|
InterruptListener interrupt_listener;
|
|
|
|
|
bool cancelled = false;
|
|
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
/** Проверим, не требуется ли остановить выполнение запроса (Ctrl+C).
|
|
|
|
|
* Если требуется - отправим об этом информацию на сервер.
|
|
|
|
|
* После чего, получим оставшиеся пакеты с сервера (чтобы не было рассинхронизации).
|
|
|
|
|
*/
|
|
|
|
|
if (!cancelled)
|
|
|
|
|
{
|
|
|
|
|
if (interrupt_listener.check())
|
|
|
|
|
{
|
2012-05-16 18:03:00 +00:00
|
|
|
|
connection->sendCancel();
|
2012-05-09 08:16:09 +00:00
|
|
|
|
cancelled = true;
|
|
|
|
|
if (is_interactive)
|
|
|
|
|
std::cout << "Cancelling query." << std::endl;
|
2012-11-10 02:21:39 +00:00
|
|
|
|
|
|
|
|
|
/// Повторное нажатие Ctrl+C приведёт к завершению работы.
|
|
|
|
|
interrupt_listener.unblock();
|
2012-05-09 08:16:09 +00:00
|
|
|
|
}
|
2012-05-16 18:03:00 +00:00
|
|
|
|
else if (!connection->poll(1000000))
|
2012-05-09 08:16:09 +00:00
|
|
|
|
continue; /// Если новых данных в ещё нет, то после таймаута продолжим проверять, не остановлено ли выполнение запроса.
|
|
|
|
|
}
|
2012-03-25 07:52:31 +00:00
|
|
|
|
|
2012-05-09 08:16:09 +00:00
|
|
|
|
if (!receivePacket())
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cancelled && is_interactive)
|
|
|
|
|
std::cout << "Query was cancelled." << std::endl;
|
2012-03-25 03:47:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-05-16 18:03:00 +00:00
|
|
|
|
/** Получить кусок результата или прогресс выполнения или эксепшен,
|
|
|
|
|
* и обработать пакет соответствующим образом.
|
|
|
|
|
* Возвращает true, если нужно продолжать чтение пакетов.
|
|
|
|
|
*/
|
2012-03-25 03:47:13 +00:00
|
|
|
|
bool receivePacket()
|
|
|
|
|
{
|
2012-05-16 18:03:00 +00:00
|
|
|
|
Connection::Packet packet = connection->receivePacket();
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
2012-05-16 18:03:00 +00:00
|
|
|
|
switch (packet.type)
|
2012-03-25 03:47:13 +00:00
|
|
|
|
{
|
|
|
|
|
case Protocol::Server::Data:
|
2012-05-16 18:03:00 +00:00
|
|
|
|
onData(packet.block);
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
case Protocol::Server::Progress:
|
|
|
|
|
onProgress(packet.progress);
|
|
|
|
|
return true;
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2013-05-22 14:57:43 +00:00
|
|
|
|
case Protocol::Server::ProfileInfo:
|
|
|
|
|
onProfileInfo(packet.profile_info);
|
|
|
|
|
return true;
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
2013-09-05 20:22:43 +00:00
|
|
|
|
case Protocol::Server::Totals:
|
|
|
|
|
onTotals(packet.block);
|
|
|
|
|
return true;
|
|
|
|
|
|
2013-09-07 02:03:13 +00:00
|
|
|
|
case Protocol::Server::Extremes:
|
|
|
|
|
onExtremes(packet.block);
|
|
|
|
|
return true;
|
|
|
|
|
|
2012-05-08 05:42:05 +00:00
|
|
|
|
case Protocol::Server::Exception:
|
2012-05-16 18:03:00 +00:00
|
|
|
|
onException(*packet.exception);
|
2013-02-21 10:40:18 +00:00
|
|
|
|
last_exception = packet.exception;
|
2012-05-08 05:42:05 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
2012-05-16 18:03:00 +00:00
|
|
|
|
case Protocol::Server::EndOfStream:
|
|
|
|
|
onEndOfStream();
|
2012-05-08 11:19:00 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
default:
|
|
|
|
|
throw Exception("Unknown packet from server", ErrorCodes::UNKNOWN_PACKET_FROM_SERVER);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-05-21 06:49:05 +00:00
|
|
|
|
/** Получить блок - пример структуры таблицы, в которую будут вставляться данные.
|
|
|
|
|
*/
|
2014-12-18 12:11:08 +00:00
|
|
|
|
bool receiveSampleBlock(Block & out)
|
2012-05-21 06:49:05 +00:00
|
|
|
|
{
|
|
|
|
|
Connection::Packet packet = connection->receivePacket();
|
|
|
|
|
|
|
|
|
|
switch (packet.type)
|
|
|
|
|
{
|
|
|
|
|
case Protocol::Server::Data:
|
2014-12-18 12:11:08 +00:00
|
|
|
|
out = packet.block;
|
|
|
|
|
return true;
|
2012-05-21 06:49:05 +00:00
|
|
|
|
|
2014-12-18 12:11:08 +00:00
|
|
|
|
case Protocol::Server::Exception:
|
|
|
|
|
onException(*packet.exception);
|
|
|
|
|
last_exception = packet.exception;
|
|
|
|
|
return false;
|
2015-03-02 01:39:42 +00:00
|
|
|
|
|
2012-05-21 06:49:05 +00:00
|
|
|
|
default:
|
2015-03-26 08:47:02 +00:00
|
|
|
|
throw NetException("Unexpected packet from server (expected Data, got "
|
2013-08-04 00:42:35 +00:00
|
|
|
|
+ String(Protocol::Server::toString(packet.type)) + ")", ErrorCodes::UNEXPECTED_PACKET_FROM_SERVER);
|
2012-05-21 06:49:05 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-07-27 15:51:37 +00:00
|
|
|
|
void initBlockOutputStream(const Block & block)
|
2012-03-25 03:47:13 +00:00
|
|
|
|
{
|
2013-11-03 01:12:10 +00:00
|
|
|
|
if (!block_std_out)
|
2012-03-25 03:47:13 +00:00
|
|
|
|
{
|
2014-12-19 23:02:18 +00:00
|
|
|
|
String current_format = format;
|
2015-03-02 01:39:42 +00:00
|
|
|
|
|
2013-11-03 01:12:10 +00:00
|
|
|
|
/// Формат может быть указан в запросе.
|
|
|
|
|
if (ASTQueryWithOutput * query_with_output = dynamic_cast<ASTQueryWithOutput *>(&*parsed_query))
|
2014-12-19 23:02:18 +00:00
|
|
|
|
{
|
2015-06-25 17:38:54 +00:00
|
|
|
|
if (query_with_output->getFormat() != nullptr)
|
2014-12-19 23:02:18 +00:00
|
|
|
|
{
|
|
|
|
|
if (has_vertical_output_suffix)
|
|
|
|
|
throw Exception("Output format already specified", ErrorCodes::CLIENT_OUTPUT_FORMAT_SPECIFIED);
|
2015-06-25 17:38:54 +00:00
|
|
|
|
if (const ASTIdentifier * id = typeid_cast<const ASTIdentifier *>(query_with_output->getFormat()))
|
2013-11-03 01:12:10 +00:00
|
|
|
|
current_format = id->name;
|
2014-12-19 23:02:18 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2015-03-02 01:39:42 +00:00
|
|
|
|
|
2014-12-19 23:02:18 +00:00
|
|
|
|
if (has_vertical_output_suffix)
|
2014-12-24 17:31:42 +00:00
|
|
|
|
current_format = "Vertical";
|
2015-03-02 01:39:42 +00:00
|
|
|
|
|
2013-11-03 01:12:10 +00:00
|
|
|
|
block_std_out = context.getFormatFactory().getOutput(current_format, std_out, block);
|
|
|
|
|
block_std_out->writePrefix();
|
2012-03-25 03:47:13 +00:00
|
|
|
|
}
|
2015-07-27 15:51:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void onData(Block & block)
|
|
|
|
|
{
|
|
|
|
|
if (written_progress_chars)
|
|
|
|
|
clearProgress();
|
|
|
|
|
|
|
|
|
|
if (!block)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
processed_rows += block.rows();
|
|
|
|
|
initBlockOutputStream(block);
|
2013-11-03 01:12:10 +00:00
|
|
|
|
|
2015-07-27 15:51:37 +00:00
|
|
|
|
/// Заголовочный блок с нулем строк использовался для инициализации block_std_out,
|
2013-11-03 01:12:10 +00:00
|
|
|
|
/// выводить его не нужно
|
|
|
|
|
if (block.rows() != 0)
|
|
|
|
|
{
|
|
|
|
|
block_std_out->write(block);
|
|
|
|
|
written_first_block = true;
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-14 20:27:41 +00:00
|
|
|
|
/// Полученный блок данных сразу выводится клиенту.
|
|
|
|
|
block_std_out->flush();
|
2013-09-05 20:22:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void onTotals(Block & block)
|
|
|
|
|
{
|
2015-07-27 15:51:37 +00:00
|
|
|
|
initBlockOutputStream(block);
|
2013-09-05 20:22:43 +00:00
|
|
|
|
block_std_out->setTotals(block);
|
2012-03-25 03:47:13 +00:00
|
|
|
|
}
|
2012-05-08 05:42:05 +00:00
|
|
|
|
|
2013-09-07 02:03:13 +00:00
|
|
|
|
void onExtremes(Block & block)
|
|
|
|
|
{
|
2015-07-27 15:51:37 +00:00
|
|
|
|
initBlockOutputStream(block);
|
2013-09-07 02:03:13 +00:00
|
|
|
|
block_std_out->setExtremes(block);
|
|
|
|
|
}
|
|
|
|
|
|
2012-05-08 05:42:05 +00:00
|
|
|
|
|
2014-10-25 18:33:52 +00:00
|
|
|
|
void onProgress(const Progress & value)
|
2012-05-08 11:19:00 +00:00
|
|
|
|
{
|
2014-10-25 18:33:52 +00:00
|
|
|
|
progress.increment(value);
|
2013-11-03 01:12:10 +00:00
|
|
|
|
writeProgress();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-08-15 00:08:15 +00:00
|
|
|
|
void clearProgress()
|
|
|
|
|
{
|
|
|
|
|
std::cerr << RESTORE_CURSOR_POSITION CLEAR_TO_END_OF_LINE;
|
|
|
|
|
written_progress_chars = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-11-03 01:12:10 +00:00
|
|
|
|
void writeProgress()
|
|
|
|
|
{
|
2015-06-28 07:05:42 +00:00
|
|
|
|
if (!need_render_progress)
|
2014-08-15 00:08:15 +00:00
|
|
|
|
return;
|
|
|
|
|
|
2012-05-09 15:15:45 +00:00
|
|
|
|
static size_t increment = 0;
|
|
|
|
|
static const char * indicators[8] =
|
|
|
|
|
{
|
2012-05-09 16:34:41 +00:00
|
|
|
|
"\033[1;30m→\033[0m",
|
|
|
|
|
"\033[1;31m↘\033[0m",
|
|
|
|
|
"\033[1;32m↓\033[0m",
|
|
|
|
|
"\033[1;33m↙\033[0m",
|
|
|
|
|
"\033[1;34m←\033[0m",
|
|
|
|
|
"\033[1;35m↖\033[0m",
|
|
|
|
|
"\033[1;36m↑\033[0m",
|
2014-08-15 00:08:15 +00:00
|
|
|
|
"\033[1m↗\033[0m",
|
2012-05-09 15:15:45 +00:00
|
|
|
|
};
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2014-08-15 00:08:15 +00:00
|
|
|
|
if (written_progress_chars)
|
|
|
|
|
clearProgress();
|
|
|
|
|
else
|
|
|
|
|
std::cerr << SAVE_CURSOR_POSITION;
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
2014-08-15 00:08:15 +00:00
|
|
|
|
std::stringstream message;
|
|
|
|
|
message << indicators[increment % 8]
|
|
|
|
|
<< std::fixed << std::setprecision(3)
|
2014-10-25 18:33:52 +00:00
|
|
|
|
<< " Progress: ";
|
|
|
|
|
|
|
|
|
|
message
|
|
|
|
|
<< formatReadableQuantity(progress.rows) << " rows, "
|
|
|
|
|
<< formatReadableSizeWithDecimalSuffix(progress.bytes);
|
2014-08-15 00:08:15 +00:00
|
|
|
|
|
|
|
|
|
size_t elapsed_ns = watch.elapsed();
|
|
|
|
|
if (elapsed_ns)
|
|
|
|
|
message << " ("
|
2014-10-25 18:33:52 +00:00
|
|
|
|
<< formatReadableQuantity(progress.rows * 1000000000.0 / elapsed_ns) << " rows/s., "
|
|
|
|
|
<< formatReadableSizeWithDecimalSuffix(progress.bytes * 1000000000.0 / elapsed_ns) << "/s.) ";
|
2014-08-15 00:08:15 +00:00
|
|
|
|
else
|
|
|
|
|
message << ". ";
|
|
|
|
|
|
2014-10-25 20:27:37 +00:00
|
|
|
|
written_progress_chars = message.str().size() - (increment % 8 == 7 ? 10 : 13);
|
|
|
|
|
std::cerr << DISABLE_LINE_WRAPPING << message.rdbuf();
|
|
|
|
|
|
|
|
|
|
/** Если известно приблизительное общее число строк, которых нужно обработать - можно вывести прогрессбар.
|
|
|
|
|
* Чтобы не было "мерцания", выводим его только если с момента начала выполнения запроса прошло хотя бы пол секунды,
|
|
|
|
|
* и если к этому моменту запрос обработан менее чем наполовину.
|
|
|
|
|
*/
|
|
|
|
|
ssize_t width_of_progress_bar = static_cast<ssize_t>(terminal_size.ws_col) - written_progress_chars - strlen(" 99%");
|
|
|
|
|
|
|
|
|
|
if (show_progress_bar
|
|
|
|
|
|| (width_of_progress_bar > 0
|
|
|
|
|
&& progress.total_rows
|
|
|
|
|
&& elapsed_ns > 500000000
|
|
|
|
|
&& progress.rows * 2 < progress.total_rows))
|
|
|
|
|
{
|
|
|
|
|
show_progress_bar = true;
|
|
|
|
|
|
2014-10-26 02:01:21 +00:00
|
|
|
|
size_t total_rows_corrected = std::max(progress.rows, progress.total_rows);
|
|
|
|
|
|
|
|
|
|
std::string bar = UnicodeBar::render(UnicodeBar::getWidth(progress.rows, 0, total_rows_corrected, width_of_progress_bar));
|
2014-10-25 20:27:37 +00:00
|
|
|
|
std::cerr << "\033[0;32m" << bar << "\033[0m";
|
|
|
|
|
if (width_of_progress_bar > static_cast<ssize_t>(bar.size() / UNICODE_BAR_CHAR_SIZE))
|
|
|
|
|
std::cerr << std::string(width_of_progress_bar - bar.size() / UNICODE_BAR_CHAR_SIZE, ' ');
|
2014-10-26 02:01:21 +00:00
|
|
|
|
std::cerr << ' ' << (99 * progress.rows / total_rows_corrected) << '%'; /// Чуть-чуть занижаем процент, чтобы не показывать 100%.
|
2014-10-25 20:27:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::cerr << ENABLE_LINE_WRAPPING;
|
2014-08-15 00:08:15 +00:00
|
|
|
|
++increment;
|
2012-05-09 15:15:45 +00:00
|
|
|
|
}
|
2012-05-16 18:03:00 +00:00
|
|
|
|
|
|
|
|
|
|
2013-11-03 01:12:10 +00:00
|
|
|
|
void writeFinalProgress()
|
|
|
|
|
{
|
2014-10-25 18:33:52 +00:00
|
|
|
|
std::cout << "Processed "
|
|
|
|
|
<< formatReadableQuantity(progress.rows) << " rows, "
|
|
|
|
|
<< formatReadableSizeWithDecimalSuffix(progress.bytes);
|
2013-11-03 01:12:10 +00:00
|
|
|
|
|
|
|
|
|
size_t elapsed_ns = watch.elapsed();
|
|
|
|
|
if (elapsed_ns)
|
|
|
|
|
std::cout << " ("
|
2014-10-25 18:33:52 +00:00
|
|
|
|
<< formatReadableQuantity(progress.rows * 1000000000.0 / elapsed_ns) << " rows/s., "
|
|
|
|
|
<< formatReadableSizeWithDecimalSuffix(progress.bytes * 1000000000.0 / elapsed_ns) << "/s.) ";
|
2013-11-03 01:12:10 +00:00
|
|
|
|
else
|
|
|
|
|
std::cout << ". ";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-05-16 18:03:00 +00:00
|
|
|
|
void onException(const Exception & e)
|
|
|
|
|
{
|
2014-08-14 20:00:19 +00:00
|
|
|
|
resetOutput();
|
2015-02-27 20:35:26 +00:00
|
|
|
|
got_exception = true;
|
2014-08-14 20:00:19 +00:00
|
|
|
|
|
2015-04-03 19:06:16 +00:00
|
|
|
|
std::string text = e.displayText();
|
|
|
|
|
|
|
|
|
|
auto embedded_stack_trace_pos = text.find("Stack trace");
|
|
|
|
|
if (std::string::npos != embedded_stack_trace_pos && !config().getBool("stacktrace", false))
|
|
|
|
|
text.resize(embedded_stack_trace_pos);
|
|
|
|
|
|
2012-05-16 18:03:00 +00:00
|
|
|
|
std::cerr << "Received exception from server:" << std::endl
|
2015-07-09 18:26:50 +00:00
|
|
|
|
<< "Code: " << e.code() << ". " << text << std::endl;
|
2012-05-16 18:03:00 +00:00
|
|
|
|
}
|
2014-06-26 00:58:14 +00:00
|
|
|
|
|
|
|
|
|
|
2013-05-22 14:57:43 +00:00
|
|
|
|
void onProfileInfo(const BlockStreamProfileInfo & profile_info)
|
|
|
|
|
{
|
2013-05-22 15:35:25 +00:00
|
|
|
|
if (profile_info.hasAppliedLimit() && block_std_out)
|
2013-05-22 14:57:43 +00:00
|
|
|
|
block_std_out->setRowsBeforeLimit(profile_info.getRowsBeforeLimit());
|
|
|
|
|
}
|
2012-05-16 18:03:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void onEndOfStream()
|
|
|
|
|
{
|
2013-09-05 20:22:43 +00:00
|
|
|
|
if (block_std_out)
|
|
|
|
|
block_std_out->writeSuffix();
|
|
|
|
|
|
2014-08-14 20:00:19 +00:00
|
|
|
|
resetOutput();
|
2013-09-05 20:22:43 +00:00
|
|
|
|
|
2012-05-16 18:03:00 +00:00
|
|
|
|
if (is_interactive && !written_first_block)
|
|
|
|
|
std::cout << "Ok." << std::endl;
|
|
|
|
|
}
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
2014-03-05 13:48:45 +00:00
|
|
|
|
public:
|
|
|
|
|
void init(int argc, char ** argv)
|
2012-03-25 03:47:13 +00:00
|
|
|
|
{
|
2014-03-05 15:26:43 +00:00
|
|
|
|
|
2014-03-05 13:48:45 +00:00
|
|
|
|
/// Останавливаем внутреннюю обработку командной строки
|
|
|
|
|
stopOptionsProcessing();
|
|
|
|
|
|
2014-04-10 14:18:53 +00:00
|
|
|
|
#define DECLARE_SETTING(TYPE, NAME, DEFAULT) (#NAME, boost::program_options::value<std::string> (), "Settings.h")
|
2014-04-10 17:06:58 +00:00
|
|
|
|
#define DECLARE_LIMIT(TYPE, NAME, DEFAULT) (#NAME, boost::program_options::value<std::string> (), "Limits.h")
|
2014-04-10 14:18:53 +00:00
|
|
|
|
|
|
|
|
|
/// Перечисляем основные опции командной строки относящиеся к функциональности клиента,
|
|
|
|
|
/// а так же все параметры из Settings
|
2014-03-05 15:26:43 +00:00
|
|
|
|
boost::program_options::options_description main_description("Main options");
|
|
|
|
|
main_description.add_options()
|
2014-04-10 14:18:53 +00:00
|
|
|
|
("help", "produce help message")
|
2014-04-22 18:35:40 +00:00
|
|
|
|
("config-file,c", boost::program_options::value<std::string>(), "config-file path")
|
|
|
|
|
("host,h", boost::program_options::value<std::string>()->implicit_value("")->default_value("localhost"), "server host")
|
|
|
|
|
("port", boost::program_options::value<int>()->default_value(9000), "server port")
|
|
|
|
|
("user,u", boost::program_options::value<std::string>(), "user")
|
|
|
|
|
("password", boost::program_options::value<std::string>(), "password")
|
2014-05-14 17:35:51 +00:00
|
|
|
|
("query,q,e", boost::program_options::value<std::string>(), "query")
|
2014-04-22 18:35:40 +00:00
|
|
|
|
("database,d", boost::program_options::value<std::string>(), "database")
|
2014-03-28 13:45:24 +00:00
|
|
|
|
("multiline,m", "multiline")
|
|
|
|
|
("multiquery,n", "multiquery")
|
2015-03-28 19:53:34 +00:00
|
|
|
|
("format,f", boost::program_options::value<std::string>(), "default output format")
|
|
|
|
|
("vertical,E", "vertical output format, same as --format=Vertical or FORMAT Vertical or \\G at end of command")
|
2015-03-27 18:43:09 +00:00
|
|
|
|
("time,t", "print query execution time to stderr in non-interactive mode (for benchmarks)")
|
2015-04-03 19:06:16 +00:00
|
|
|
|
("stacktrace", "print stack traces of exceptions")
|
2015-06-28 07:05:42 +00:00
|
|
|
|
("progress", "print progress even in non-interactive mode")
|
2014-04-10 14:18:53 +00:00
|
|
|
|
APPLY_FOR_SETTINGS(DECLARE_SETTING)
|
2014-04-10 17:06:58 +00:00
|
|
|
|
APPLY_FOR_LIMITS(DECLARE_LIMIT)
|
2014-03-05 13:48:45 +00:00
|
|
|
|
;
|
2014-04-10 14:18:53 +00:00
|
|
|
|
#undef DECLARE_SETTING
|
2014-04-10 17:06:58 +00:00
|
|
|
|
#undef DECLARE_LIMIT
|
2014-04-09 16:42:37 +00:00
|
|
|
|
|
2014-03-05 15:26:43 +00:00
|
|
|
|
/// Перечисляем опции командной строки относящиеся к внешним таблицам
|
2014-04-10 17:06:58 +00:00
|
|
|
|
boost::program_options::options_description external_description("External tables options");
|
2014-03-05 15:26:43 +00:00
|
|
|
|
external_description.add_options()
|
2014-04-22 18:35:40 +00:00
|
|
|
|
("file", boost::program_options::value<std::string>(), "data file or - for stdin")
|
|
|
|
|
("name", boost::program_options::value<std::string>()->default_value("_data"), "name of the table")
|
|
|
|
|
("format", boost::program_options::value<std::string>()->default_value("TabSeparated"), "data format")
|
|
|
|
|
("structure", boost::program_options::value<std::string>(), "structure")
|
|
|
|
|
("types", boost::program_options::value<std::string>(), "types")
|
2014-03-05 15:26:43 +00:00
|
|
|
|
;
|
|
|
|
|
|
2014-04-09 16:42:37 +00:00
|
|
|
|
/// Парсим основные опции командной строки
|
|
|
|
|
boost::program_options::parsed_options parsed = boost::program_options::command_line_parser(argc, argv).options(main_description).allow_unregistered().run();
|
2014-04-10 14:18:53 +00:00
|
|
|
|
boost::program_options::variables_map options;
|
2014-04-09 16:42:37 +00:00
|
|
|
|
boost::program_options::store(parsed, options);
|
|
|
|
|
|
2014-04-15 13:36:32 +00:00
|
|
|
|
/// Демонстрация help message
|
2014-04-22 17:55:30 +00:00
|
|
|
|
if (options.count("help")
|
|
|
|
|
|| (options.count("host") && (options["host"].as<std::string>().empty() || options["host"].as<std::string>() == "elp")))
|
|
|
|
|
{
|
2014-04-10 14:18:53 +00:00
|
|
|
|
std::cout << main_description << "\n";
|
|
|
|
|
std::cout << external_description << "\n";
|
|
|
|
|
exit(0);
|
|
|
|
|
}
|
2014-04-15 13:36:32 +00:00
|
|
|
|
|
2014-04-09 16:42:37 +00:00
|
|
|
|
std::vector<std::string> to_pass_further = boost::program_options::collect_unrecognized(parsed.options, boost::program_options::include_positional);
|
2014-03-05 15:26:43 +00:00
|
|
|
|
|
2014-04-15 13:36:32 +00:00
|
|
|
|
/// Опции командной строки, составленные только из аргументов, не перечисленных в main_description.
|
2014-04-09 16:42:37 +00:00
|
|
|
|
char newargc = to_pass_further.size() + 1;
|
2014-04-22 17:55:30 +00:00
|
|
|
|
const char * new_argv[newargc];
|
|
|
|
|
|
|
|
|
|
new_argv[0] = "";
|
2014-04-09 16:42:37 +00:00
|
|
|
|
for (size_t i = 0; i < to_pass_further.size(); ++i)
|
2014-04-22 17:55:30 +00:00
|
|
|
|
new_argv[i + 1] = to_pass_further[i].c_str();
|
2014-04-09 16:42:37 +00:00
|
|
|
|
|
2014-04-10 14:18:53 +00:00
|
|
|
|
/// Разбиваем на интервалы внешних таблиц.
|
2014-04-09 16:42:37 +00:00
|
|
|
|
std::vector<int> positions;
|
2014-03-05 15:26:43 +00:00
|
|
|
|
positions.push_back(0);
|
2014-04-09 16:42:37 +00:00
|
|
|
|
for (int i = 1; i < newargc; ++i)
|
|
|
|
|
if (strcmp(new_argv[i], "--external") == 0)
|
2014-03-05 15:26:43 +00:00
|
|
|
|
positions.push_back(i);
|
2014-04-09 16:42:37 +00:00
|
|
|
|
positions.push_back(newargc);
|
2014-03-05 15:26:43 +00:00
|
|
|
|
|
2014-04-09 16:42:37 +00:00
|
|
|
|
size_t cnt = positions.size();
|
2014-03-05 15:26:43 +00:00
|
|
|
|
|
2014-05-15 11:57:52 +00:00
|
|
|
|
if (cnt == 2 && newargc > 1)
|
|
|
|
|
{
|
|
|
|
|
Exception e("Unknown option " + to_pass_further[0] + ". Maybe missed --external flag in front of it.", ErrorCodes::BAD_ARGUMENTS);
|
|
|
|
|
std::string text = e.displayText();
|
|
|
|
|
std::cerr << "Code: " << e.code() << ". " << text << std::endl;
|
|
|
|
|
exit(e.code());
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-12 13:14:16 +00:00
|
|
|
|
size_t stdin_count = 0;
|
2014-04-22 17:55:30 +00:00
|
|
|
|
for (size_t i = 1; i + 1 < cnt; ++i)
|
2014-03-05 15:26:43 +00:00
|
|
|
|
{
|
2014-05-15 11:57:52 +00:00
|
|
|
|
/// Парсим основные опции командной строки
|
|
|
|
|
boost::program_options::parsed_options parsed = boost::program_options::command_line_parser(positions[i + 1] - positions[i], &new_argv[positions[i]]).options(external_description).run();
|
2014-03-05 15:26:43 +00:00
|
|
|
|
boost::program_options::variables_map external_options;
|
2014-05-15 11:57:52 +00:00
|
|
|
|
boost::program_options::store(parsed, external_options);
|
2014-04-22 17:55:30 +00:00
|
|
|
|
|
2014-03-06 11:37:30 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
2014-04-22 18:35:40 +00:00
|
|
|
|
external_tables.emplace_back(external_options);
|
2014-03-12 13:14:16 +00:00
|
|
|
|
if (external_tables.back().file == "-")
|
2014-03-27 19:09:23 +00:00
|
|
|
|
++stdin_count;
|
2014-03-12 13:14:16 +00:00
|
|
|
|
if (stdin_count > 1)
|
|
|
|
|
throw Exception("Two or more external tables has stdin (-) set as --file field", ErrorCodes::BAD_ARGUMENTS);
|
2014-03-06 11:37:30 +00:00
|
|
|
|
}
|
|
|
|
|
catch (const Exception & e)
|
|
|
|
|
{
|
|
|
|
|
std::string text = e.displayText();
|
2014-03-12 13:14:16 +00:00
|
|
|
|
std::cerr << "Code: " << e.code() << ". " << text << std::endl;
|
2014-04-22 17:55:30 +00:00
|
|
|
|
std::cerr << "Table №" << i << std::endl << std::endl;
|
2014-03-06 11:37:30 +00:00
|
|
|
|
exit(e.code());
|
|
|
|
|
}
|
2014-03-05 15:26:43 +00:00
|
|
|
|
}
|
2014-03-05 13:48:45 +00:00
|
|
|
|
|
2014-04-15 13:36:32 +00:00
|
|
|
|
/// Извлекаем settings and limits из полученных options
|
2014-04-10 14:18:53 +00:00
|
|
|
|
#define EXTRACT_SETTING(TYPE, NAME, DEFAULT) \
|
|
|
|
|
if (options.count(#NAME)) \
|
2014-04-15 13:36:32 +00:00
|
|
|
|
context.setSetting(#NAME, options[#NAME].as<std::string>());
|
2014-04-10 14:18:53 +00:00
|
|
|
|
APPLY_FOR_SETTINGS(EXTRACT_SETTING)
|
2014-10-06 02:01:41 +00:00
|
|
|
|
APPLY_FOR_LIMITS(EXTRACT_SETTING)
|
2014-04-10 14:18:53 +00:00
|
|
|
|
#undef EXTRACT_SETTING
|
|
|
|
|
|
2014-03-05 15:26:43 +00:00
|
|
|
|
/// Сохраняем полученные данные во внутренний конфиг
|
2014-03-05 13:48:45 +00:00
|
|
|
|
if (options.count("config-file"))
|
|
|
|
|
config().setString("config-file", options["config-file"].as<std::string>());
|
|
|
|
|
if (options.count("host"))
|
|
|
|
|
config().setString("host", options["host"].as<std::string>());
|
|
|
|
|
if (options.count("query"))
|
|
|
|
|
config().setString("query", options["query"].as<std::string>());
|
|
|
|
|
if (options.count("database"))
|
|
|
|
|
config().setString("database", options["database"].as<std::string>());
|
|
|
|
|
|
|
|
|
|
if (options.count("port"))
|
|
|
|
|
config().setInt("port", options["port"].as<int>());
|
|
|
|
|
if (options.count("user"))
|
2014-04-18 15:52:27 +00:00
|
|
|
|
config().setString("user", options["user"].as<std::string>());
|
2014-03-05 13:48:45 +00:00
|
|
|
|
if (options.count("password"))
|
2014-04-18 15:52:27 +00:00
|
|
|
|
config().setString("password", options["password"].as<std::string>());
|
2014-03-05 13:48:45 +00:00
|
|
|
|
|
|
|
|
|
if (options.count("multiline"))
|
|
|
|
|
config().setBool("multiline", true);
|
2014-03-28 13:45:24 +00:00
|
|
|
|
if (options.count("multiquery"))
|
|
|
|
|
config().setBool("multiquery", true);
|
2015-03-28 19:53:34 +00:00
|
|
|
|
if (options.count("format"))
|
|
|
|
|
config().setString("format", options["format"].as<std::string>());
|
2014-12-18 15:48:49 +00:00
|
|
|
|
if (options.count("vertical"))
|
|
|
|
|
config().setBool("vertical", true);
|
2015-04-03 19:06:16 +00:00
|
|
|
|
if (options.count("stacktrace"))
|
|
|
|
|
config().setBool("stacktrace", true);
|
2015-06-28 07:05:42 +00:00
|
|
|
|
if (options.count("progress"))
|
|
|
|
|
config().setBool("progress", true);
|
2015-03-27 18:43:09 +00:00
|
|
|
|
if (options.count("time"))
|
|
|
|
|
print_time_to_stderr = true;
|
2012-03-25 03:47:13 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int main(int argc, char ** argv)
|
|
|
|
|
{
|
2012-03-26 02:48:08 +00:00
|
|
|
|
DB::Client client;
|
2014-10-17 23:42:50 +00:00
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
client.init(argc, argv);
|
|
|
|
|
}
|
|
|
|
|
catch (const boost::program_options::error & e)
|
|
|
|
|
{
|
|
|
|
|
std::cerr << "Bad arguments: " << e.what() << std::endl;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-26 02:48:08 +00:00
|
|
|
|
return client.run();
|
2012-03-25 03:47:13 +00:00
|
|
|
|
}
|