2023-03-10 02:23:57 +00:00
# include "KeeperClient.h"
2023-04-26 04:54:28 +00:00
# include "Commands.h"
2023-03-10 02:23:57 +00:00
# include <Client/ReplxxLineReader.h>
# include <Client/ClientBase.h>
2023-10-11 02:28:28 +00:00
# include "Common/VersionNumber.h"
2023-08-24 02:18:24 +00:00
# include <Common/Config/ConfigProcessor.h>
2023-03-10 02:23:57 +00:00
# include <Common/EventNotifier.h>
# include <Common/filesystemHelpers.h>
# include <Common/ZooKeeper/ZooKeeper.h>
2023-04-26 04:54:28 +00:00
# include <Parsers/parseQuery.h>
2023-04-27 00:39:33 +00:00
# include <Poco/Util/HelpFormatter.h>
2023-03-10 02:23:57 +00:00
namespace DB
{
2023-03-10 02:45:58 +00:00
namespace ErrorCodes
{
extern const int BAD_ARGUMENTS ;
}
2023-03-11 04:12:16 +00:00
String KeeperClient : : executeFourLetterCommand ( const String & command )
{
2023-03-11 04:15:15 +00:00
/// We need to create a new socket every time because ZooKeeper forcefully shuts down the connection after a four-letter-word command.
2023-03-11 04:12:16 +00:00
Poco : : Net : : StreamSocket socket ;
socket . connect ( Poco : : Net : : SocketAddress { zk_args . hosts [ 0 ] } , zk_args . connection_timeout_ms * 1000 ) ;
socket . setReceiveTimeout ( zk_args . operation_timeout_ms * 1000 ) ;
socket . setSendTimeout ( zk_args . operation_timeout_ms * 1000 ) ;
socket . setNoDelay ( true ) ;
ReadBufferFromPocoSocket in ( socket ) ;
WriteBufferFromPocoSocket out ( socket ) ;
out . write ( command . data ( ) , command . size ( ) ) ;
out . next ( ) ;
String result ;
readStringUntilEOF ( result , in ) ;
in . next ( ) ;
return result ;
}
2023-04-27 00:11:45 +00:00
std : : vector < String > KeeperClient : : getCompletions ( const String & prefix ) const
{
Tokens tokens ( prefix . data ( ) , prefix . data ( ) + prefix . size ( ) , 0 , false ) ;
2024-03-17 18:53:58 +00:00
IParser : : Pos pos ( tokens , DBMS_DEFAULT_MAX_PARSER_DEPTH , DBMS_DEFAULT_MAX_PARSER_BACKTRACKS ) ;
2023-04-27 00:11:45 +00:00
if ( pos - > type ! = TokenType : : BareWord )
return registered_commands_and_four_letter_words ;
+ + pos ;
if ( pos - > isEnd ( ) )
return registered_commands_and_four_letter_words ;
ParserToken { TokenType : : Whitespace } . ignore ( pos ) ;
std : : vector < String > result ;
String string_path ;
Expected expected ;
if ( ! parseKeeperPath ( pos , expected , string_path ) )
string_path = cwd ;
if ( ! pos - > isEnd ( ) )
return result ;
fs : : path path = string_path ;
String parent_path ;
if ( string_path . ends_with ( " / " ) )
parent_path = getAbsolutePath ( string_path ) ;
else
parent_path = getAbsolutePath ( path . parent_path ( ) ) ;
try
{
for ( const auto & child : zookeeper - > getChildren ( parent_path ) )
result . push_back ( child ) ;
}
2023-09-25 20:19:09 +00:00
catch ( Coordination : : Exception & ) { } // NOLINT(bugprone-empty-catch)
2023-04-27 00:11:45 +00:00
std : : sort ( result . begin ( ) , result . end ( ) ) ;
return result ;
}
2023-03-11 04:12:16 +00:00
void KeeperClient : : askConfirmation ( const String & prompt , std : : function < void ( ) > & & callback )
{
2023-09-02 03:34:47 +00:00
if ( ! ask_confirmation )
2024-05-09 00:07:04 +00:00
{
callback ( ) ;
return ;
}
2023-09-02 03:34:47 +00:00
2023-03-11 04:12:16 +00:00
std : : cout < < prompt < < " Continue? \n " ;
2023-09-02 03:34:47 +00:00
waiting_confirmation = true ;
2023-03-11 04:12:16 +00:00
confirmation_callback = callback ;
}
2023-04-27 00:11:45 +00:00
fs : : path KeeperClient : : getAbsolutePath ( const String & relative ) const
2023-03-10 02:23:57 +00:00
{
String result ;
if ( relative . starts_with ( ' / ' ) )
result = fs : : weakly_canonical ( relative ) ;
else
result = fs : : weakly_canonical ( cwd / relative ) ;
if ( result . ends_with ( ' / ' ) & & result . size ( ) > 1 )
result . pop_back ( ) ;
return result ;
}
2023-04-26 04:54:28 +00:00
void KeeperClient : : loadCommands ( std : : vector < Command > & & new_commands )
2023-03-10 02:23:57 +00:00
{
2023-04-26 04:54:28 +00:00
for ( const auto & command : new_commands )
2023-03-10 02:45:58 +00:00
{
2023-04-26 04:54:28 +00:00
String name = command - > getName ( ) ;
commands . insert ( { name , command } ) ;
2023-04-27 00:11:45 +00:00
registered_commands_and_four_letter_words . push_back ( std : : move ( name ) ) ;
2023-03-10 02:23:57 +00:00
}
2023-03-14 19:32:48 +00:00
2023-03-14 22:30:23 +00:00
for ( const auto & command : four_letter_word_commands )
2023-04-27 00:11:45 +00:00
registered_commands_and_four_letter_words . push_back ( command ) ;
2023-04-26 04:54:28 +00:00
2023-04-27 00:11:45 +00:00
std : : sort ( registered_commands_and_four_letter_words . begin ( ) , registered_commands_and_four_letter_words . end ( ) ) ;
2023-03-10 02:23:57 +00:00
}
void KeeperClient : : defineOptions ( Poco : : Util : : OptionSet & options )
{
Poco : : Util : : Application : : defineOptions ( options ) ;
options . addOption (
2023-04-02 20:51:10 +00:00
Poco : : Util : : Option ( " help " , " " , " show help and exit " )
2023-03-10 02:23:57 +00:00
. binding ( " help " ) ) ;
2023-04-02 20:51:10 +00:00
options . addOption (
Poco : : Util : : Option ( " host " , " h " , " server hostname. default `localhost` " )
2023-06-14 01:09:30 +00:00
. argument ( " <host> " )
2023-04-02 20:51:10 +00:00
. binding ( " host " ) ) ;
options . addOption (
2023-08-03 16:29:31 +00:00
Poco : : Util : : Option ( " port " , " p " , " server port. default `9181` " )
2023-06-14 01:09:30 +00:00
. argument ( " <port> " )
2023-04-02 20:51:10 +00:00
. binding ( " port " ) ) ;
2023-03-14 19:32:48 +00:00
options . addOption (
Poco : : Util : : Option ( " query " , " q " , " will execute given query, then exit. " )
2023-06-14 01:09:30 +00:00
. argument ( " <query> " )
2023-03-14 19:32:48 +00:00
. binding ( " query " ) ) ;
2023-03-10 02:23:57 +00:00
options . addOption (
Poco : : Util : : Option ( " connection-timeout " , " " , " set connection timeout in seconds. default 10s. " )
2023-06-14 01:09:30 +00:00
. argument ( " <seconds> " )
2023-03-10 02:23:57 +00:00
. binding ( " connection-timeout " ) ) ;
options . addOption (
Poco : : Util : : Option ( " session-timeout " , " " , " set session timeout in seconds. default 10s. " )
2023-06-14 01:09:30 +00:00
. argument ( " <seconds> " )
2023-03-10 02:23:57 +00:00
. binding ( " session-timeout " ) ) ;
options . addOption (
Poco : : Util : : Option ( " operation-timeout " , " " , " set operation timeout in seconds. default 10s. " )
2023-06-14 01:09:30 +00:00
. argument ( " <seconds> " )
2023-03-10 02:23:57 +00:00
. binding ( " operation-timeout " ) ) ;
2023-08-24 02:18:24 +00:00
options . addOption (
Poco : : Util : : Option ( " config-file " , " c " , " if set, will try to get a connection string from clickhouse config. default `config.xml` " )
. argument ( " <file> " )
. binding ( " config-file " ) ) ;
2023-03-10 02:23:57 +00:00
options . addOption (
Poco : : Util : : Option ( " history-file " , " " , " set path of history file. default `~/.keeper-client-history` " )
2023-06-14 01:09:30 +00:00
. argument ( " <file> " )
2023-03-10 02:23:57 +00:00
. binding ( " history-file " ) ) ;
2023-03-14 21:50:09 +00:00
options . addOption (
Poco : : Util : : Option ( " log-level " , " " , " set log level " )
2023-06-14 01:09:30 +00:00
. argument ( " <level> " )
2023-03-14 21:50:09 +00:00
. binding ( " log-level " ) ) ;
2023-09-07 00:36:39 +00:00
options . addOption (
Poco : : Util : : Option ( " no-confirmation " , " " , " if set, will not require a confirmation on several commands. default false for interactive and true for query " )
. binding ( " no-confirmation " ) ) ;
options . addOption (
Poco : : Util : : Option ( " tests-mode " , " " , " run keeper-client in a special mode for tests. all commands output are separated by special symbols. default false " )
. binding ( " tests-mode " ) ) ;
2023-03-10 02:23:57 +00:00
}
void KeeperClient : : initialize ( Poco : : Util : : Application & /* self */ )
{
2023-04-27 00:11:45 +00:00
suggest . setCompletionsCallback (
[ & ] ( const String & prefix , size_t /* prefix_length */ ) { return getCompletions ( prefix ) ; } ) ;
2023-03-10 02:23:57 +00:00
loadCommands ( {
2023-04-26 04:54:28 +00:00
std : : make_shared < LSCommand > ( ) ,
std : : make_shared < CDCommand > ( ) ,
std : : make_shared < SetCommand > ( ) ,
std : : make_shared < CreateCommand > ( ) ,
2023-08-07 16:08:31 +00:00
std : : make_shared < TouchCommand > ( ) ,
2023-04-26 04:54:28 +00:00
std : : make_shared < GetCommand > ( ) ,
2023-09-07 00:36:39 +00:00
std : : make_shared < ExistsCommand > ( ) ,
2023-06-17 03:24:10 +00:00
std : : make_shared < GetStatCommand > ( ) ,
2023-07-26 05:57:16 +00:00
std : : make_shared < FindSuperNodes > ( ) ,
2023-08-07 16:08:31 +00:00
std : : make_shared < DeleteStaleBackups > ( ) ,
2023-06-21 03:41:48 +00:00
std : : make_shared < FindBigFamily > ( ) ,
2023-04-26 04:54:28 +00:00
std : : make_shared < RMCommand > ( ) ,
std : : make_shared < RMRCommand > ( ) ,
2023-09-02 03:34:47 +00:00
std : : make_shared < ReconfigCommand > ( ) ,
2023-09-07 00:36:39 +00:00
std : : make_shared < SyncCommand > ( ) ,
2023-04-26 04:54:28 +00:00
std : : make_shared < HelpCommand > ( ) ,
std : : make_shared < FourLetterWordCommand > ( ) ,
2023-10-24 15:02:54 +00:00
std : : make_shared < GetDirectChildrenNumberCommand > ( ) ,
2023-10-11 02:28:28 +00:00
std : : make_shared < GetAllChildrenNumberCommand > ( ) ,
2023-03-10 02:23:57 +00:00
} ) ;
String home_path ;
const char * home_path_cstr = getenv ( " HOME " ) ; // NOLINT(concurrency-mt-unsafe)
if ( home_path_cstr )
home_path = home_path_cstr ;
if ( config ( ) . has ( " history-file " ) )
history_file = config ( ) . getString ( " history-file " ) ;
else
history_file = home_path + " /.keeper-client-history " ;
if ( ! history_file . empty ( ) & & ! fs : : exists ( history_file ) )
{
try
{
FS : : createFile ( history_file ) ;
}
catch ( const ErrnoException & e )
{
if ( e . getErrno ( ) ! = EEXIST )
throw ;
}
}
2023-08-25 01:45:10 +00:00
String default_log_level ;
if ( config ( ) . has ( " query " ) )
/// We don't want to see any information log in query mode, unless it was set explicitly
default_log_level = " error " ;
else
default_log_level = " information " ;
2023-08-29 02:23:01 +00:00
Poco : : Logger : : root ( ) . setLevel ( config ( ) . getString ( " log-level " , default_log_level ) ) ;
2023-03-14 21:50:09 +00:00
2023-03-10 02:23:57 +00:00
EventNotifier : : init ( ) ;
}
bool KeeperClient : : processQueryText ( const String & text )
{
if ( exit_strings . find ( text ) ! = exit_strings . end ( ) )
return false ;
try
{
2023-09-02 03:34:47 +00:00
if ( waiting_confirmation )
2023-03-11 04:12:16 +00:00
{
2023-09-02 03:34:47 +00:00
waiting_confirmation = false ;
2023-04-26 04:54:28 +00:00
if ( text . size ( ) = = 1 & & ( text = = " y " | | text = = " Y " ) )
2023-03-11 04:12:16 +00:00
confirmation_callback ( ) ;
2023-04-26 04:54:28 +00:00
return true ;
2023-03-11 04:12:16 +00:00
}
2023-04-26 04:54:28 +00:00
KeeperParser parser ;
const char * begin = text . data ( ) ;
2023-09-02 03:34:47 +00:00
const char * end = begin + text . size ( ) ;
2023-04-26 04:54:28 +00:00
2023-09-02 03:34:47 +00:00
while ( begin < end )
2023-03-11 04:12:16 +00:00
{
2023-09-02 03:34:47 +00:00
String message ;
ASTPtr res = tryParseQuery (
parser ,
begin ,
end ,
/* out_error_message = */ message ,
/* hilite = */ true ,
/* description = */ " " ,
/* allow_multi_statements = */ true ,
/* max_query_size = */ 0 ,
/* max_parser_depth = */ 0 ,
2024-03-17 18:53:58 +00:00
/* max_parser_backtracks = */ 0 ,
2023-09-02 03:34:47 +00:00
/* skip_insignificant = */ false ) ;
if ( ! res )
{
std : : cerr < < message < < " \n " ;
return true ;
}
auto * query = res - > as < ASTKeeperQuery > ( ) ;
auto command = KeeperClient : : commands . find ( query - > command ) ;
command - > second - > execute ( query , this ) ;
2023-03-11 04:12:16 +00:00
}
2023-03-10 02:23:57 +00:00
}
catch ( Coordination : : Exception & err )
{
std : : cerr < < err . message ( ) < < " \n " ;
}
return true ;
}
2023-09-07 00:36:39 +00:00
void KeeperClient : : runInteractiveReplxx ( )
2023-03-10 02:23:57 +00:00
{
LineReader : : Patterns query_extenders = { " \\ " } ;
LineReader : : Patterns query_delimiters = { } ;
2023-08-04 07:20:01 +00:00
char word_break_characters [ ] = " \t \v \f \a \b \r \n / " ;
ReplxxLineReader lr (
suggest ,
history_file ,
/* multiline= */ false ,
query_extenders ,
query_delimiters ,
word_break_characters ,
/* highlighter_= */ { } ) ;
2023-03-10 02:23:57 +00:00
lr . enableBracketedPaste ( ) ;
while ( true )
{
2023-03-11 04:12:16 +00:00
String prompt ;
2023-09-02 03:34:47 +00:00
if ( waiting_confirmation )
2023-03-11 04:12:16 +00:00
prompt = " [y/n] " ;
else
prompt = cwd . string ( ) + " :) " ;
auto input = lr . readLine ( prompt , " :-] " ) ;
2023-03-10 02:23:57 +00:00
if ( input . empty ( ) )
break ;
if ( ! processQueryText ( input ) )
break ;
}
}
2023-09-07 00:36:39 +00:00
void KeeperClient : : runInteractiveInputStream ( )
{
for ( String input ; std : : getline ( std : : cin , input ) ; )
{
if ( ! processQueryText ( input ) )
break ;
std : : cout < < " \a \a \a \a " < < std : : endl ;
std : : cerr < < std : : flush ;
}
}
void KeeperClient : : runInteractive ( )
{
if ( config ( ) . hasOption ( " tests-mode " ) )
runInteractiveInputStream ( ) ;
else
runInteractiveReplxx ( ) ;
}
2023-04-02 20:51:10 +00:00
int KeeperClient : : main ( const std : : vector < String > & /* args */ )
2023-03-10 02:23:57 +00:00
{
2023-04-27 00:39:33 +00:00
if ( config ( ) . hasOption ( " help " ) )
{
Poco : : Util : : HelpFormatter help_formatter ( KeeperClient : : options ( ) ) ;
auto header_str = fmt : : format ( " {} [OPTION] \n " , commandName ( ) ) ;
help_formatter . setHeader ( header_str ) ;
help_formatter . format ( std : : cout ) ;
return 0 ;
}
2023-08-24 02:18:24 +00:00
DB : : ConfigProcessor config_processor ( config ( ) . getString ( " config-file " , " config.xml " ) ) ;
/// This will handle a situation when clickhouse is running on the embedded config, but config.d folder is also present.
2024-04-07 09:51:45 +00:00
ConfigProcessor : : registerEmbeddedConfig ( " config.xml " , " <clickhouse/> " ) ;
2023-08-24 02:18:24 +00:00
auto clickhouse_config = config_processor . loadConfig ( ) ;
2023-08-29 02:23:01 +00:00
Poco : : Util : : AbstractConfiguration : : Keys keys ;
clickhouse_config . configuration - > keys ( " zookeeper " , keys ) ;
2023-08-24 02:18:24 +00:00
2023-08-29 02:23:01 +00:00
if ( ! config ( ) . has ( " host " ) & & ! config ( ) . has ( " port " ) & & ! keys . empty ( ) )
2023-08-24 02:18:24 +00:00
{
2024-01-23 17:04:50 +00:00
LOG_INFO ( getLogger ( " KeeperClient " ) , " Found keeper node in the config.xml, will use it for connection " ) ;
2023-08-24 02:18:24 +00:00
2023-08-29 02:23:01 +00:00
for ( const auto & key : keys )
{
String prefix = " zookeeper. " + key ;
String host = clickhouse_config . configuration - > getString ( prefix + " .host " ) ;
String port = clickhouse_config . configuration - > getString ( prefix + " .port " ) ;
if ( clickhouse_config . configuration - > has ( prefix + " .secure " ) )
host = " secure:// " + host ;
zk_args . hosts . push_back ( host + " : " + port ) ;
}
2023-08-24 02:18:24 +00:00
}
else
{
2023-08-29 02:23:01 +00:00
String host = config ( ) . getString ( " host " , " localhost " ) ;
String port = config ( ) . getString ( " port " , " 9181 " ) ;
zk_args . hosts . push_back ( host + " : " + port ) ;
2023-08-24 02:18:24 +00:00
}
2023-03-10 02:23:57 +00:00
zk_args . connection_timeout_ms = config ( ) . getInt ( " connection-timeout " , 10 ) * 1000 ;
zk_args . session_timeout_ms = config ( ) . getInt ( " session-timeout " , 10 ) * 1000 ;
zk_args . operation_timeout_ms = config ( ) . getInt ( " operation-timeout " , 10 ) * 1000 ;
2024-02-15 21:17:31 +00:00
zookeeper = zkutil : : ZooKeeper : : createWithoutKillingPreviousSessions ( zk_args ) ;
2023-03-10 02:23:57 +00:00
2023-09-07 00:36:39 +00:00
if ( config ( ) . has ( " no-confirmation " ) | | config ( ) . has ( " query " ) )
ask_confirmation = false ;
2023-03-14 19:32:48 +00:00
if ( config ( ) . has ( " query " ) )
2023-09-02 03:34:47 +00:00
{
processQueryText ( config ( ) . getString ( " query " ) ) ;
}
2023-03-14 19:32:48 +00:00
else
runInteractive ( ) ;
2023-03-10 02:23:57 +00:00
return 0 ;
}
}
int mainEntryClickHouseKeeperClient ( int argc , char * * argv )
{
try
{
DB : : KeeperClient client ;
client . init ( argc , argv ) ;
return client . run ( ) ;
}
catch ( const DB : : Exception & e )
{
std : : cerr < < DB : : getExceptionMessage ( e , false ) < < std : : endl ;
return 1 ;
}
catch ( const boost : : program_options : : error & e )
{
std : : cerr < < " Bad arguments: " < < e . what ( ) < < std : : endl ;
return DB : : ErrorCodes : : BAD_ARGUMENTS ;
}
catch ( . . . )
{
std : : cerr < < DB : : getCurrentExceptionMessage ( true ) < < std : : endl ;
return 1 ;
}
}