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
|
|
|
|
|
|
|
|
|
#include <tr1/unordered_set>
|
|
|
|
|
|
|
|
|
|
#include <boost/assign/list_inserter.hpp>
|
|
|
|
|
|
|
|
|
|
#include <Poco/File.h>
|
|
|
|
|
#include <Poco/SharedPtr.h>
|
|
|
|
|
#include <Poco/Util/Application.h>
|
|
|
|
|
|
|
|
|
|
#include <Yandex/Revision.h>
|
|
|
|
|
|
2012-03-26 02:48:08 +00:00
|
|
|
|
#include <statdaemons/Stopwatch.h>
|
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
#include <DB/Core/Exception.h>
|
|
|
|
|
#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>
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
2012-09-24 07:04:04 +00:00
|
|
|
|
#include <DB/DataStreams/AsynchronousBlockInputStream.h>
|
|
|
|
|
|
2012-03-26 04:17:17 +00:00
|
|
|
|
#include <DB/Parsers/ParserQuery.h>
|
|
|
|
|
#include <DB/Parsers/formatAST.h>
|
|
|
|
|
|
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>
|
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
|
|
|
|
/** Клиент командной строки СУБД ClickHouse.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace DB
|
|
|
|
|
{
|
|
|
|
|
|
2012-03-25 07:52:31 +00:00
|
|
|
|
using Poco::SharedPtr;
|
|
|
|
|
|
|
|
|
|
|
2012-05-09 08:16:09 +00:00
|
|
|
|
/** Пока существует объект этого класса - блокирует сигнал INT, при этом позволяет узнать, не пришёл ли он.
|
2012-11-10 02:21:39 +00:00
|
|
|
|
* Это нужно, чтобы можно было прервать выполнение запроса с помощью Ctrl+C.
|
2012-05-09 08:16:09 +00:00
|
|
|
|
* В один момент времени используйте только один экземпляр этого класса.
|
2013-03-06 13:53:09 +00:00
|
|
|
|
* Если метод check вернул true (пришёл сигнал), то следующие вызовы будут ждать следующий сигнал.
|
2012-05-09 08:16:09 +00:00
|
|
|
|
*/
|
|
|
|
|
class InterruptListener
|
|
|
|
|
{
|
2012-11-10 02:21:39 +00:00
|
|
|
|
private:
|
|
|
|
|
bool active;
|
|
|
|
|
|
2012-05-09 08:16:09 +00:00
|
|
|
|
public:
|
2012-11-10 02:21:39 +00:00
|
|
|
|
InterruptListener() : active(false)
|
2012-05-09 08:16:09 +00:00
|
|
|
|
{
|
2012-11-10 02:21:39 +00:00
|
|
|
|
block();
|
2012-05-09 08:16:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~InterruptListener()
|
|
|
|
|
{
|
2012-11-10 02:21:39 +00:00
|
|
|
|
unblock();
|
2012-05-09 08:16:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-11-10 02:21:39 +00:00
|
|
|
|
bool check()
|
2012-05-09 08:16:09 +00:00
|
|
|
|
{
|
2012-11-10 02:21:39 +00:00
|
|
|
|
if (!active)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
timespec timeout = { 0, 0 };
|
2012-05-09 08:16:09 +00:00
|
|
|
|
sigset_t sig_set;
|
|
|
|
|
|
|
|
|
|
if (sigemptyset(&sig_set)
|
2012-11-10 02:21:39 +00:00
|
|
|
|
|| sigaddset(&sig_set, SIGINT))
|
|
|
|
|
throwFromErrno("Cannot manipulate with signal set.", ErrorCodes::CANNOT_MANIPULATE_SIGSET);
|
|
|
|
|
|
|
|
|
|
if (-1 == sigtimedwait(&sig_set, NULL, &timeout))
|
2012-05-09 08:16:09 +00:00
|
|
|
|
{
|
2012-11-10 02:21:39 +00:00
|
|
|
|
if (errno == EAGAIN)
|
|
|
|
|
return false;
|
|
|
|
|
else
|
|
|
|
|
throwFromErrno("Cannot poll signal (sigtimedwait).", ErrorCodes::CANNOT_WAIT_FOR_SIGNAL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void block()
|
|
|
|
|
{
|
|
|
|
|
if (!active)
|
|
|
|
|
{
|
|
|
|
|
sigset_t sig_set;
|
|
|
|
|
|
|
|
|
|
if (sigemptyset(&sig_set)
|
|
|
|
|
|| sigaddset(&sig_set, SIGINT))
|
|
|
|
|
throwFromErrno("Cannot manipulate with signal set.", ErrorCodes::CANNOT_MANIPULATE_SIGSET);
|
|
|
|
|
|
|
|
|
|
if (pthread_sigmask(SIG_BLOCK, &sig_set, NULL))
|
|
|
|
|
throwFromErrno("Cannot block signal.", ErrorCodes::CANNOT_BLOCK_SIGNAL);
|
|
|
|
|
|
|
|
|
|
active = true;
|
2012-05-09 08:16:09 +00:00
|
|
|
|
}
|
2012-11-10 02:21:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Можно прекратить блокировать сигнал раньше, чем в деструкторе.
|
|
|
|
|
void unblock()
|
|
|
|
|
{
|
|
|
|
|
if (active)
|
|
|
|
|
{
|
|
|
|
|
sigset_t sig_set;
|
2012-05-09 08:16:09 +00:00
|
|
|
|
|
2012-11-10 02:21:39 +00:00
|
|
|
|
if (sigemptyset(&sig_set)
|
|
|
|
|
|| sigaddset(&sig_set, SIGINT))
|
|
|
|
|
throwFromErrno("Cannot manipulate with signal set.", ErrorCodes::CANNOT_MANIPULATE_SIGSET);
|
|
|
|
|
|
|
|
|
|
if (pthread_sigmask(SIG_UNBLOCK, &sig_set, NULL))
|
|
|
|
|
throwFromErrno("Cannot unblock signal.", ErrorCodes::CANNOT_UNBLOCK_SIGNAL);
|
|
|
|
|
|
|
|
|
|
active = false;
|
|
|
|
|
}
|
2012-05-09 08:16:09 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
class Client : public Poco::Util::Application
|
|
|
|
|
{
|
|
|
|
|
public:
|
2012-05-16 18:03:00 +00:00
|
|
|
|
Client() : is_interactive(true), stdin_is_not_tty(false), query_id(0),
|
2012-05-21 06:49:05 +00:00
|
|
|
|
format_max_block_size(0), std_in(STDIN_FILENO), std_out(STDOUT_FILENO), processed_rows(0),
|
2012-05-09 16:34:41 +00:00
|
|
|
|
rows_read_on_server(0), bytes_read_on_server(0), written_progress_chars(0), written_first_block(false) {}
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
typedef std::tr1::unordered_set<String> StringSet;
|
|
|
|
|
StringSet exit_strings;
|
|
|
|
|
|
2012-05-08 05:42:05 +00:00
|
|
|
|
bool is_interactive; /// Использовать readline интерфейс или batch режим.
|
|
|
|
|
bool stdin_is_not_tty; /// stdin - не терминал.
|
|
|
|
|
|
2012-05-16 18:03:00 +00:00
|
|
|
|
SharedPtr<Connection> connection; /// Соединение с БД.
|
2012-03-25 03:47:13 +00:00
|
|
|
|
String query; /// Текущий запрос.
|
|
|
|
|
UInt64 query_id; /// Идентификатор запроса. Его можно использовать, чтобы отменить запрос.
|
|
|
|
|
|
|
|
|
|
String format; /// Формат вывода результата в консоль.
|
|
|
|
|
size_t format_max_block_size; /// Максимальный размер блока при выводе в консоль.
|
2012-05-08 11:19:00 +00:00
|
|
|
|
String insert_format; /// Формат данных для INSERT-а при чтении их из stdin в batch режиме
|
|
|
|
|
size_t insert_format_max_block_size; /// Максимальный размер блока при чтении данных INSERT-а.
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
|
|
|
|
Context context;
|
|
|
|
|
|
2012-05-08 11:19:00 +00:00
|
|
|
|
/// Чтение из stdin для batch режима
|
|
|
|
|
ReadBufferFromFileDescriptor std_in;
|
|
|
|
|
BlockInputStreamPtr block_std_in;
|
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
/// Вывод в консоль
|
2012-03-25 07:52:31 +00:00
|
|
|
|
WriteBufferFromFileDescriptor std_out;
|
2012-03-25 03:47:13 +00:00
|
|
|
|
BlockOutputStreamPtr block_std_out;
|
2012-03-26 02:48:08 +00:00
|
|
|
|
|
|
|
|
|
String home_path;
|
|
|
|
|
|
|
|
|
|
/// Путь к файлу истории команд.
|
|
|
|
|
String history_file;
|
2012-03-26 04:17:17 +00:00
|
|
|
|
|
2012-05-21 06:49:05 +00:00
|
|
|
|
/// Строк прочитано или записано.
|
|
|
|
|
size_t processed_rows;
|
2012-03-26 04:17:17 +00:00
|
|
|
|
|
|
|
|
|
/// Распарсенный запрос. Оттуда берутся некоторые настройки (формат).
|
|
|
|
|
ASTPtr parsed_query;
|
2013-02-21 10:40:18 +00:00
|
|
|
|
|
|
|
|
|
/// Последнее полученное от сервера исключение.
|
|
|
|
|
ExceptionPtr last_exception;
|
2012-05-09 08:16:09 +00:00
|
|
|
|
|
2012-05-10 07:47:13 +00:00
|
|
|
|
Stopwatch watch;
|
|
|
|
|
|
2012-05-09 15:50:42 +00:00
|
|
|
|
size_t rows_read_on_server;
|
|
|
|
|
size_t bytes_read_on_server;
|
2012-05-09 15:15:45 +00:00
|
|
|
|
size_t written_progress_chars;
|
2012-05-09 16:34:41 +00:00
|
|
|
|
bool written_first_block;
|
2012-05-09 15:15:45 +00:00
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
|
|
|
|
void initialize(Poco::Util::Application & self)
|
|
|
|
|
{
|
|
|
|
|
Poco::Util::Application::initialize(self);
|
|
|
|
|
|
|
|
|
|
boost::assign::insert(exit_strings)
|
|
|
|
|
("exit")("quit")("logout")
|
|
|
|
|
("учше")("йгше")("дщпщге")
|
|
|
|
|
("exit;")("quit;")("logout;")
|
2012-03-25 07:52:31 +00:00
|
|
|
|
("учше;")("йгше;")("дщпщге;")
|
|
|
|
|
("q")("й");
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
2012-03-26 02:48:08 +00:00
|
|
|
|
const char * home_path_cstr = getenv("HOME");
|
|
|
|
|
if (!home_path_cstr)
|
2012-05-08 05:42:05 +00:00
|
|
|
|
throw Exception("Cannot get HOME environment variable");
|
2012-03-26 02:48:08 +00:00
|
|
|
|
else
|
|
|
|
|
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");
|
2012-03-26 02:48:08 +00:00
|
|
|
|
else if (Poco::File(home_path + "/.clickhouse-client/config.xml").exists())
|
|
|
|
|
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");
|
|
|
|
|
}
|
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);
|
|
|
|
|
}
|
|
|
|
|
catch (const DB::Exception & e)
|
|
|
|
|
{
|
|
|
|
|
std::cerr << "Code: " << e.code() << ". " << e.displayText() << std::endl
|
|
|
|
|
<< std::endl
|
|
|
|
|
<< "Stack trace:" << std::endl
|
|
|
|
|
<< e.getStackTrace().toString();
|
2013-02-21 10:40:18 +00:00
|
|
|
|
return e.code();
|
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
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
2012-03-26 02:48:08 +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
|
|
|
|
|
2012-05-16 18:03:00 +00:00
|
|
|
|
format = config().getString("format", is_interactive ? "PrettyCompact" : "TabSeparated");
|
2012-03-25 03:47:13 +00:00
|
|
|
|
format_max_block_size = config().getInt("format_max_block_size", DEFAULT_BLOCK_SIZE);
|
|
|
|
|
|
2012-05-21 06:49:05 +00:00
|
|
|
|
insert_format = "Values";
|
|
|
|
|
insert_format_max_block_size = config().getInt("insert_format_max_block_size", format_max_block_size);
|
|
|
|
|
|
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
|
|
|
|
{
|
2012-05-08 05:42:05 +00:00
|
|
|
|
/// Отключаем tab completion.
|
|
|
|
|
rl_bind_key('\t', rl_insert);
|
|
|
|
|
|
|
|
|
|
/// Загружаем историю команд, если есть.
|
|
|
|
|
history_file = config().getString("history_file", home_path + "/.clickhouse-client-history");
|
|
|
|
|
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-08-31 18:44:05 +00:00
|
|
|
|
/// Инициализируем DateLUT, чтобы потраченное время не отображалось, как время, потраченное на запрос.
|
|
|
|
|
Yandex::DateLUTSingleton::instance();
|
|
|
|
|
|
2012-05-08 05:42:05 +00:00
|
|
|
|
loop();
|
|
|
|
|
|
|
|
|
|
std::cout << "Bye." << std::endl;
|
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();
|
2013-02-21 10:40:18 +00:00
|
|
|
|
|
|
|
|
|
if (last_exception)
|
|
|
|
|
return last_exception->code();
|
|
|
|
|
|
|
|
|
|
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", "");
|
|
|
|
|
|
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)
|
2012-05-30 06:46:57 +00:00
|
|
|
|
std::cout << "Connecting to " << (!default_database.empty() ? default_database + "@" : "") << host << ":" << port << "." << std::endl;
|
2012-05-09 13:12:38 +00:00
|
|
|
|
|
2012-08-02 17:33:31 +00:00
|
|
|
|
connection = new Connection(host, port, default_database, context.getDataTypeFactory(), "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
|
|
|
|
}
|
|
|
|
|
|
2013-02-28 10:24:09 +00:00
|
|
|
|
|
|
|
|
|
static bool IsWhitespace(char c)
|
|
|
|
|
{
|
|
|
|
|
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f';
|
|
|
|
|
}
|
|
|
|
|
|
2012-05-09 13:12:38 +00:00
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
void loop()
|
|
|
|
|
{
|
2013-02-28 10:24:09 +00:00
|
|
|
|
String 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_);
|
2013-02-28 10:24:09 +00:00
|
|
|
|
|
|
|
|
|
size_t ws = line.size();
|
|
|
|
|
while (ws > 0 && IsWhitespace(line[ws-1]))
|
|
|
|
|
--ws;
|
|
|
|
|
|
2013-02-28 10:28:38 +00:00
|
|
|
|
if (ws == 0 && query.empty())
|
2012-03-26 04:17:17 +00:00
|
|
|
|
continue;
|
2013-02-28 10:24:09 +00:00
|
|
|
|
|
|
|
|
|
bool ends_with_semicolon = line[ws - 1] == ';';
|
|
|
|
|
bool ends_with_backslash = line[ws - 1] == '\\';
|
2012-03-26 04:17:17 +00:00
|
|
|
|
|
2013-02-28 10:24:09 +00:00
|
|
|
|
if (append_history(1, history_file.c_str()))
|
2012-03-26 02:48:08 +00:00
|
|
|
|
throwFromErrno("Cannot append history to file " + history_file, ErrorCodes::CANNOT_APPEND_HISTORY);
|
2013-02-28 10:24:09 +00:00
|
|
|
|
|
|
|
|
|
if (ends_with_backslash)
|
|
|
|
|
line = line.substr(0, ws - 1);
|
|
|
|
|
|
|
|
|
|
query += line;
|
|
|
|
|
|
2013-02-28 10:25:15 +00:00
|
|
|
|
if (!ends_with_backslash && (ends_with_semicolon || !config().hasOption("multiline")))
|
2013-02-28 10:24:09 +00:00
|
|
|
|
{
|
2013-03-19 12:36:13 +00:00
|
|
|
|
add_history(query.c_str());
|
|
|
|
|
|
2013-02-28 10:24:09 +00:00
|
|
|
|
if (!process(query))
|
|
|
|
|
break;
|
|
|
|
|
query = "";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
query += '\n';
|
|
|
|
|
}
|
2012-03-25 03:47:13 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-05-08 05:42:05 +00:00
|
|
|
|
void nonInteractive()
|
|
|
|
|
{
|
|
|
|
|
if (config().has("query"))
|
|
|
|
|
process(config().getString("query"));
|
|
|
|
|
else
|
|
|
|
|
{
|
2013-02-01 18:55:31 +00:00
|
|
|
|
/** В случае, если параметр query не задан, то запрос будет читаться из stdin.
|
|
|
|
|
* При этом, запрос будет читаться не потоково (целиком в оперативку).
|
|
|
|
|
* Поддерживается только один запрос в stdin.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
String stdin_str;
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
ReadBufferFromFileDescriptor in(STDIN_FILENO);
|
|
|
|
|
WriteBufferFromString out(stdin_str);
|
|
|
|
|
copyData(in, out);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
process(stdin_str);
|
2012-05-08 05:42:05 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
bool process(const String & line)
|
|
|
|
|
{
|
|
|
|
|
if (exit_strings.end() != exit_strings.find(line))
|
|
|
|
|
return false;
|
|
|
|
|
|
2012-05-16 18:03:00 +00:00
|
|
|
|
block_std_in = NULL;
|
|
|
|
|
block_std_out = NULL;
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
/// Некоторые части запроса выполняются на стороне клиента (форматирование результата). Поэтому, распарсим запрос.
|
|
|
|
|
if (!parseQuery())
|
|
|
|
|
return true;
|
2012-05-09 08:16:09 +00:00
|
|
|
|
|
|
|
|
|
++query_id;
|
2012-05-21 06:49:05 +00:00
|
|
|
|
processed_rows = 0;
|
2012-05-09 15:50:42 +00:00
|
|
|
|
rows_read_on_server = 0;
|
|
|
|
|
bytes_read_on_server = 0;
|
2012-05-09 16:34:41 +00:00
|
|
|
|
written_progress_chars = 0;
|
|
|
|
|
written_first_block = false;
|
2012-05-09 13:12:38 +00:00
|
|
|
|
|
2012-06-25 01:22:30 +00:00
|
|
|
|
/// Запрос INSERT (но только тот, что требует передачи данных - не INSERT SELECT), обрабатывается отдельным способом.
|
|
|
|
|
const ASTInsertQuery * insert = dynamic_cast<const ASTInsertQuery *>(&*parsed_query);
|
|
|
|
|
|
|
|
|
|
if (insert && !insert->select)
|
2012-05-21 06:49:05 +00:00
|
|
|
|
processInsertQuery();
|
|
|
|
|
else
|
|
|
|
|
processOrdinaryQuery();
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
2012-05-08 05:42:05 +00:00
|
|
|
|
if (is_interactive)
|
|
|
|
|
std::cout << std::endl
|
2012-05-21 06:49:05 +00:00
|
|
|
|
<< processed_rows << " rows in set. Elapsed: " << watch.elapsedSeconds() << " sec."
|
2012-05-08 05:42:05 +00:00
|
|
|
|
<< std::endl << std::endl;
|
2012-03-26 02:48:08 +00:00
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-05-21 06:49:05 +00:00
|
|
|
|
/// Обработать запрос, который не требует передачи блоков данных на сервер.
|
|
|
|
|
void processOrdinaryQuery()
|
|
|
|
|
{
|
|
|
|
|
connection->sendQuery(query, query_id, QueryProcessingStage::Complete);
|
|
|
|
|
receiveResult();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Обработать запрос, который требует передачи блоков данных на сервер.
|
|
|
|
|
void processInsertQuery()
|
|
|
|
|
{
|
|
|
|
|
/// Отправляем часть запроса - без данных, так как данные будут отправлены отдельно.
|
|
|
|
|
const ASTInsertQuery & parsed_insert_query = dynamic_cast<const ASTInsertQuery &>(*parsed_query);
|
|
|
|
|
String query_without_data = parsed_insert_query.data
|
|
|
|
|
? query.substr(0, parsed_insert_query.data - query.data())
|
|
|
|
|
: query;
|
|
|
|
|
|
2012-05-28 19:57:44 +00:00
|
|
|
|
if ((is_interactive && !parsed_insert_query.data) || (!is_interactive && std_in.eof()))
|
|
|
|
|
throw Exception("No data to insert", ErrorCodes::NO_DATA_TO_INSERT);
|
|
|
|
|
|
2012-05-21 06:49:05 +00:00
|
|
|
|
connection->sendQuery(query_without_data, query_id, QueryProcessingStage::Complete);
|
|
|
|
|
|
|
|
|
|
/// Получим структуру таблицы
|
|
|
|
|
Block sample = receiveSampleBlock();
|
|
|
|
|
|
|
|
|
|
sendData(sample);
|
|
|
|
|
receivePacket();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-03-26 04:17:17 +00:00
|
|
|
|
bool parseQuery()
|
|
|
|
|
{
|
|
|
|
|
ParserQuery parser;
|
|
|
|
|
std::string expected;
|
|
|
|
|
|
|
|
|
|
const char * begin = query.data();
|
|
|
|
|
const char * end = begin + query.size();
|
|
|
|
|
const char * pos = begin;
|
|
|
|
|
|
|
|
|
|
bool parse_res = parser.parse(pos, end, parsed_query, expected);
|
|
|
|
|
|
|
|
|
|
/// Распарсенный запрос должен заканчиваться на конец входных данных или на точку с запятой.
|
|
|
|
|
if (!parse_res || (pos != end && *pos != ';'))
|
|
|
|
|
{
|
2012-05-08 05:42:05 +00:00
|
|
|
|
std::cerr << "Syntax error: failed at position "
|
2012-03-26 04:17:17 +00:00
|
|
|
|
<< (pos - begin) << ": "
|
|
|
|
|
<< std::string(pos, std::min(SHOW_CHARS_ON_SYNTAX_ERROR, end - pos))
|
|
|
|
|
<< ", expected " << (parse_res ? "end of query" : expected) << "."
|
|
|
|
|
<< std::endl << std::endl;
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2012-05-08 05:42:05 +00:00
|
|
|
|
if (is_interactive)
|
|
|
|
|
{
|
|
|
|
|
std::cout << std::endl;
|
|
|
|
|
formatAST(*parsed_query, std::cout);
|
|
|
|
|
std::cout << std::endl << std::endl;
|
|
|
|
|
}
|
2012-03-26 04:17:17 +00:00
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-05-21 06:49:05 +00:00
|
|
|
|
void sendData(Block & sample)
|
2012-05-08 11:19:00 +00:00
|
|
|
|
{
|
|
|
|
|
/// Если нужно отправить данные INSERT-а.
|
|
|
|
|
const ASTInsertQuery * parsed_insert_query = dynamic_cast<const ASTInsertQuery *>(&*parsed_query);
|
|
|
|
|
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
|
|
|
|
}
|
2012-05-28 19:57:44 +00:00
|
|
|
|
else if (!is_interactive && !std_in.eof())
|
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 запросе.
|
|
|
|
|
if (ASTInsertQuery * insert = dynamic_cast<ASTInsertQuery *>(&*parsed_query))
|
|
|
|
|
if (!insert->format.empty())
|
|
|
|
|
current_format = insert->format;
|
|
|
|
|
|
2012-09-24 07:04:04 +00:00
|
|
|
|
block_std_in = new AsynchronousBlockInputStream(context.getFormatFactory().getInput(
|
|
|
|
|
current_format, buf, sample, insert_format_max_block_size, context.getDataTypeFactory()));
|
2012-05-21 06:49:05 +00:00
|
|
|
|
block_std_in->readPrefix();
|
|
|
|
|
|
|
|
|
|
while (true)
|
2012-05-08 11:19:00 +00:00
|
|
|
|
{
|
2012-05-21 06:49:05 +00:00
|
|
|
|
Block block = block_std_in->read();
|
|
|
|
|
connection->sendData(block);
|
|
|
|
|
processed_rows += block.rows();
|
|
|
|
|
|
|
|
|
|
if (!block)
|
|
|
|
|
break;
|
2012-05-08 11:19:00 +00:00
|
|
|
|
}
|
2012-05-21 06:49:05 +00:00
|
|
|
|
|
|
|
|
|
block_std_in->readSuffix();
|
2012-05-08 11:19:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
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;
|
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
|
|
|
|
|
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
|
|
|
|
/** Получить блок - пример структуры таблицы, в которую будут вставляться данные.
|
|
|
|
|
*/
|
|
|
|
|
Block receiveSampleBlock()
|
|
|
|
|
{
|
|
|
|
|
Connection::Packet packet = connection->receivePacket();
|
|
|
|
|
|
|
|
|
|
switch (packet.type)
|
|
|
|
|
{
|
|
|
|
|
case Protocol::Server::Data:
|
|
|
|
|
return packet.block;
|
|
|
|
|
|
|
|
|
|
default:
|
2012-06-19 22:46:02 +00:00
|
|
|
|
throw Exception("Unexpected packet from server (expected Data, got "
|
2012-06-19 23:02:27 +00:00
|
|
|
|
+ String(Protocol::Server::toString(Protocol::Server::Enum(packet.type))) + ")", ErrorCodes::UNEXPECTED_PACKET_FROM_SERVER);
|
2012-05-21 06:49:05 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-05-16 18:03:00 +00:00
|
|
|
|
void onData(Block & block)
|
2012-03-25 03:47:13 +00:00
|
|
|
|
{
|
2012-05-09 15:15:45 +00:00
|
|
|
|
if (written_progress_chars)
|
|
|
|
|
{
|
2012-05-09 16:34:41 +00:00
|
|
|
|
if (written_first_block)
|
|
|
|
|
for (size_t i = 0; i < written_progress_chars; ++i)
|
|
|
|
|
std::cerr << "\b \b";
|
|
|
|
|
else
|
|
|
|
|
std::cerr << "\n\n";
|
|
|
|
|
|
2012-05-09 15:15:45 +00:00
|
|
|
|
written_progress_chars = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2012-05-09 16:34:41 +00:00
|
|
|
|
written_first_block = true;
|
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
if (block)
|
|
|
|
|
{
|
2012-05-21 06:49:05 +00:00
|
|
|
|
processed_rows += block.rows();
|
2012-03-25 07:52:31 +00:00
|
|
|
|
if (!block_std_out)
|
2012-03-26 04:17:17 +00:00
|
|
|
|
{
|
|
|
|
|
String current_format = format;
|
|
|
|
|
|
2013-02-20 13:14:12 +00:00
|
|
|
|
/// Формат может быть указан в запросе.
|
|
|
|
|
if (ASTQueryWithOutput * query_with_output = dynamic_cast<ASTQueryWithOutput *>(&*parsed_query))
|
|
|
|
|
if (query_with_output->format)
|
|
|
|
|
if (ASTIdentifier * id = dynamic_cast<ASTIdentifier *>(&*query_with_output->format))
|
2012-03-26 04:17:17 +00:00
|
|
|
|
current_format = id->name;
|
|
|
|
|
|
2013-05-22 14:57:43 +00:00
|
|
|
|
block_std_out = context.getFormatFactory().getOutput(current_format, std_out, block);
|
2012-05-08 11:19:00 +00:00
|
|
|
|
block_std_out->writePrefix();
|
2012-03-26 04:17:17 +00:00
|
|
|
|
}
|
2012-03-25 07:52:31 +00:00
|
|
|
|
|
2012-03-25 03:47:13 +00:00
|
|
|
|
block_std_out->write(block);
|
2012-03-25 07:52:31 +00:00
|
|
|
|
std_out.next();
|
2012-03-25 03:47:13 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
2012-05-08 11:19:00 +00:00
|
|
|
|
{
|
|
|
|
|
if (block_std_out)
|
|
|
|
|
block_std_out->writeSuffix();
|
|
|
|
|
|
|
|
|
|
std_out.next();
|
|
|
|
|
}
|
2012-03-25 03:47:13 +00:00
|
|
|
|
}
|
2012-05-08 05:42:05 +00:00
|
|
|
|
|
|
|
|
|
|
2012-05-16 18:03:00 +00:00
|
|
|
|
void onProgress(const Progress & progress)
|
2012-05-08 11:19:00 +00:00
|
|
|
|
{
|
2012-05-16 18:03:00 +00:00
|
|
|
|
rows_read_on_server += progress.rows;
|
|
|
|
|
bytes_read_on_server += progress.bytes;
|
2012-05-09 15:50:42 +00:00
|
|
|
|
|
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",
|
|
|
|
|
"\033[1;37m↗\033[0m",
|
2012-05-09 15:15:45 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (is_interactive)
|
|
|
|
|
{
|
2012-05-10 07:47:13 +00:00
|
|
|
|
std::cerr << std::string(written_progress_chars, '\b');
|
2012-05-09 16:34:41 +00:00
|
|
|
|
|
|
|
|
|
std::stringstream message;
|
2012-05-10 07:47:13 +00:00
|
|
|
|
message << indicators[increment % 8]
|
|
|
|
|
<< std::fixed << std::setprecision(3)
|
|
|
|
|
<< " Progress: " << rows_read_on_server << " rows, " << bytes_read_on_server / 1000000.0 << " MB";
|
|
|
|
|
|
|
|
|
|
size_t elapsed_ns = watch.elapsed();
|
|
|
|
|
if (elapsed_ns)
|
|
|
|
|
message << " ("
|
2013-02-16 22:17:01 +00:00
|
|
|
|
<< rows_read_on_server * 1000000000.0 / elapsed_ns << " rows/s., "
|
2012-05-10 07:47:13 +00:00
|
|
|
|
<< bytes_read_on_server * 1000.0 / elapsed_ns << " MB/s.) ";
|
|
|
|
|
else
|
|
|
|
|
message << ". ";
|
|
|
|
|
|
2012-05-09 16:34:41 +00:00
|
|
|
|
written_progress_chars = message.str().size() - 13;
|
|
|
|
|
std::cerr << message.rdbuf();
|
2012-05-09 15:15:45 +00:00
|
|
|
|
++increment;
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-05-16 18:03:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void onException(const Exception & e)
|
|
|
|
|
{
|
|
|
|
|
std::cerr << "Received exception from server:" << std::endl
|
|
|
|
|
<< "Code: " << e.code() << ". " << e.displayText();
|
|
|
|
|
}
|
2013-05-22 14:57:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void onProfileInfo(const BlockStreamProfileInfo & profile_info)
|
|
|
|
|
{
|
|
|
|
|
if (profile_info.hasAppliedLimit())
|
|
|
|
|
block_std_out->setRowsBeforeLimit(profile_info.getRowsBeforeLimit());
|
|
|
|
|
}
|
2012-05-16 18:03:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void onEndOfStream()
|
|
|
|
|
{
|
|
|
|
|
if (is_interactive && !written_first_block)
|
|
|
|
|
std::cout << "Ok." << std::endl;
|
|
|
|
|
}
|
2012-03-25 03:47:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void defineOptions(Poco::Util::OptionSet & options)
|
|
|
|
|
{
|
|
|
|
|
Poco::Util::Application::defineOptions(options);
|
|
|
|
|
|
|
|
|
|
options.addOption(
|
2012-05-09 13:12:38 +00:00
|
|
|
|
Poco::Util::Option("config-file", "c")
|
2012-03-25 03:47:13 +00:00
|
|
|
|
.required(false)
|
|
|
|
|
.repeatable(false)
|
|
|
|
|
.argument("<file>")
|
|
|
|
|
.binding("config-file"));
|
|
|
|
|
|
|
|
|
|
options.addOption(
|
|
|
|
|
Poco::Util::Option("host", "h")
|
|
|
|
|
.required(false)
|
|
|
|
|
.repeatable(false)
|
|
|
|
|
.argument("<host>")
|
|
|
|
|
.binding("host"));
|
|
|
|
|
|
|
|
|
|
options.addOption(
|
|
|
|
|
Poco::Util::Option("port", "p")
|
|
|
|
|
.required(false)
|
|
|
|
|
.repeatable(false)
|
|
|
|
|
.argument("<number>")
|
|
|
|
|
.binding("port"));
|
2012-05-08 05:42:05 +00:00
|
|
|
|
|
|
|
|
|
options.addOption(
|
|
|
|
|
Poco::Util::Option("query", "e")
|
|
|
|
|
.required(false)
|
|
|
|
|
.repeatable(false)
|
|
|
|
|
.argument("<string>")
|
|
|
|
|
.binding("query"));
|
2012-05-30 06:46:57 +00:00
|
|
|
|
|
|
|
|
|
options.addOption(
|
|
|
|
|
Poco::Util::Option("database", "d")
|
|
|
|
|
.required(false)
|
|
|
|
|
.repeatable(false)
|
|
|
|
|
.argument("<string>")
|
|
|
|
|
.binding("database"));
|
2013-02-28 10:24:09 +00:00
|
|
|
|
|
|
|
|
|
options.addOption(
|
|
|
|
|
Poco::Util::Option("multiline", "m")
|
|
|
|
|
.required(false)
|
|
|
|
|
.repeatable(false)
|
|
|
|
|
.binding("multiline"));
|
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;
|
|
|
|
|
client.init(argc, argv);
|
|
|
|
|
return client.run();
|
2012-03-25 03:47:13 +00:00
|
|
|
|
}
|