diff --git a/dbms/src/TableFunctions/TableFunctionRemote.cpp b/dbms/src/TableFunctions/TableFunctionRemote.cpp index 63c917b6832..58537492e53 100644 --- a/dbms/src/TableFunctions/TableFunctionRemote.cpp +++ b/dbms/src/TableFunctions/TableFunctionRemote.cpp @@ -185,18 +185,18 @@ StoragePtr TableFunctionRemote::execute(const ASTPtr & ast_function, const Conte { ASTs & args_func = typeid_cast(*ast_function).children; - const char * err = "Table function 'remote' requires from 2 to 5 parameters: " - "addresses pattern, name of remote database, name of remote table, [username, [password]]."; + const size_t max_args = is_cluster_function ? 3 : 5; if (args_func.size() != 1) - throw Exception(err, ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + throw Exception(help_message, ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); ASTs & args = typeid_cast(*args_func.at(0)).children; - if (args.size() < 2 || args.size() > 5) - throw Exception(err, ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + if (args.size() < 2 || args.size() > max_args) + throw Exception(help_message, ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - String description; + String cluster_name; + String cluster_description; String remote_database; String remote_table; String username; @@ -216,7 +216,18 @@ StoragePtr TableFunctionRemote::execute(const ASTPtr & ast_function, const Conte return safeGet(lit->value); }; - description = getStringLiteral(*args[arg_num], "Hosts pattern"); + if (is_cluster_function) + { + ASTPtr ast_name = evaluateConstantExpressionOrIdentifierAsLiteral(args[arg_num], context); + cluster_name = static_cast(*ast_name).value.safeGet(); + } + else + { + if (auto ast_cluster = typeid_cast(args[arg_num].get())) + cluster_name = ast_cluster->name; + else + cluster_description = getStringLiteral(*args[arg_num], "Hosts pattern"); + } ++arg_num; args[arg_num] = evaluateConstantExpressionOrIdentifierAsLiteral(args[arg_num], context); @@ -233,29 +244,33 @@ StoragePtr TableFunctionRemote::execute(const ASTPtr & ast_function, const Conte else { if (arg_num >= args.size()) - throw Exception(err, ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + throw Exception(help_message, ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); args[arg_num] = evaluateConstantExpressionOrIdentifierAsLiteral(args[arg_num], context); remote_table = static_cast(*args[arg_num]).value.safeGet(); ++arg_num; } - if (arg_num < args.size()) + /// Username and password parameters are prohibited in cluster version of the function + if (!is_cluster_function) { - username = getStringLiteral(*args[arg_num], "Username"); - ++arg_num; - } - else - username = "default"; + if (arg_num < args.size()) + { + username = getStringLiteral(*args[arg_num], "Username"); + ++arg_num; + } + else + username = "default"; - if (arg_num < args.size()) - { - password = getStringLiteral(*args[arg_num], "Password"); - ++arg_num; + if (arg_num < args.size()) + { + password = getStringLiteral(*args[arg_num], "Password"); + ++arg_num; + } } if (arg_num < args.size()) - throw Exception(err, ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + throw Exception(help_message, ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); /// ExpressionAnalyzer will be created in InterpreterSelectQuery that will meet these `Identifier` when processing the request. /// We need to mark them as the name of the database or table, because the default value is column. @@ -265,16 +280,26 @@ StoragePtr TableFunctionRemote::execute(const ASTPtr & ast_function, const Conte size_t max_addresses = context.getSettingsRef().table_function_remote_max_addresses; - std::vector> names; - std::vector shards = parseDescription(description, 0, description.size(), ',', max_addresses); + ClusterPtr cluster; + if (!cluster_name.empty()) + { + /// Use an existing cluster from the main config + cluster = context.getCluster(cluster_name); + } + else + { + /// Create new cluster from the scratch + std::vector shards = parseDescription(cluster_description, 0, cluster_description.size(), ',', max_addresses); - for (size_t i = 0; i < shards.size(); ++i) - names.push_back(parseDescription(shards[i], 0, shards[i].size(), '|', max_addresses)); + std::vector> names; + for (size_t i = 0; i < shards.size(); ++i) + names.push_back(parseDescription(shards[i], 0, shards[i].size(), '|', max_addresses)); - if (names.empty()) - throw Exception("Shard list is empty after parsing first argument", ErrorCodes::BAD_ARGUMENTS); + if (names.empty()) + throw Exception("Shard list is empty after parsing first argument", ErrorCodes::BAD_ARGUMENTS); - auto cluster = std::make_shared(context.getSettings(), names, username, password, context.getTCPPort()); + cluster = std::make_shared(context.getSettings(), names, username, password, context.getTCPPort()); + } auto res = StorageDistributed::createWithOwnCluster( getName(), @@ -288,9 +313,23 @@ StoragePtr TableFunctionRemote::execute(const ASTPtr & ast_function, const Conte } +TableFunctionRemote::TableFunctionRemote(const std::string & name_) + : name(name_) +{ + is_cluster_function = name == "cluster"; + + std::stringstream ss; + ss << "Table function '" << name + "' requires from 2 to " << (is_cluster_function ? 3 : 5) << " parameters" + << ": , , " + << (is_cluster_function ? "" : ", [username, [password]]."); + help_message = ss.str(); +} + + void registerTableFunctionRemote(TableFunctionFactory & factory) { - factory.registerFunction(); + factory.registerFunction("remote", [] () -> TableFunctionPtr { return std::make_shared("remote"); }); + factory.registerFunction("cluster", [] () -> TableFunctionPtr { return std::make_shared("cluster"); }); } } diff --git a/dbms/src/TableFunctions/TableFunctionRemote.h b/dbms/src/TableFunctions/TableFunctionRemote.h index 1891dbd3795..d1cce599903 100644 --- a/dbms/src/TableFunctions/TableFunctionRemote.h +++ b/dbms/src/TableFunctions/TableFunctionRemote.h @@ -11,13 +11,23 @@ namespace DB * For example * SELECT count() FROM remote('example01-01-1', merge, hits) - go to `example01-01-1`, in the merge database, the hits table. * An expression that generates a set of shards and replicas can also be specified as the host name - see below. + * Also, there is a cluster version of the function: cluster('existing_cluster_name', 'db', 'table') */ class TableFunctionRemote : public ITableFunction { public: - static constexpr auto name = "remote"; + + explicit TableFunctionRemote(const std::string & name_ = "remote"); + std::string getName() const override { return name; } + StoragePtr execute(const ASTPtr & ast_function, const Context & context) const override; + +private: + + std::string name; + bool is_cluster_function; + std::string help_message; }; } diff --git a/dbms/tests/queries/0_stateless/00557_remote_port.reference b/dbms/tests/queries/0_stateless/00557_remote_port.reference index 405d3348775..c4f459c5fe7 100644 --- a/dbms/tests/queries/0_stateless/00557_remote_port.reference +++ b/dbms/tests/queries/0_stateless/00557_remote_port.reference @@ -6,3 +6,6 @@ 0 0 0 +0 +0 +0 diff --git a/dbms/tests/queries/0_stateless/00557_remote_port.sh b/dbms/tests/queries/0_stateless/00557_remote_port.sh index 9cb3c29e3ad..e2a736f02a8 100755 --- a/dbms/tests/queries/0_stateless/00557_remote_port.sh +++ b/dbms/tests/queries/0_stateless/00557_remote_port.sh @@ -20,3 +20,7 @@ fi $CLICKHOUSE_CLIENT -q "SELECT * FROM remote('${CLICKHOUSE_HOST}', system, one);" $CLICKHOUSE_CLIENT -q "SELECT * FROM remote('${CLICKHOUSE_HOST}:${CLICKHOUSE_PORT_TCP}', system, one);" + +$CLICKHOUSE_CLIENT -q "SELECT * FROM remote(test_shard_localhost, system, one);" +$CLICKHOUSE_CLIENT -q "SELECT * FROM remote(test_shard_localhost, system, one, 'default', '');" +$CLICKHOUSE_CLIENT -q "SELECT * FROM cluster('test_shard_localhost', system, one);"