2018-03-23 16:05:14 +00:00
# include <port/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>
2018-02-26 13:24:06 +00:00
# include <map>
2012-03-25 03:47:13 +00:00
# include <iostream>
2012-03-26 02:48:08 +00:00
# include <fstream>
# include <iomanip>
2014-01-08 16:33:28 +00:00
# include <unordered_set>
2014-12-05 13:10:55 +00:00
# include <algorithm>
2017-11-20 04:15:43 +00:00
# include <optional>
2014-03-05 13:48:45 +00:00
# include <boost/program_options.hpp>
2018-02-26 13:24:06 +00:00
# include <boost/algorithm/string/replace.hpp>
2012-03-25 03:47:13 +00:00
# include <Poco/File.h>
# include <Poco/Util/Application.h>
2017-12-19 19:46:02 +00:00
# include <common/readline_use.h>
# include <common/find_first_symbols.h>
2018-05-21 17:27:18 +00:00
# include <common/SetTerminalEcho.h>
2017-04-01 09:19:00 +00:00
# include <Common/ClickHouseRevision.h>
# include <Common/Stopwatch.h>
# include <Common/Exception.h>
# include <Common/ShellCommand.h>
2017-07-15 03:48:36 +00:00
# include <Common/ExternalTable.h>
# include <Common/UnicodeBar.h>
# include <Common/formatReadable.h>
# include <Common/NetException.h>
2017-12-08 15:02:17 +00:00
# include <Common/Throttler.h>
2018-01-21 21:59:03 +00:00
# include <Common/StringUtils/StringUtils.h>
2017-07-15 03:48:36 +00:00
# include <Common/typeid_cast.h>
2018-03-14 19:38:48 +00:00
# include <Common/Config/ConfigProcessor.h>
2017-04-01 09:19:00 +00:00
# include <Core/Types.h>
# include <Core/QueryProcessingStage.h>
# include <IO/ReadBufferFromFileDescriptor.h>
# include <IO/WriteBufferFromFileDescriptor.h>
# include <IO/WriteBufferFromFile.h>
# include <IO/ReadBufferFromMemory.h>
2018-03-23 16:55:35 +00:00
# include <IO/ReadBufferFromString.h>
2017-04-01 09:19:00 +00:00
# include <IO/ReadHelpers.h>
# include <IO/WriteHelpers.h>
# include <DataStreams/AsynchronousBlockInputStream.h>
# include <Parsers/ParserQuery.h>
# include <Parsers/ASTSetQuery.h>
# include <Parsers/ASTUseQuery.h>
# include <Parsers/ASTInsertQuery.h>
2018-02-25 06:34:20 +00:00
# include <Parsers/ASTSelectWithUnionQuery.h>
2017-04-01 09:19:00 +00:00
# include <Parsers/ASTQueryWithOutput.h>
# include <Parsers/ASTLiteral.h>
# include <Parsers/ASTIdentifier.h>
# include <Parsers/formatAST.h>
# include <Parsers/parseQuery.h>
# include <Interpreters/Context.h>
# include <Client/Connection.h>
2018-06-05 20:09:51 +00:00
# include <Common/InterruptListener.h>
2017-07-13 00:04:06 +00:00
# include <Functions/registerFunctions.h>
# include <AggregateFunctions/registerAggregateFunctions.h>
2018-05-21 17:27:18 +00:00
# include <ext/scope_guard.h>
2016-05-17 00:59:24 +00:00
2014-08-15 00:08:15 +00:00
/// http://en.wikipedia.org/wiki/ANSI_escape_code
2017-04-07 17:56:03 +00:00
/// Similar codes \e[s, \e[u don't work in VT100 and Mosh.
# define SAVE_CURSOR_POSITION "\e7"
# define RESTORE_CURSOR_POSITION "\e8"
2014-08-15 00:08:15 +00:00
# define CLEAR_TO_END_OF_LINE "\033[K"
2017-04-07 17:56:03 +00:00
2017-01-18 13:53:20 +00:00
/// This codes are possibly not supported everywhere.
2014-08-15 00:08:15 +00:00
# define DISABLE_LINE_WRAPPING "\033[?7l"
# define ENABLE_LINE_WRAPPING "\033[?7h"
2012-03-25 03:47:13 +00:00
namespace DB
{
2016-01-12 02:21:15 +00:00
namespace ErrorCodes
{
2017-04-01 07:20:54 +00:00
extern const int POCO_EXCEPTION ;
extern const int STD_EXCEPTION ;
extern const int UNKNOWN_EXCEPTION ;
extern const int NETWORK_ERROR ;
extern const int NO_DATA_TO_INSERT ;
extern const int BAD_ARGUMENTS ;
extern const int CANNOT_READ_HISTORY ;
extern const int CANNOT_APPEND_HISTORY ;
extern const int UNKNOWN_PACKET_FROM_SERVER ;
extern const int UNEXPECTED_PACKET_FROM_SERVER ;
extern const int CLIENT_OUTPUT_FORMAT_SPECIFIED ;
2016-01-12 02:21:15 +00:00
}
2012-03-25 03:47:13 +00:00
class Client : public Poco : : Util : : Application
{
public :
2017-04-01 07:20:54 +00:00
Client ( ) { }
2014-01-08 16:33:28 +00:00
2012-03-25 03:47:13 +00:00
private :
2017-04-01 07:20:54 +00:00
using StringSet = std : : unordered_set < String > ;
2018-01-10 00:04:08 +00:00
StringSet exit_strings
{
2017-04-01 07:20:54 +00:00
" exit " , " quit " , " logout " ,
" учше " , " йгше " , " дщпщге " ,
" exit; " , " quit; " , " logout; " ,
" учшеж " , " йгшеж " , " дщпщгеж " ,
" q " , " й " , " \\ q " , " \\ Q " , " \\ й " , " \\ Й " , " :q " , " Жй "
} ;
2017-07-12 00:49:30 +00:00
bool is_interactive = true ; /// Use either readline interface or batch mode.
2017-04-01 07:20:54 +00:00
bool need_render_progress = true ; /// Render query execution progress.
2017-07-12 00:49:30 +00:00
bool echo_queries = false ; /// Print queries before execution in batch mode.
bool print_time_to_stderr = false ; /// Output execution time to stderr in batch mode.
bool stdin_is_not_tty = false ; /// stdin is not a terminal.
2012-05-08 05:42:05 +00:00
2017-04-01 07:20:54 +00:00
winsize terminal_size { } ; /// Terminal size is needed to render progress bar.
2014-10-25 20:27:37 +00:00
2017-04-01 07:20:54 +00:00
std : : unique_ptr < Connection > connection ; /// Connection to DB.
2018-03-24 23:50:30 +00:00
String query_id ; /// Current query_id.
2017-04-01 07:20:54 +00:00
String query ; /// Current query.
2012-03-25 03:47:13 +00:00
2017-07-12 00:49:30 +00:00
String format ; /// Query results output format.
bool is_default_format = true ; /// false, if format is set in the config or command line.
2017-04-01 07:20:54 +00:00
size_t format_max_block_size = 0 ; /// Max block size for console output.
String insert_format ; /// Format of INSERT data that is read from stdin in batch mode.
size_t insert_format_max_block_size = 0 ; /// Max block size when reading INSERT data.
2017-12-13 20:53:53 +00:00
size_t max_client_network_bandwidth = 0 ; /// The maximum speed of data exchange over the network for the client in bytes per second.
2012-03-25 03:47:13 +00:00
2017-04-01 07:20:54 +00:00
bool has_vertical_output_suffix = false ; /// Is \G present at the end of the query string?
2014-12-19 23:02:18 +00:00
2017-06-19 20:31:23 +00:00
Context context = Context : : createGlobal ( ) ;
2012-03-25 03:47:13 +00:00
2017-04-01 07:20:54 +00:00
/// Buffer that reads from stdin in batch mode.
ReadBufferFromFileDescriptor std_in { STDIN_FILENO } ;
2012-05-08 11:19:00 +00:00
2017-04-01 07:20:54 +00:00
/// Console output.
WriteBufferFromFileDescriptor std_out { STDOUT_FILENO } ;
std : : unique_ptr < ShellCommand > pager_cmd ;
/// The user can specify to redirect query output to a file.
2017-11-20 04:15:43 +00:00
std : : optional < WriteBufferFromFile > out_file_buf ;
2017-04-01 07:20:54 +00:00
BlockOutputStreamPtr block_out_stream ;
2017-03-13 17:35:17 +00:00
2017-04-01 07:20:54 +00:00
String home_path ;
2017-03-13 17:35:17 +00:00
2017-04-01 07:20:54 +00:00
String current_profile ;
2017-03-13 17:35:17 +00:00
2018-02-26 13:48:07 +00:00
String prompt_by_server_display_name ;
2018-02-26 13:24:06 +00:00
2017-04-01 07:20:54 +00:00
/// Path to a file containing command history.
String history_file ;
2017-03-13 17:35:17 +00:00
2017-04-01 07:20:54 +00:00
/// How many rows have been read or written.
size_t processed_rows = 0 ;
2017-03-13 17:35:17 +00:00
2017-04-01 07:20:54 +00:00
/// Parsed query. Is used to determine some settings (e.g. format, output file).
ASTPtr parsed_query ;
2017-03-13 17:35:17 +00:00
2017-04-01 07:20:54 +00:00
/// The last exception that was received from the server. Is used for the return code in batch mode.
std : : unique_ptr < Exception > last_exception ;
2017-03-13 17:35:17 +00:00
2017-04-01 07:20:54 +00:00
/// If the last query resulted in exception.
bool got_exception = false ;
2017-11-02 14:45:23 +00:00
String server_version ;
2018-02-26 06:49:17 +00:00
String server_display_name ;
2017-03-13 17:35:17 +00:00
2017-04-01 07:20:54 +00:00
Stopwatch watch ;
2017-03-13 17:35:17 +00:00
2017-04-01 07:20:54 +00:00
/// The server periodically sends information about how much data was read since last time.
Progress progress ;
bool show_progress_bar = false ;
2017-03-13 17:35:17 +00:00
2017-04-01 07:20:54 +00:00
size_t written_progress_chars = 0 ;
bool written_first_block = false ;
2017-03-13 17:35:17 +00:00
2017-04-01 07:20:54 +00:00
/// External tables info.
std : : list < ExternalTable > external_tables ;
2017-03-13 17:35:17 +00:00
2018-03-23 18:20:54 +00:00
struct ConnectionParameters
{
String host ;
UInt16 port ;
String default_database ;
String user ;
String password ;
2018-03-29 01:41:06 +00:00
Protocol : : Secure security ;
2018-03-23 18:20:54 +00:00
Protocol : : Compression compression ;
ConnectionTimeouts timeouts ;
ConnectionParameters ( ) { }
ConnectionParameters ( const Poco : : Util : : AbstractConfiguration & config )
{
bool is_secure = config . getBool ( " secure " , false ) ;
security = is_secure
2018-03-29 01:41:06 +00:00
? Protocol : : Secure : : Enable
: Protocol : : Secure : : Disable ;
2018-03-23 18:20:54 +00:00
host = config . getString ( " host " , " localhost " ) ;
port = config . getInt ( " port " ,
2018-04-10 18:25:08 +00:00
config . getInt ( is_secure ? " tcp_port_secure " : " tcp_port " ,
2018-03-23 18:20:54 +00:00
is_secure ? DBMS_DEFAULT_SECURE_PORT : DBMS_DEFAULT_PORT ) ) ;
default_database = config . getString ( " database " , " " ) ;
user = config . getString ( " user " , " " ) ;
2018-05-21 17:27:18 +00:00
if ( config . getBool ( " ask-password " , false ) )
{
if ( config . has ( " password " ) )
throw Exception ( " Specified both --password and --ask-password. Remove one of them " , ErrorCodes : : BAD_ARGUMENTS ) ;
std : : cout < < " Password for user " < < user < < " : " ;
SetTerminalEcho ( false ) ;
SCOPE_EXIT ( {
SetTerminalEcho ( true ) ;
} ) ;
std : : getline ( std : : cin , password ) ;
std : : cout < < std : : endl ;
}
else
{
password = config . getString ( " password " , " " ) ;
}
2018-03-23 18:20:54 +00:00
compression = config . getBool ( " compression " , true )
? Protocol : : Compression : : Enable
: Protocol : : Compression : : Disable ;
timeouts = ConnectionTimeouts (
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 ) ) ;
}
} ;
ConnectionParameters connection_parameters ;
2017-04-01 07:20:54 +00:00
void initialize ( Poco : : Util : : Application & self )
{
Poco : : Util : : Application : : initialize ( self ) ;
2017-03-13 17:35:17 +00:00
2017-04-01 07:20:54 +00:00
const char * home_path_cstr = getenv ( " HOME " ) ;
if ( home_path_cstr )
home_path = home_path_cstr ;
2017-03-13 17:35:17 +00:00
2018-03-14 19:38:48 +00:00
std : : string config_path ;
2017-04-01 07:20:54 +00:00
if ( config ( ) . has ( " config-file " ) )
2018-03-14 19:38:48 +00:00
config_path = config ( ) . getString ( " config-file " ) ;
2017-04-01 07:20:54 +00:00
else if ( Poco : : File ( " ./clickhouse-client.xml " ) . exists ( ) )
2018-03-14 19:38:48 +00:00
config_path = " ./clickhouse-client.xml " ;
2017-04-01 07:20:54 +00:00
else if ( ! home_path . empty ( ) & & Poco : : File ( home_path + " /.clickhouse-client/config.xml " ) . exists ( ) )
2018-03-14 19:38:48 +00:00
config_path = home_path + " /.clickhouse-client/config.xml " ;
2017-04-01 07:20:54 +00:00
else if ( Poco : : File ( " /etc/clickhouse-client/config.xml " ) . exists ( ) )
2018-03-14 19:38:48 +00:00
config_path = " /etc/clickhouse-client/config.xml " ;
if ( ! config_path . empty ( ) )
{
ConfigProcessor config_processor ( config_path ) ;
auto loaded_config = config_processor . loadConfig ( ) ;
config ( ) . add ( loaded_config . configuration ) ;
}
2017-03-13 17:35:17 +00:00
2017-04-01 07:20:54 +00:00
context . setApplicationType ( Context : : ApplicationType : : CLIENT ) ;
2017-03-13 17:35:17 +00:00
2017-04-01 07:20:54 +00:00
/// settings and limits could be specified in config file, but passed settings has higher priority
2017-11-30 11:50:02 +00:00
# define EXTRACT_SETTING(TYPE, NAME, DEFAULT, DESCRIPTION) \
2017-04-01 07:20:54 +00:00
if ( config ( ) . has ( # NAME ) & & ! context . getSettingsRef ( ) . NAME . changed ) \
context . setSetting ( # NAME , config ( ) . getString ( # NAME ) ) ;
APPLY_FOR_SETTINGS ( EXTRACT_SETTING )
2017-03-13 17:35:17 +00:00
# undef EXTRACT_SETTING
2017-04-01 07:20:54 +00:00
}
2017-12-02 02:47:12 +00:00
int main ( const std : : vector < std : : string > & /*args*/ )
2017-04-01 07:20:54 +00:00
{
try
{
2017-12-02 02:47:12 +00:00
return mainImpl ( ) ;
2017-04-01 07:20:54 +00:00
}
catch ( const Exception & e )
{
bool print_stack_trace = config ( ) . getBool ( " stacktrace " , false ) ;
std : : string text = e . displayText ( ) ;
/** If exception is received from server, then stack trace is embedded in message.
* If exception is thrown on client , then stack trace is in separate field .
*/
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 ) ;
std : : cerr < < " Code: " < < e . code ( ) < < " . " < < text < < std : : endl < < std : : endl ;
/// Don't print the stack trace on the client if it was logged on the server.
/// Also don't print the stack trace in case of network errors.
if ( print_stack_trace
& & e . code ( ) ! = ErrorCodes : : NETWORK_ERROR
& & std : : string : : npos = = embedded_stack_trace_pos )
{
std : : cerr < < " Stack trace: " < < std : : endl
< < e . getStackTrace ( ) . toString ( ) ;
}
/// If exception code isn't zero, we should return non-zero return code anyway.
return e . code ( ) ? e . code ( ) : - 1 ;
}
catch ( const Poco : : Exception & e )
{
std : : cerr < < " Poco::Exception: " < < e . displayText ( ) < < std : : endl ;
return ErrorCodes : : POCO_EXCEPTION ;
}
catch ( const std : : exception & e )
{
std : : cerr < < " std::exception: " < < e . what ( ) < < std : : endl ;
return ErrorCodes : : STD_EXCEPTION ;
}
catch ( . . . )
{
std : : cerr < < " Unknown exception " < < std : : endl ;
return ErrorCodes : : UNKNOWN_EXCEPTION ;
}
}
/// Should we celebrate a bit?
bool isNewYearMode ( )
{
2017-08-04 14:00:26 +00:00
time_t current_time = time ( nullptr ) ;
2017-04-01 07:20:54 +00:00
/// It's bad to be intrusive.
if ( current_time % 3 ! = 0 )
return false ;
LocalDate now ( current_time ) ;
return ( now . month ( ) = = 12 & & now . day ( ) > = 20 )
| | ( now . month ( ) = = 1 & & now . day ( ) < = 5 ) ;
}
2017-12-02 02:47:12 +00:00
int mainImpl ( )
2017-04-01 07:20:54 +00:00
{
2017-07-13 00:04:06 +00:00
registerFunctions ( ) ;
registerAggregateFunctions ( ) ;
2017-04-01 07:20:54 +00:00
/// Batch mode is enabled if one of the following is true:
/// - -e (--query) command line option is present.
/// The value of the option is used as the text of query (or of multiple queries).
/// If stdin is not a terminal, INSERT data for the first query is read from it.
/// - stdin is not a terminal. In this case queries are read from it.
stdin_is_not_tty = ! isatty ( STDIN_FILENO ) ;
if ( stdin_is_not_tty | | config ( ) . has ( " query " ) )
is_interactive = false ;
std : : cout < < std : : fixed < < std : : setprecision ( 3 ) ;
std : : cerr < < std : : fixed < < std : : setprecision ( 3 ) ;
if ( is_interactive )
showClientVersion ( ) ;
is_default_format = ! config ( ) . has ( " vertical " ) & & ! config ( ) . has ( " format " ) ;
if ( config ( ) . has ( " vertical " ) )
format = config ( ) . getString ( " format " , " Vertical " ) ;
else
format = config ( ) . getString ( " format " , is_interactive ? " PrettyCompact " : " TabSeparated " ) ;
format_max_block_size = config ( ) . getInt ( " format_max_block_size " , context . getSettingsRef ( ) . max_block_size ) ;
insert_format = " Values " ;
insert_format_max_block_size = config ( ) . getInt ( " insert_format_max_block_size " , context . getSettingsRef ( ) . max_insert_block_size ) ;
if ( ! is_interactive )
{
need_render_progress = config ( ) . getBool ( " progress " , false ) ;
echo_queries = config ( ) . getBool ( " echo " , false ) ;
}
connect ( ) ;
/// Initialize DateLUT here to avoid counting time spent here as query execution time.
DateLUT : : instance ( ) ;
if ( ! context . getSettingsRef ( ) . use_client_time_zone )
{
const auto & time_zone = connection - > getServerTimezone ( ) ;
if ( ! time_zone . empty ( ) )
{
try
{
DateLUT : : setDefaultTimezone ( time_zone ) ;
}
catch ( . . . )
{
std : : cerr < < " Warning: could not switch to server time zone: " < < time_zone
< < " , reason: " < < getCurrentExceptionMessage ( /* with_stacktrace = */ false ) < < std : : endl
< < " Proceeding with local time zone. "
< < std : : endl < < std : : endl ;
}
}
else
{
std : : cerr < < " Warning: could not determine server time zone. "
< < " Proceeding with local time zone. "
< < std : : endl < < std : : endl ;
}
}
2018-02-26 13:24:06 +00:00
Strings keys ;
2018-03-23 16:55:35 +00:00
prompt_by_server_display_name = config ( ) . getRawString ( " prompt_by_server_display_name.default " , " {display_name} :) " ) ;
2018-02-26 13:48:07 +00:00
2018-03-08 08:55:27 +00:00
config ( ) . keys ( " prompt_by_server_display_name " , keys ) ;
2018-02-26 13:24:06 +00:00
for ( const String & key : keys )
{
2018-03-23 16:55:35 +00:00
if ( key ! = " default " & & server_display_name . find ( key ) ! = std : : string : : npos )
2018-02-26 13:24:06 +00:00
{
2018-03-23 16:55:35 +00:00
prompt_by_server_display_name = config ( ) . getRawString ( " prompt_by_server_display_name. " + key ) ;
2018-02-26 13:24:06 +00:00
break ;
}
}
2018-03-23 16:55:35 +00:00
/// Prompt may contain escape sequences including \e[ or \x1b[ sequences to set terminal color.
2018-03-08 08:55:27 +00:00
{
2018-03-23 16:55:35 +00:00
String unescaped_prompt_by_server_display_name ;
ReadBufferFromString in ( prompt_by_server_display_name ) ;
readEscapedString ( unescaped_prompt_by_server_display_name , in ) ;
prompt_by_server_display_name = std : : move ( unescaped_prompt_by_server_display_name ) ;
2018-03-08 08:55:27 +00:00
}
2018-03-23 16:55:35 +00:00
/// Prompt may contain the following substitutions in a form of {name}.
2018-03-23 18:20:54 +00:00
std : : map < String , String > prompt_substitutions
2018-03-23 16:55:35 +00:00
{
2018-03-23 18:20:54 +00:00
{ " host " , connection_parameters . host } ,
{ " port " , toString ( connection_parameters . port ) } ,
{ " user " , connection_parameters . user } ,
2018-03-08 08:55:27 +00:00
{ " display_name " , server_display_name } ,
} ;
2018-03-23 16:55:35 +00:00
/// Quite suboptimal.
2018-03-23 18:20:54 +00:00
for ( const auto & [ key , value ] : prompt_substitutions )
2018-03-08 08:55:27 +00:00
boost : : replace_all ( prompt_by_server_display_name , " { " + key + " } " , value ) ;
2017-04-01 07:20:54 +00:00
if ( is_interactive )
{
2018-03-25 22:44:54 +00:00
if ( ! query_id . empty ( ) )
throw Exception ( " query_id could be specified only in non-interactive mode " , ErrorCodes : : BAD_ARGUMENTS ) ;
2017-04-01 07:20:54 +00:00
if ( print_time_to_stderr )
throw Exception ( " time option could be specified only in non-interactive mode " , ErrorCodes : : BAD_ARGUMENTS ) ;
/// Turn tab completion off.
rl_bind_key ( ' \t ' , rl_insert ) ;
/// Load command history if present.
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 ( ) )
{
if ( Poco : : File ( history_file ) . exists ( ) )
{
2017-03-13 17:35:17 +00:00
# if USE_READLINE
2017-04-01 07:20:54 +00:00
int res = read_history ( history_file . c_str ( ) ) ;
if ( res )
throwFromErrno ( " Cannot read history from file " + history_file , ErrorCodes : : CANNOT_READ_HISTORY ) ;
2017-03-13 17:35:17 +00:00
# endif
2017-04-01 07:20:54 +00:00
}
else /// Create history file.
Poco : : File ( history_file ) . createFile ( ) ;
}
loop ( ) ;
std : : cout < < ( isNewYearMode ( ) ? " Happy new year. " : " Bye. " ) < < std : : endl ;
return 0 ;
}
else
{
2018-03-24 23:50:30 +00:00
query_id = config ( ) . getString ( " query_id " , " " ) ;
2017-04-01 07:20:54 +00:00
nonInteractive ( ) ;
2018-05-21 13:49:54 +00:00
/// If exception code isn't zero, we should return non-zero return code anyway.
2017-04-01 07:20:54 +00:00
if ( last_exception )
2018-05-21 13:49:54 +00:00
return last_exception - > code ( ) ! = 0 ? last_exception - > code ( ) : - 1 ;
2017-04-01 07:20:54 +00:00
return 0 ;
}
}
void connect ( )
{
2018-07-02 12:32:40 +00:00
connection_parameters = ConnectionParameters ( config ( ) ) ;
2017-04-01 07:20:54 +00:00
if ( is_interactive )
std : : cout < < " Connecting to "
2018-03-23 18:20:54 +00:00
< < ( ! connection_parameters . default_database . empty ( ) ? " database " + connection_parameters . default_database + " at " : " " )
< < connection_parameters . host < < " : " < < connection_parameters . port
< < ( ! connection_parameters . user . empty ( ) ? " as user " + connection_parameters . user : " " )
2017-04-01 07:20:54 +00:00
< < " . " < < std : : endl ;
2018-01-10 01:43:54 +00:00
connection = std : : make_unique < Connection > (
2018-03-23 18:20:54 +00:00
connection_parameters . host ,
connection_parameters . port ,
connection_parameters . default_database ,
connection_parameters . user ,
connection_parameters . password ,
connection_parameters . timeouts ,
" client " ,
connection_parameters . compression ,
connection_parameters . security ) ;
2018-01-10 01:43:54 +00:00
2017-11-02 14:45:23 +00:00
String server_name ;
UInt64 server_version_major = 0 ;
UInt64 server_version_minor = 0 ;
UInt64 server_revision = 0 ;
2017-04-01 07:20:54 +00:00
2017-12-13 20:53:53 +00:00
if ( max_client_network_bandwidth )
2017-12-08 15:02:17 +00:00
{
2017-12-13 20:53:53 +00:00
ThrottlerPtr throttler = std : : make_shared < Throttler > ( max_client_network_bandwidth , 0 , " " ) ;
2017-12-08 15:02:17 +00:00
connection - > setThrottler ( throttler ) ;
}
2017-11-02 14:45:23 +00:00
connection - > getServerVersion ( server_name , server_version_major , server_version_minor , server_revision ) ;
2017-04-01 07:20:54 +00:00
2017-11-02 14:45:23 +00:00
server_version = toString ( server_version_major ) + " . " + toString ( server_version_minor ) + " . " + toString ( server_revision ) ;
2018-03-08 08:55:27 +00:00
if ( server_display_name = connection - > getServerDisplayName ( ) ; server_display_name . length ( ) = = 0 )
2018-03-05 14:47:10 +00:00
{
server_display_name = config ( ) . getString ( " host " , " localhost " ) ;
}
2017-11-02 14:45:23 +00:00
if ( is_interactive )
{
2017-04-01 07:20:54 +00:00
std : : cout < < " Connected to " < < server_name
2017-11-02 14:45:23 +00:00
< < " server version " < < server_version
< < " . " < < std : : endl < < std : : endl ;
2017-04-01 07:20:54 +00:00
}
}
/// Check if multi-line query is inserted from the paste buffer.
/// Allows delaying the start of query execution until the entirety of query is inserted.
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 ;
}
2018-03-12 13:23:15 +00:00
inline const String prompt ( ) const
2018-02-26 13:24:06 +00:00
{
2018-03-12 13:23:15 +00:00
return boost : : replace_all_copy ( prompt_by_server_display_name , " {database} " , config ( ) . getString ( " database " , " default " ) ) ;
2018-02-26 13:24:06 +00:00
}
2017-04-01 07:20:54 +00:00
void loop ( )
{
String query ;
String prev_query ;
2018-02-26 13:24:06 +00:00
while ( char * line_ = readline ( query . empty ( ) ? prompt ( ) . c_str ( ) : " :-] " ) )
2017-04-01 07:20:54 +00:00
{
String line = line_ ;
free ( line_ ) ;
size_t ws = line . size ( ) ;
2018-01-21 21:59:03 +00:00
while ( ws > 0 & & isWhitespaceASCII ( line [ ws - 1 ] ) )
2017-04-01 07:20:54 +00:00
- - ws ;
2018-03-01 06:18:25 +00:00
if ( ws = = 0 | | line . empty ( ) )
2017-04-01 07:20:54 +00:00
continue ;
bool ends_with_semicolon = line [ ws - 1 ] = = ' ; ' ;
bool ends_with_backslash = line [ ws - 1 ] = = ' \\ ' ;
has_vertical_output_suffix = ( ws > = 2 ) & & ( line [ ws - 2 ] = = ' \\ ' ) & & ( line [ ws - 1 ] = = ' G ' ) ;
if ( ends_with_backslash )
line = line . substr ( 0 , ws - 1 ) ;
query + = line ;
if ( ! ends_with_backslash & & ( ends_with_semicolon | | has_vertical_output_suffix | | ( ! config ( ) . has ( " multiline " ) & & ! hasDataInSTDIN ( ) ) ) )
{
if ( query ! = prev_query )
{
/// Replace line breaks with spaces to prevent the following problem.
/// Every line of multi-line query is saved to history file as a separate line.
/// If the user restarts the client then after pressing the "up" button
/// every line of the query will be displayed separately.
std : : string logged_query = query ;
std : : replace ( logged_query . begin ( ) , logged_query . end ( ) , ' \n ' , ' ' ) ;
add_history ( logged_query . c_str ( ) ) ;
2017-03-13 17:35:17 +00:00
# if USE_READLINE && HAVE_READLINE_HISTORY
2017-04-01 07:20:54 +00:00
if ( ! history_file . empty ( ) & & append_history ( 1 , history_file . c_str ( ) ) )
throwFromErrno ( " Cannot append history to file " + history_file , ErrorCodes : : CANNOT_APPEND_HISTORY ) ;
2017-03-13 17:35:17 +00:00
# endif
2017-04-01 07:20:54 +00:00
prev_query = query ;
}
if ( has_vertical_output_suffix )
query = query . substr ( 0 , query . length ( ) - 2 ) ;
try
{
/// Determine the terminal size.
ioctl ( 0 , TIOCGWINSZ , & terminal_size ) ;
if ( ! process ( query ) )
break ;
}
catch ( const Exception & e )
{
std : : cerr < < std : : endl
< < " Exception on client: " < < std : : endl
< < " Code: " < < e . code ( ) < < " . " < < e . displayText ( ) < < std : : endl
< < std : : endl ;
/// Client-side exception during query execution can result in the loss of
/// sync in the connection protocol.
/// So we reconnect and allow to enter the next query.
connect ( ) ;
}
query = " " ;
}
else
{
query + = ' \n ' ;
}
}
}
void nonInteractive ( )
{
2017-12-19 19:46:02 +00:00
String text ;
2017-04-01 07:20:54 +00:00
if ( config ( ) . has ( " query " ) )
2017-12-19 19:46:02 +00:00
text = config ( ) . getString ( " query " ) ;
2017-04-01 07:20:54 +00:00
else
{
/// If 'query' parameter is not set, read a query from stdin.
/// The query is read entirely into memory (streaming is disabled).
ReadBufferFromFileDescriptor in ( STDIN_FILENO ) ;
2017-12-19 19:46:02 +00:00
readStringUntilEOF ( text , in ) ;
2017-04-01 07:20:54 +00:00
}
2017-12-19 19:46:02 +00:00
process ( text ) ;
2017-04-01 07:20:54 +00:00
}
2017-12-19 19:46:02 +00:00
bool process ( const String & text )
2017-04-01 07:20:54 +00:00
{
2017-12-22 18:29:03 +00:00
const auto ignore_error = config ( ) . getBool ( " ignore-error " , false ) ;
2017-04-01 07:20:54 +00:00
if ( config ( ) . has ( " multiquery " ) )
{
/// Several queries separated by ';'.
/// INSERT data is ended by the end of line, not ';'.
String query ;
2017-12-19 19:46:02 +00:00
const char * begin = text . data ( ) ;
const char * end = begin + text . size ( ) ;
2017-04-01 07:20:54 +00:00
while ( begin < end )
{
const char * pos = begin ;
ASTPtr ast = parseQuery ( pos , end , true ) ;
if ( ! ast )
2017-12-22 18:29:03 +00:00
{
if ( ignore_error )
{
Tokens tokens ( begin , end ) ;
TokenIterator token_iterator ( tokens ) ;
while ( token_iterator - > type ! = TokenType : : Semicolon & & token_iterator . isValid ( ) )
+ + token_iterator ;
begin = token_iterator - > end ;
continue ;
}
2017-04-01 07:20:54 +00:00
return true ;
2017-12-22 18:29:03 +00:00
}
2017-04-01 07:20:54 +00:00
ASTInsertQuery * insert = typeid_cast < ASTInsertQuery * > ( & * ast ) ;
if ( insert & & insert - > data )
2017-12-19 23:14:37 +00:00
{
pos = find_first_symbols < ' \n ' > ( insert - > data , end ) ;
insert - > end = pos ;
}
2017-04-01 07:20:54 +00:00
2017-12-19 19:46:02 +00:00
query = text . substr ( begin - text . data ( ) , pos - begin ) ;
2017-04-01 07:20:54 +00:00
begin = pos ;
2018-01-21 21:59:03 +00:00
while ( isWhitespaceASCII ( * begin ) | | * begin = = ' ; ' )
2017-04-01 07:20:54 +00:00
+ + begin ;
2017-12-22 18:29:03 +00:00
try
{
if ( ! processSingleQuery ( query , ast ) & & ! ignore_error )
return false ;
}
catch ( . . . )
{
std : : cerr < < " Error on processing query: " < < query < < std : : endl < < getCurrentExceptionMessage ( true ) ;
got_exception = true ;
}
2017-04-01 07:20:54 +00:00
2017-12-22 18:29:03 +00:00
if ( got_exception & & ! ignore_error )
2017-04-01 07:20:54 +00:00
{
if ( is_interactive )
break ;
else
return false ;
}
}
return true ;
}
else
{
2017-12-19 19:46:02 +00:00
return processSingleQuery ( text ) ;
2017-04-01 07:20:54 +00:00
}
}
bool processSingleQuery ( const String & line , ASTPtr parsed_query_ = nullptr )
{
2018-06-14 19:15:35 +00:00
if ( exit_strings . end ( ) ! = exit_strings . find ( trim ( line , [ ] ( char c ) { return isWhitespaceASCII ( c ) | | c = = ' ; ' ; } ) ) )
2017-04-01 07:20:54 +00:00
return false ;
resetOutput ( ) ;
got_exception = false ;
if ( echo_queries )
{
writeString ( line , std_out ) ;
writeChar ( ' \n ' , std_out ) ;
std_out . next ( ) ;
}
watch . restart ( ) ;
query = line ;
/// Some parts of a query (result output and formatting) are executed client-side.
/// Thus we need to parse the query.
parsed_query = parsed_query_ ;
if ( ! parsed_query )
{
const char * begin = query . data ( ) ;
parsed_query = parseQuery ( begin , begin + query . size ( ) , false ) ;
}
if ( ! parsed_query )
return true ;
processed_rows = 0 ;
progress . reset ( ) ;
show_progress_bar = false ;
written_progress_chars = 0 ;
written_first_block = false ;
const ASTSetQuery * set_query = typeid_cast < const ASTSetQuery * > ( & * parsed_query ) ;
const ASTUseQuery * use_query = typeid_cast < const ASTUseQuery * > ( & * parsed_query ) ;
/// INSERT query for which data transfer is needed (not an INSERT SELECT) is processed separately.
const ASTInsertQuery * insert = typeid_cast < const ASTInsertQuery * > ( & * parsed_query ) ;
2017-04-17 16:16:04 +00:00
connection - > forceConnected ( ) ;
2017-04-01 07:20:54 +00:00
if ( insert & & ! insert - > select )
processInsertQuery ( ) ;
else
processOrdinaryQuery ( ) ;
/// Do not change context (current DB, settings) in case of an exception.
if ( ! got_exception )
{
if ( set_query )
{
/// Save all changes in settings to avoid losing them if the connection is lost.
2017-06-07 12:54:35 +00:00
for ( const auto & change : set_query - > changes )
2017-04-01 07:20:54 +00:00
{
2017-06-07 12:54:35 +00:00
if ( change . name = = " profile " )
current_profile = change . value . safeGet < String > ( ) ;
2017-04-01 07:20:54 +00:00
else
2017-06-07 12:54:35 +00:00
context . setSetting ( change . name , change . value ) ;
2017-04-01 07:20:54 +00:00
}
}
if ( use_query )
{
const String & new_database = use_query - > database ;
/// If the client initiates the reconnection, it takes the settings from the config.
config ( ) . setString ( " database " , new_database ) ;
/// If the connection initiates the reconnection, it uses its variable.
connection - > setDefaultDatabase ( new_database ) ;
}
}
if ( is_interactive )
{
std : : cout < < std : : endl
< < processed_rows < < " rows in set. Elapsed: " < < watch . elapsedSeconds ( ) < < " sec. " ;
if ( progress . rows > = 1000 )
writeFinalProgress ( ) ;
std : : cout < < std : : endl < < std : : endl ;
}
else if ( print_time_to_stderr )
{
std : : cerr < < watch . elapsedSeconds ( ) < < " \n " ;
}
return true ;
}
/// Convert external tables to ExternalTableData and send them using the connection.
void sendExternalTables ( )
{
2018-02-25 06:34:20 +00:00
auto * select = typeid_cast < const ASTSelectWithUnionQuery * > ( & * parsed_query ) ;
2017-04-01 07:20:54 +00:00
if ( ! select & & ! external_tables . empty ( ) )
throw Exception ( " External tables could be sent only with select query " , ErrorCodes : : BAD_ARGUMENTS ) ;
std : : vector < ExternalTableData > data ;
for ( auto & table : external_tables )
data . emplace_back ( table . getData ( context ) ) ;
connection - > sendExternalTablesData ( data ) ;
}
/// Process the query that doesn't require transfering data blocks to the server.
void processOrdinaryQuery ( )
{
2018-03-24 23:22:58 +00:00
connection - > sendQuery ( query , query_id , QueryProcessingStage : : Complete , & context . getSettingsRef ( ) , nullptr , true ) ;
2017-04-01 07:20:54 +00:00
sendExternalTables ( ) ;
receiveResult ( ) ;
}
/// Process the query that requires transfering data blocks to the server.
void processInsertQuery ( )
{
/// Send part of query without data, because data will be sent separately.
const ASTInsertQuery & parsed_insert_query = typeid_cast < const ASTInsertQuery & > ( * parsed_query ) ;
String query_without_data = parsed_insert_query . data
? query . substr ( 0 , parsed_insert_query . data - query . data ( ) )
: query ;
if ( ! parsed_insert_query . data & & ( is_interactive | | ( stdin_is_not_tty & & std_in . eof ( ) ) ) )
throw Exception ( " No data to insert " , ErrorCodes : : NO_DATA_TO_INSERT ) ;
2018-03-24 23:22:58 +00:00
connection - > sendQuery ( query_without_data , query_id , QueryProcessingStage : : Complete , & context . getSettingsRef ( ) , nullptr , true ) ;
2017-04-01 07:20:54 +00:00
sendExternalTables ( ) ;
/// Receive description of table structure.
Block sample ;
if ( receiveSampleBlock ( sample ) )
{
/// If structure was received (thus, server has not thrown an exception),
/// send our data with that structure.
sendData ( sample ) ;
receivePacket ( ) ;
}
}
2017-07-12 03:03:56 +00:00
ASTPtr parseQuery ( const char * & pos , const char * end , bool allow_multi_statements )
2017-04-01 07:20:54 +00:00
{
2017-07-12 03:03:56 +00:00
ParserQuery parser ( end ) ;
2017-04-01 07:20:54 +00:00
ASTPtr res ;
2017-12-22 18:29:03 +00:00
const auto ignore_error = config ( ) . getBool ( " ignore-error " , false ) ;
if ( is_interactive | | ignore_error )
2017-04-01 07:20:54 +00:00
{
String message ;
2018-04-16 15:11:13 +00:00
res = tryParseQuery ( parser , pos , end , message , true , " " , allow_multi_statements , 0 ) ;
2017-04-01 07:20:54 +00:00
if ( ! res )
{
std : : cerr < < std : : endl < < message < < std : : endl < < std : : endl ;
return nullptr ;
}
}
else
2018-04-16 15:11:13 +00:00
res = parseQueryAndMovePosition ( parser , pos , end , " " , allow_multi_statements , 0 ) ;
2017-04-01 07:20:54 +00:00
if ( is_interactive )
{
std : : cout < < std : : endl ;
formatAST ( * res , std : : cout ) ;
std : : cout < < std : : endl < < std : : endl ;
}
return res ;
}
void sendData ( Block & sample )
{
/// If INSERT data must be sent.
const ASTInsertQuery * parsed_insert_query = typeid_cast < const ASTInsertQuery * > ( & * parsed_query ) ;
if ( ! parsed_insert_query )
return ;
if ( parsed_insert_query - > data )
{
/// Send data contained in the query.
ReadBufferFromMemory data_in ( parsed_insert_query - > data , parsed_insert_query - > end - parsed_insert_query - > data ) ;
sendDataFrom ( data_in , sample ) ;
}
else if ( ! is_interactive )
{
/// Send data read from stdin.
sendDataFrom ( std_in , sample ) ;
}
else
throw Exception ( " No data to insert " , ErrorCodes : : NO_DATA_TO_INSERT ) ;
}
void sendDataFrom ( ReadBuffer & buf , Block & sample )
{
String current_format = insert_format ;
/// Data format can be specified in the INSERT query.
if ( ASTInsertQuery * insert = typeid_cast < ASTInsertQuery * > ( & * parsed_query ) )
if ( ! insert - > format . empty ( ) )
current_format = insert - > format ;
BlockInputStreamPtr block_input = context . getInputFormat (
current_format , buf , sample , insert_format_max_block_size ) ;
BlockInputStreamPtr async_block_input = std : : make_shared < AsynchronousBlockInputStream > ( block_input ) ;
async_block_input - > readPrefix ( ) ;
while ( true )
{
Block block = async_block_input - > read ( ) ;
connection - > sendData ( block ) ;
processed_rows + = block . rows ( ) ;
if ( ! block )
break ;
}
async_block_input - > readSuffix ( ) ;
}
/// Flush all buffers.
void resetOutput ( )
{
block_out_stream = nullptr ;
if ( pager_cmd )
{
pager_cmd - > in . close ( ) ;
pager_cmd - > wait ( ) ;
}
pager_cmd = nullptr ;
if ( out_file_buf )
{
out_file_buf - > next ( ) ;
2017-11-20 04:41:56 +00:00
out_file_buf . reset ( ) ;
2017-04-01 07:20:54 +00:00
}
std_out . next ( ) ;
}
/// Receives and processes packets coming from server.
/// Also checks if query execution should be cancelled.
void receiveResult ( )
{
InterruptListener interrupt_listener ;
bool cancelled = false ;
while ( true )
{
/// Has the Ctrl+C been pressed and thus the query should be cancelled?
/// If this is the case, inform the server about it and receive the remaining packets
/// to avoid losing sync.
if ( ! cancelled )
{
if ( interrupt_listener . check ( ) )
{
connection - > sendCancel ( ) ;
cancelled = true ;
if ( is_interactive )
std : : cout < < " Cancelling query. " < < std : : endl ;
/// Pressing Ctrl+C twice results in shut down.
interrupt_listener . unblock ( ) ;
}
else if ( ! connection - > poll ( 1000000 ) )
continue ; /// If there is no new data, continue checking whether the query was cancelled after a timeout.
}
if ( ! receivePacket ( ) )
break ;
}
if ( cancelled & & is_interactive )
std : : cout < < " Query was cancelled. " < < std : : endl ;
}
/// Receive a part of the result, or progress info or an exception and process it.
/// Returns true if one should continue receiving packets.
bool receivePacket ( )
{
Connection : : Packet packet = connection - > receivePacket ( ) ;
switch ( packet . type )
{
case Protocol : : Server : : Data :
onData ( packet . block ) ;
return true ;
case Protocol : : Server : : Progress :
onProgress ( packet . progress ) ;
return true ;
case Protocol : : Server : : ProfileInfo :
onProfileInfo ( packet . profile_info ) ;
return true ;
case Protocol : : Server : : Totals :
onTotals ( packet . block ) ;
return true ;
case Protocol : : Server : : Extremes :
onExtremes ( packet . block ) ;
return true ;
case Protocol : : Server : : Exception :
onException ( * packet . exception ) ;
last_exception = std : : move ( packet . exception ) ;
return false ;
case Protocol : : Server : : EndOfStream :
onEndOfStream ( ) ;
return false ;
default :
throw Exception ( " Unknown packet from server " , ErrorCodes : : UNKNOWN_PACKET_FROM_SERVER ) ;
}
}
/// Receive the block that serves as an example of the structure of table where data will be inserted.
bool receiveSampleBlock ( Block & out )
{
Connection : : Packet packet = connection - > receivePacket ( ) ;
switch ( packet . type )
{
case Protocol : : Server : : Data :
out = packet . block ;
return true ;
case Protocol : : Server : : Exception :
onException ( * packet . exception ) ;
last_exception = std : : move ( packet . exception ) ;
return false ;
default :
throw NetException ( " Unexpected packet from server (expected Data, got "
+ String ( Protocol : : Server : : toString ( packet . type ) ) + " ) " , ErrorCodes : : UNEXPECTED_PACKET_FROM_SERVER ) ;
}
}
void initBlockOutputStream ( const Block & block )
{
if ( ! block_out_stream )
{
WriteBuffer * out_buf = nullptr ;
String pager = config ( ) . getString ( " pager " , " " ) ;
if ( ! pager . empty ( ) )
{
2017-12-10 14:57:08 +00:00
signal ( SIGPIPE , SIG_IGN ) ;
2017-04-01 07:20:54 +00:00
pager_cmd = ShellCommand : : execute ( pager , true ) ;
out_buf = & pager_cmd - > in ;
}
else
{
out_buf = & std_out ;
}
String current_format = format ;
/// The query can specify output format or output file.
if ( ASTQueryWithOutput * query_with_output = dynamic_cast < ASTQueryWithOutput * > ( & * parsed_query ) )
{
if ( query_with_output - > out_file ! = nullptr )
{
const auto & out_file_node = typeid_cast < const ASTLiteral & > ( * query_with_output - > out_file ) ;
const auto & out_file = out_file_node . value . safeGet < std : : string > ( ) ;
out_file_buf . emplace ( out_file , DBMS_DEFAULT_BUFFER_SIZE , O_WRONLY | O_EXCL | O_CREAT ) ;
2017-10-04 00:22:00 +00:00
out_buf = & * out_file_buf ;
2017-04-01 07:20:54 +00:00
// We are writing to file, so default format is the same as in non-interactive mode.
if ( is_interactive & & is_default_format )
current_format = " TabSeparated " ;
}
if ( query_with_output - > format ! = nullptr )
{
if ( has_vertical_output_suffix )
throw Exception ( " Output format already specified " , ErrorCodes : : CLIENT_OUTPUT_FORMAT_SPECIFIED ) ;
const auto & id = typeid_cast < const ASTIdentifier & > ( * query_with_output - > format ) ;
current_format = id . name ;
}
}
if ( has_vertical_output_suffix )
current_format = " Vertical " ;
block_out_stream = context . getOutputFormat ( current_format , * out_buf , block ) ;
block_out_stream - > writePrefix ( ) ;
}
}
void onData ( Block & block )
{
if ( written_progress_chars )
clearProgress ( ) ;
if ( ! block )
return ;
processed_rows + = block . rows ( ) ;
initBlockOutputStream ( block ) ;
/// The header block containing zero rows was used to initialize block_out_stream, do not output it.
if ( block . rows ( ) ! = 0 )
{
block_out_stream - > write ( block ) ;
written_first_block = true ;
}
/// Received data block is immediately displayed to the user.
block_out_stream - > flush ( ) ;
}
void onTotals ( Block & block )
{
initBlockOutputStream ( block ) ;
block_out_stream - > setTotals ( block ) ;
}
void onExtremes ( Block & block )
{
initBlockOutputStream ( block ) ;
block_out_stream - > setExtremes ( block ) ;
}
void onProgress ( const Progress & value )
{
progress . incrementPiecewiseAtomically ( value ) ;
2018-01-10 00:04:08 +00:00
if ( block_out_stream )
block_out_stream - > onProgress ( value ) ;
2017-04-01 07:20:54 +00:00
writeProgress ( ) ;
}
void clearProgress ( )
{
std : : cerr < < RESTORE_CURSOR_POSITION CLEAR_TO_END_OF_LINE ;
written_progress_chars = 0 ;
}
void writeProgress ( )
{
if ( ! need_render_progress )
return ;
static size_t increment = 0 ;
static const char * indicators [ 8 ] =
{
" \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 [1m↗ \033 [0m " ,
} ;
if ( written_progress_chars )
clearProgress ( ) ;
else
std : : cerr < < SAVE_CURSOR_POSITION ;
std : : stringstream message ;
message < < indicators [ increment % 8 ]
< < std : : fixed < < std : : setprecision ( 3 )
< < " Progress: " ;
message
< < formatReadableQuantity ( progress . rows ) < < " rows, "
< < formatReadableSizeWithDecimalSuffix ( progress . bytes ) ;
size_t elapsed_ns = watch . elapsed ( ) ;
if ( elapsed_ns )
message < < " ( "
< < formatReadableQuantity ( progress . rows * 1000000000.0 / elapsed_ns ) < < " rows/s., "
< < formatReadableSizeWithDecimalSuffix ( progress . bytes * 1000000000.0 / elapsed_ns ) < < " /s.) " ;
else
message < < " . " ;
written_progress_chars = message . str ( ) . size ( ) - ( increment % 8 = = 7 ? 10 : 13 ) ;
std : : cerr < < DISABLE_LINE_WRAPPING < < message . rdbuf ( ) ;
2017-04-12 19:50:49 +00:00
/// If the approximate number of rows to process is known, we can display a progress bar and percentage.
2017-04-16 04:28:04 +00:00
if ( progress . total_rows > 0 )
2017-04-01 07:20:54 +00:00
{
2017-04-16 04:28:04 +00:00
size_t total_rows_corrected = std : : max ( progress . rows , progress . total_rows ) ;
2017-04-12 19:50:49 +00:00
/// To avoid flicker, display progress bar only if .5 seconds have passed since query execution start
/// and the query is less than halfway done.
if ( elapsed_ns > 500000000 )
{
/// Trigger to start displaying progress bar. If query is mostly done, don't display it.
if ( progress . rows * 2 < total_rows_corrected )
show_progress_bar = true ;
if ( show_progress_bar )
{
ssize_t width_of_progress_bar = static_cast < ssize_t > ( terminal_size . ws_col ) - written_progress_chars - strlen ( " 99% " ) ;
if ( width_of_progress_bar > 0 )
{
std : : string bar = UnicodeBar : : render ( UnicodeBar : : getWidth ( progress . rows , 0 , total_rows_corrected , width_of_progress_bar ) ) ;
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 , ' ' ) ;
}
}
}
/// Underestimate percentage a bit to avoid displaying 100%.
std : : cerr < < ' ' < < ( 99 * progress . rows / total_rows_corrected ) < < ' % ' ;
2017-04-01 07:20:54 +00:00
}
2017-04-12 19:50:49 +00:00
2017-04-01 07:20:54 +00:00
std : : cerr < < ENABLE_LINE_WRAPPING ;
+ + increment ;
}
void writeFinalProgress ( )
{
std : : cout < < " Processed "
< < formatReadableQuantity ( progress . rows ) < < " rows, "
< < formatReadableSizeWithDecimalSuffix ( progress . bytes ) ;
size_t elapsed_ns = watch . elapsed ( ) ;
if ( elapsed_ns )
std : : cout < < " ( "
< < formatReadableQuantity ( progress . rows * 1000000000.0 / elapsed_ns ) < < " rows/s., "
< < formatReadableSizeWithDecimalSuffix ( progress . bytes * 1000000000.0 / elapsed_ns ) < < " /s.) " ;
else
std : : cout < < " . " ;
}
void onException ( const Exception & e )
{
resetOutput ( ) ;
got_exception = true ;
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 ) ;
2017-11-02 21:27:12 +00:00
std : : cerr < < " Received exception from server (version " < < server_version < < " ): " < < std : : endl
2017-04-01 07:20:54 +00:00
< < " Code: " < < e . code ( ) < < " . " < < text < < std : : endl ;
}
void onProfileInfo ( const BlockStreamProfileInfo & profile_info )
{
if ( profile_info . hasAppliedLimit ( ) & & block_out_stream )
block_out_stream - > setRowsBeforeLimit ( profile_info . getRowsBeforeLimit ( ) ) ;
}
void onEndOfStream ( )
{
if ( block_out_stream )
block_out_stream - > writeSuffix ( ) ;
resetOutput ( ) ;
if ( is_interactive & & ! written_first_block )
std : : cout < < " Ok. " < < std : : endl ;
}
void showClientVersion ( )
{
std : : cout < < " ClickHouse client version " < < DBMS_VERSION_MAJOR
< < " . " < < DBMS_VERSION_MINOR
< < " . " < < ClickHouseRevision : : get ( )
< < " . " < < std : : endl ;
}
2017-03-13 17:35:17 +00:00
public :
2017-04-01 07:20:54 +00:00
void init ( int argc , char * * argv )
{
/// Don't parse options with Poco library. We need more sophisticated processing.
stopOptionsProcessing ( ) ;
/** We allow different groups of arguments:
* - common arguments ;
* - arguments for any number of external tables each in form " --external args... " ,
* where possible args are file , name , format , structure , types .
* Split these groups before processing .
*/
using Arguments = std : : vector < const char * > ;
Arguments common_arguments { " " } ; /// 0th argument is ignored.
std : : vector < Arguments > external_tables_arguments ;
bool in_external_group = false ;
for ( int arg_num = 1 ; arg_num < argc ; + + arg_num )
{
const char * arg = argv [ arg_num ] ;
if ( 0 = = strcmp ( arg , " --external " ) )
{
in_external_group = true ;
external_tables_arguments . emplace_back ( Arguments { " " } ) ;
}
/// Options with value after equal sign.
else if ( in_external_group
2017-07-12 00:49:30 +00:00
& & ( 0 = = strncmp ( arg , " --file= " , strlen ( " --file= " ) )
| | 0 = = strncmp ( arg , " --name= " , strlen ( " --name= " ) )
| | 0 = = strncmp ( arg , " --format= " , strlen ( " --format= " ) )
| | 0 = = strncmp ( arg , " --structure= " , strlen ( " --structure= " ) )
| | 0 = = strncmp ( arg , " --types= " , strlen ( " --types= " ) ) ) )
2017-04-01 07:20:54 +00:00
{
external_tables_arguments . back ( ) . emplace_back ( arg ) ;
}
/// Options with value after whitespace.
else if ( in_external_group
& & ( 0 = = strcmp ( arg , " --file " )
| | 0 = = strcmp ( arg , " --name " )
| | 0 = = strcmp ( arg , " --format " )
| | 0 = = strcmp ( arg , " --structure " )
| | 0 = = strcmp ( arg , " --types " ) ) )
{
if ( arg_num + 1 < argc )
{
external_tables_arguments . back ( ) . emplace_back ( arg ) ;
+ + arg_num ;
arg = argv [ arg_num ] ;
external_tables_arguments . back ( ) . emplace_back ( arg ) ;
}
else
break ;
}
else
{
in_external_group = false ;
common_arguments . emplace_back ( arg ) ;
}
}
2017-03-13 17:35:17 +00:00
2018-04-20 19:31:19 +00:00
ioctl ( 0 , TIOCGWINSZ , & terminal_size ) ;
2018-05-23 16:12:46 +00:00
unsigned line_length = boost : : program_options : : options_description : : m_default_line_length ;
unsigned min_description_length = line_length / 2 ;
if ( ! stdin_is_not_tty )
{
line_length = std : : max ( 3U , static_cast < unsigned > ( terminal_size . ws_col ) ) ;
min_description_length = std : : min ( min_description_length , line_length - 2 ) ;
}
2018-03-11 00:15:26 +00:00
# define DECLARE_SETTING(TYPE, NAME, DEFAULT, DESCRIPTION) (#NAME, boost::program_options::value<std::string> (), DESCRIPTION)
2017-03-13 17:35:17 +00:00
2017-04-01 07:20:54 +00:00
/// Main commandline options related to client functionality and all parameters from Settings.
2018-05-23 16:12:46 +00:00
boost : : program_options : : options_description main_description ( " Main options " , line_length , min_description_length ) ;
2017-04-01 07:20:54 +00:00
main_description . add_options ( )
( " help " , " produce help message " )
2017-07-12 00:49:30 +00:00
( " config-file,c " , boost : : program_options : : value < std : : string > ( ) , " config-file path " )
( " host,h " , boost : : program_options : : value < std : : string > ( ) - > default_value ( " localhost " ) , " server host " )
( " port " , boost : : program_options : : value < int > ( ) - > default_value ( 9000 ) , " server port " )
2018-03-29 01:41:06 +00:00
( " secure,s " , " secure " )
2018-05-21 17:27:18 +00:00
( " user,u " , boost : : program_options : : value < std : : string > ( ) - > default_value ( " default " ) , " user " )
2017-07-12 00:49:30 +00:00
( " password " , boost : : program_options : : value < std : : string > ( ) , " password " )
2018-05-21 17:27:18 +00:00
( " ask-password " , " ask-password " )
2018-03-24 23:22:58 +00:00
( " query_id " , boost : : program_options : : value < std : : string > ( ) , " query_id " )
2017-07-12 00:49:30 +00:00
( " query,q " , boost : : program_options : : value < std : : string > ( ) , " query " )
( " database,d " , boost : : program_options : : value < std : : string > ( ) , " database " )
( " pager " , boost : : program_options : : value < std : : string > ( ) , " pager " )
( " multiline,m " , " multiline " )
2017-09-29 22:21:17 +00:00
( " multiquery,n " , " multiquery " )
2017-12-22 18:29:03 +00:00
( " ignore-error " , " Do not stop processing in multiquery mode " )
2017-07-12 00:49:30 +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 " )
( " time,t " , " print query execution time to stderr in non-interactive mode (for benchmarks) " )
( " stacktrace " , " print stack traces of exceptions " )
( " progress " , " print progress even in non-interactive mode " )
( " version,V " , " print version information and exit " )
( " echo " , " in batch mode, print query before execution " )
2017-12-13 20:53:53 +00:00
( " max_client_network_bandwidth " , boost : : program_options : : value < int > ( ) , " the maximum speed of data exchange over the network for the client in bytes per second. " )
2017-07-12 00:49:30 +00:00
( " compression " , boost : : program_options : : value < bool > ( ) , " enable or disable compression " )
2017-04-01 07:20:54 +00:00
APPLY_FOR_SETTINGS ( DECLARE_SETTING )
;
2017-03-13 17:35:17 +00:00
# undef DECLARE_SETTING
2017-04-01 07:20:54 +00:00
/// Commandline options related to external tables.
boost : : program_options : : options_description external_description ( " External tables options " ) ;
external_description . add_options ( )
2017-07-12 00:49:30 +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 " )
2017-04-01 07:20:54 +00:00
;
/// Parse main commandline options.
boost : : program_options : : parsed_options parsed = boost : : program_options : : command_line_parser (
common_arguments . size ( ) , common_arguments . data ( ) ) . options ( main_description ) . run ( ) ;
boost : : program_options : : variables_map options ;
boost : : program_options : : store ( parsed , options ) ;
if ( options . count ( " version " ) | | options . count ( " V " ) )
{
showClientVersion ( ) ;
exit ( 0 ) ;
}
/// Output of help message.
if ( options . count ( " help " )
| | ( options . count ( " host " ) & & options [ " host " ] . as < std : : string > ( ) = = " elp " ) ) /// If user writes -help instead of --help.
{
std : : cout < < main_description < < " \n " ;
std : : cout < < external_description < < " \n " ;
exit ( 0 ) ;
}
size_t number_of_external_tables_with_stdin_source = 0 ;
for ( size_t i = 0 ; i < external_tables_arguments . size ( ) ; + + i )
{
/// Parse commandline options related to external tables.
boost : : program_options : : parsed_options parsed = boost : : program_options : : command_line_parser (
external_tables_arguments [ i ] . size ( ) , external_tables_arguments [ i ] . data ( ) ) . options ( external_description ) . run ( ) ;
boost : : program_options : : variables_map external_options ;
boost : : program_options : : store ( parsed , external_options ) ;
try
{
external_tables . emplace_back ( external_options ) ;
if ( external_tables . back ( ) . file = = " - " )
+ + number_of_external_tables_with_stdin_source ;
if ( number_of_external_tables_with_stdin_source > 1 )
throw Exception ( " Two or more external tables has stdin (-) set as - - file field " , ErrorCodes::BAD_ARGUMENTS) ;
}
catch ( const Exception & e )
{
std : : string text = e . displayText ( ) ;
std : : cerr < < " Code: " < < e . code ( ) < < " . " < < text < < std : : endl ;
std : : cerr < < " Table № " < < i < < std : : endl < < std : : endl ;
exit ( e . code ( ) ) ;
}
}
2018-04-20 19:31:19 +00:00
/// Extract settings from the options.
2017-11-30 11:50:02 +00:00
# define EXTRACT_SETTING(TYPE, NAME, DEFAULT, DESCRIPTION) \
2017-04-01 07:20:54 +00:00
if ( options . count ( # NAME ) ) \
context . setSetting ( # NAME , options [ # NAME ] . as < std : : string > ( ) ) ;
APPLY_FOR_SETTINGS ( EXTRACT_SETTING )
2017-03-13 17:35:17 +00:00
# undef EXTRACT_SETTING
2017-04-01 07:20:54 +00:00
/// Save received data into the internal config.
if ( options . count ( " config-file " ) )
config ( ) . setString ( " config-file " , options [ " config-file " ] . as < std : : string > ( ) ) ;
if ( options . count ( " host " ) & & ! options [ " host " ] . defaulted ( ) )
config ( ) . setString ( " host " , options [ " host " ] . as < std : : string > ( ) ) ;
2018-03-24 23:22:58 +00:00
if ( options . count ( " query_id " ) )
config ( ) . setString ( " query_id " , options [ " query_id " ] . as < std : : string > ( ) ) ;
2017-04-01 07:20:54 +00:00
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 ( " pager " ) )
config ( ) . setString ( " pager " , options [ " pager " ] . as < std : : string > ( ) ) ;
if ( options . count ( " port " ) & & ! options [ " port " ] . defaulted ( ) )
config ( ) . setInt ( " port " , options [ " port " ] . as < int > ( ) ) ;
2018-03-29 01:41:06 +00:00
if ( options . count ( " secure " ) )
config ( ) . setBool ( " secure " , true ) ;
2017-04-01 07:20:54 +00:00
if ( options . count ( " user " ) )
config ( ) . setString ( " user " , options [ " user " ] . as < std : : string > ( ) ) ;
if ( options . count ( " password " ) )
config ( ) . setString ( " password " , options [ " password " ] . as < std : : string > ( ) ) ;
2018-05-21 17:27:18 +00:00
if ( options . count ( " ask-password " ) )
config ( ) . setBool ( " ask-password " , true ) ;
2017-04-01 07:20:54 +00:00
if ( options . count ( " multiline " ) )
config ( ) . setBool ( " multiline " , true ) ;
if ( options . count ( " multiquery " ) )
config ( ) . setBool ( " multiquery " , true ) ;
2017-12-22 18:29:03 +00:00
if ( options . count ( " ignore-error " ) )
config ( ) . setBool ( " ignore-error " , true ) ;
2017-04-01 07:20:54 +00:00
if ( options . count ( " format " ) )
config ( ) . setString ( " format " , options [ " format " ] . as < std : : string > ( ) ) ;
if ( options . count ( " vertical " ) )
config ( ) . setBool ( " vertical " , true ) ;
if ( options . count ( " stacktrace " ) )
config ( ) . setBool ( " stacktrace " , true ) ;
if ( options . count ( " progress " ) )
config ( ) . setBool ( " progress " , true ) ;
if ( options . count ( " echo " ) )
config ( ) . setBool ( " echo " , true ) ;
if ( options . count ( " time " ) )
print_time_to_stderr = true ;
2017-12-13 20:53:53 +00:00
if ( options . count ( " max_client_network_bandwidth " ) )
2017-12-13 21:26:17 +00:00
max_client_network_bandwidth = options [ " max_client_network_bandwidth " ] . as < int > ( ) ;
2017-04-01 07:20:54 +00:00
if ( options . count ( " compression " ) )
config ( ) . setBool ( " compression " , options [ " compression " ] . as < bool > ( ) ) ;
}
2017-03-13 17:35:17 +00:00
} ;
}
2017-03-24 15:05:54 +00:00
int mainEntryClickHouseClient ( int argc , char * * argv )
2017-03-13 17:35:17 +00:00
{
2017-04-01 07:20:54 +00:00
DB : : Client client ;
try
{
client . init ( argc , argv ) ;
}
catch ( const boost : : program_options : : error & e )
{
std : : cerr < < " Bad arguments: " < < e . what ( ) < < std : : endl ;
return 1 ;
}
return client . run ( ) ;
2017-03-13 17:35:17 +00:00
}