mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-10 01:25:21 +00:00
Merge pull request #50689 from arenadata/ADQM-871
Added connection string to clickhouse-client
This commit is contained in:
commit
2643fd2c25
@ -194,7 +194,129 @@ You can pass parameters to `clickhouse-client` (all parameters have a default va
|
||||
- `--print-profile-events` – Print `ProfileEvents` packets.
|
||||
- `--profile-events-delay-ms` – Delay between printing `ProfileEvents` packets (-1 - print only totals, 0 - print every single packet).
|
||||
|
||||
Since version 20.5, `clickhouse-client` has automatic syntax highlighting (always enabled).
|
||||
Instead of `--host`, `--port`, `--user` and `--password` options, ClickHouse client also supports connection strings (see next section).
|
||||
|
||||
|
||||
## Connection string {#connection_string}
|
||||
|
||||
clickhouse-client alternatively supports connecting to clickhouse server using a connection string similar to [MongoDB](https://www.mongodb.com/docs/manual/reference/connection-string/), [PostgreSQL](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING), [MySQL](https://dev.mysql.com/doc/refman/8.0/en/connecting-using-uri-or-key-value-pairs.html#connecting-using-uri). It has the following syntax:
|
||||
|
||||
```text
|
||||
clickhouse:[//[user[:password]@][hosts_and_ports]][/database][?query_parameters]
|
||||
```
|
||||
|
||||
Where
|
||||
|
||||
- `user` - (optional) is a user name,
|
||||
- `password` - (optional) is a user password. If `:` is specified and the password is blank, the client will prompt for the user's password.
|
||||
- `hosts_and_ports` - (optional) is a list of hosts and optional ports `host[:port] [, host:[port]], ...`,
|
||||
- `database` - (optional) is the database name,
|
||||
- `query_parameters` - (optional) is a list of key-value pairs `param1=value1[,¶m2=value2], ...`. For some parameters, no value is required. Parameter names and values are case-sensitive.
|
||||
|
||||
If no user is specified, `default` user without password will be used.
|
||||
If no host is specified, the `localhost` will be used (localhost).
|
||||
If no port is specified is not specified, `9000` will be used as port.
|
||||
If no database is specified, the `default` database will be used.
|
||||
|
||||
If the user name, password or database was specified in the connection string, it cannot be specified using `--user`, `--password` or `--database` (and vice versa).
|
||||
|
||||
The host component can either be an a host name and IP address. Put an IPv6 address in square brackets to specify it:
|
||||
|
||||
```text
|
||||
clickhouse://[2001:db8::1234]
|
||||
```
|
||||
|
||||
URI allows multiple hosts to be connected to. Connection strings can contain multiple hosts. ClickHouse-client will try to connect to these hosts in order (i.e. from left to right). After the connection is established, no attempt to connect to the remaining hosts is made.
|
||||
|
||||
The connection string must be specified as the first argument of clickhouse-client. The connection string can be combined with arbitrary other [command-line-options](#command-line-options) except `--host/-h` and `--port`.
|
||||
|
||||
The following keys are allowed for component `query_parameter`:
|
||||
|
||||
- `secure` or shorthanded `s` - no value. If specified, client will connect to the server over a secure connection (TLS). See `secure` in [command-line-options](#command-line-options)
|
||||
|
||||
### Percent encoding {#connection_string_uri_percent_encoding}
|
||||
|
||||
Non-US ASCII, spaces and special characters in the `user`, `password`, `hosts`, `database` and `query parameters` must be [percent-encoded](https://en.wikipedia.org/wiki/URL_encoding).
|
||||
|
||||
### Examples {#connection_string_examples}
|
||||
|
||||
Connect to localhost using port 9000 and execute the query `SELECT 1`.
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse://localhost:9000 --query "SELECT 1"
|
||||
```
|
||||
|
||||
Connect to localhost using user `john` with password `secret`, host `127.0.0.1` and port `9000`
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse://john:secret@127.0.0.1:9000
|
||||
```
|
||||
|
||||
Connect to localhost using default user, host with IPV6 address `[::1]` and port `9000`.
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse://[::1]:9000
|
||||
```
|
||||
|
||||
Connect to localhost using port 9000 in multiline mode.
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse://localhost:9000 '-m'
|
||||
```
|
||||
|
||||
Connect to localhost using port 9000 with the user `default`.
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse://default@localhost:9000
|
||||
|
||||
# equivalent to:
|
||||
clickhouse-client clickhouse://localhost:9000 --user default
|
||||
```
|
||||
|
||||
Connect to localhost using port 9000 to `my_database` database.
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse://localhost:9000/my_database
|
||||
|
||||
# equivalent to:
|
||||
clickhouse-client clickhouse://localhost:9000 --database my_database
|
||||
```
|
||||
|
||||
Connect to localhost using port 9000 to `my_database` database specified in the connection string and a secure connection using shorthanded 's' URI parameter.
|
||||
|
||||
```bash
|
||||
clickhouse-client clickhouse://localhost/my_database?s
|
||||
|
||||
# equivalent to:
|
||||
clickhouse-client clickhouse://localhost/my_database -s
|
||||
```
|
||||
|
||||
Connect to default host using default port, default user, and default database.
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse:
|
||||
```
|
||||
|
||||
Connect to the default host using the default port, using user `my_user` and no password.
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse://my_user@
|
||||
|
||||
# Using a blank password between : and @ means to asking user to enter the password before starting the connection.
|
||||
clickhouse-client clickhouse://my_user:@
|
||||
```
|
||||
|
||||
Connect to localhost using email as the user name. `@` symbol is percent encoded to `%40`.
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse://some_user%40some_mail.com@localhost:9000
|
||||
```
|
||||
|
||||
Connect to one of provides hosts: `192.168.1.15`, `192.168.1.25`.
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse://192.168.1.15,192.168.1.25
|
||||
```
|
||||
|
||||
### Configuration Files {#configuration_files}
|
||||
|
||||
|
@ -142,7 +142,129 @@ $ clickhouse-client --param_tbl="numbers" --param_db="system" --param_col="numbe
|
||||
- `--history_file` - путь к файлу с историей команд.
|
||||
- `--param_<name>` — значение параметра для [запроса с параметрами](#cli-queries-with-parameters).
|
||||
|
||||
Начиная с версии 20.5, в `clickhouse-client` есть автоматическая подсветка синтаксиса (включена всегда).
|
||||
Вместо параметров `--host`, `--port`, `--user` и `--password` клиент ClickHouse также поддерживает строки подключения (смотри следующий раздел).
|
||||
|
||||
## Строка подключения {#connection_string}
|
||||
|
||||
clickhouse-client также поддерживает подключение к серверу clickhouse с помощью строки подключения, аналогичной [MongoDB](https://www.mongodb.com/docs/manual/reference/connection-string/), [PostgreSQL](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING), [MySQL](https://dev.mysql.com/doc/refman/8.0/en/connecting-using-uri-or-key-value-pairs.html#connecting-using-uri). Она имеет следующий синтаксис:
|
||||
|
||||
```text
|
||||
clickhouse:[//[user[:password]@][hosts_and_ports]][/database][?query_parameters]
|
||||
```
|
||||
|
||||
Где
|
||||
|
||||
- `user` - (необязательно) - это имя пользователя,
|
||||
- `password` - (необязательно) - Пароль пользователя. Если символ `:` укаказан, и пароль пуст, то клиент запросит ввести пользователя пароль.
|
||||
- `hosts_and_ports` - (необязательно) - список хостов и необязательных портов. `host[:port] [, host:[port]], ...`,
|
||||
- `database` - (необязательно) - это имя базы данных,
|
||||
- `query_parameters` - (опционально) список пар ключ-значение `param1=value1[,¶m2=value2], ...`. Для некоторых параметров значение не требуется. Имена и значения параметров чувствительны к регистру.
|
||||
|
||||
Если user не указан, будут использоваться имя пользователя `default`.
|
||||
Если host не указан, будет использован хост `localhost`.
|
||||
Если port не указан, будет использоваться порт `9000`.
|
||||
Если база данных не указана, будет использоваться база данных `default`.
|
||||
|
||||
Если имя пользователя, пароль или база данных были указаны в строке подключения, их нельзя указать с помощью `--user`, `--password` или `--database` (и наоборот).
|
||||
|
||||
Параметр host может быть либо именем хоста, либо IP-адресом. Для указания IPv6-адреса поместите его в квадратные скобки:
|
||||
|
||||
```text
|
||||
clickhouse://[2001:db8::1234]
|
||||
```
|
||||
|
||||
URI позволяет подключаться к нескольким хостам. Строки подключения могут содержать несколько хостов. ClickHouse-client будет пытаться подключиться к этим хостам по порядку (т.е. слева направо). После установления соединения попытки подключения к оставшимся хостам не предпринимаются.
|
||||
|
||||
|
||||
|
||||
Строка подключения должна быть указана в первом аргументе clickhouse-client. Строка подключения может комбинироваться с другими [параметрами командной строки] (#command-line-options) кроме `--host/-h` и `--port`.
|
||||
|
||||
Для компонента `query_parameter` разрешены следующие ключи:
|
||||
|
||||
- `secure` или сокращенно `s` - без значение. Если параметр указан, то соединение с сервером будет осуществляться по защищенному каналу (TLS). См. `secure` в [command-line-options](#command-line-options).
|
||||
|
||||
### Кодирование URI {#connection_string_uri_percent_encoding}
|
||||
|
||||
Не US ASCII и специальные символы в имени пользователя, пароле, хостах, базе данных и параметрах запроса должны быть [закодированы](https://ru.wikipedia.org/wiki/URL#%D0%9A%D0%BE%D0%B4%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_URL).
|
||||
|
||||
### Примеры {#connection_string_examples}
|
||||
|
||||
Подключиться к localhost через порт 9000 и выполнить запрос `SELECT 1`
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse://localhost:9000 --query "SELECT 1"
|
||||
```
|
||||
Подключиться к localhost, используя пользователя `john` с паролем `secret`, хост `127.0.0.1` и порт `9000`
|
||||
|
||||
``bash
|
||||
clickhouse-client clickhouse://john:secret@127.0.0.1:9000
|
||||
```
|
||||
|
||||
Подключиться к localhost, используя пользователя по умолчанию, хост с IPV6 адресом `[::1]` и порт `9000`.
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse://[::1]:9000
|
||||
```
|
||||
|
||||
Подключиться к localhost через порт 9000 многострочном режиме.
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse://localhost:9000 '-m'
|
||||
```
|
||||
|
||||
Подключиться к localhost через порт 9000 с пользователем default.
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse://default@localhost:9000
|
||||
|
||||
# Эквивалетно:
|
||||
clickhouse-client clickhouse://localhost:9000 --user default
|
||||
```
|
||||
|
||||
Подключиться к localhost через порт 9000 с базой данных `my_database`
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse://localhost:9000/my_database
|
||||
|
||||
# Эквивалетно:
|
||||
clickhouse-client clickhouse://localhost:9000 --database my_database
|
||||
```
|
||||
|
||||
Подключиться к localhost через порт 9000 с базой данных `my_database`, указанной в строке подключения, используя безопасным соединением при помощи короткого варианта параметра URI 's'.
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse://localhost/my_database?s
|
||||
|
||||
# Эквивалетно:
|
||||
clickhouse-client clickhouse://localhost/my_database -s
|
||||
```
|
||||
|
||||
Подключиться к хосту по умолчанию с использованием порта по умолчанию, пользователя по умолчанию, и базы данных по умолчанию.
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse:
|
||||
```
|
||||
|
||||
Подключиться к хосту по умолчанию через порт по умолчанию, используя имя пользователя `my_user` без пароля.
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse://my_user@
|
||||
|
||||
# Использование пустого пароля между : и @ означает, что пользователь должен ввести пароль перед началом соединения.
|
||||
clickhouse-client clickhouse://my_user:@
|
||||
```
|
||||
|
||||
Подключиться к localhost, используя электронную почту, как имя пользователя. Символ `@` закодирован как `%40`.
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse://some_user%40some_mail.com@localhost:9000
|
||||
```
|
||||
|
||||
Подключится к одному из хостов: `192.168.1.15`, `192.168.1.25`.
|
||||
|
||||
``` bash
|
||||
clickhouse-client clickhouse://192.168.1.15,192.168.1.25
|
||||
```
|
||||
|
||||
### Конфигурационные файлы {#configuration_files}
|
||||
|
||||
|
@ -5,13 +5,13 @@
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <Common/scope_guard_safe.h>
|
||||
#include <boost/program_options.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include "Client.h"
|
||||
#include "Client/ConnectionString.h"
|
||||
#include "Core/Protocol.h"
|
||||
#include "Parsers/formatAST.h"
|
||||
|
||||
@ -1248,6 +1248,9 @@ void Client::readArguments(
|
||||
std::vector<Arguments> & external_tables_arguments,
|
||||
std::vector<Arguments> & hosts_and_ports_arguments)
|
||||
{
|
||||
bool has_connection_string = argc >= 2 && tryParseConnectionString(std::string_view(argv[1]), common_arguments, hosts_and_ports_arguments);
|
||||
int start_argument_index = has_connection_string ? 2 : 1;
|
||||
|
||||
/** We allow different groups of arguments:
|
||||
* - common arguments;
|
||||
* - arguments for any number of external tables each in form "--external args...",
|
||||
@ -1260,10 +1263,13 @@ void Client::readArguments(
|
||||
std::string prev_host_arg;
|
||||
std::string prev_port_arg;
|
||||
|
||||
for (int arg_num = 1; arg_num < argc; ++arg_num)
|
||||
for (int arg_num = start_argument_index; arg_num < argc; ++arg_num)
|
||||
{
|
||||
std::string_view arg = argv[arg_num];
|
||||
|
||||
if (has_connection_string)
|
||||
checkIfCmdLineOptionCanBeUsedWithConnectionString(arg);
|
||||
|
||||
if (arg == "--external")
|
||||
{
|
||||
in_external_group = true;
|
||||
|
239
src/Client/ConnectionString.cpp
Normal file
239
src/Client/ConnectionString.cpp
Normal file
@ -0,0 +1,239 @@
|
||||
#include "ConnectionString.h"
|
||||
|
||||
#include <Common/Exception.h>
|
||||
#include <Poco/Exception.h>
|
||||
#include <Poco/URI.h>
|
||||
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
namespace ErrorCodes
|
||||
{
|
||||
extern const int BAD_ARGUMENTS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using namespace std::string_literals;
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
constexpr auto CONNECTION_URI_SCHEME = "clickhouse:"sv;
|
||||
|
||||
const std::unordered_map<std::string_view, std::string_view> PROHIBITED_CLIENT_OPTIONS = {
|
||||
/// Client option, client option long name
|
||||
{"-h", "--host"},
|
||||
{"--host", "--host"},
|
||||
{"--port", "--port"},
|
||||
{"--connection", "--connection"},
|
||||
};
|
||||
|
||||
std::string uriDecode(const std::string & uri_encoded_string, bool plus_as_space)
|
||||
{
|
||||
std::string decoded_string;
|
||||
Poco::URI::decode(uri_encoded_string, decoded_string, plus_as_space);
|
||||
return decoded_string;
|
||||
}
|
||||
|
||||
void getHostAndPort(const Poco::URI & uri, std::vector<std::vector<std::string>> & hosts_and_ports_arguments)
|
||||
{
|
||||
std::vector<std::string> host_and_port;
|
||||
const std::string & host = uri.getHost();
|
||||
if (!host.empty())
|
||||
{
|
||||
host_and_port.push_back("--host=" + uriDecode(host, false));
|
||||
}
|
||||
|
||||
// Port can be written without host (":9000"). Empty host name equals to default host.
|
||||
auto port = uri.getPort();
|
||||
if (port != 0)
|
||||
host_and_port.push_back("--port=" + std::to_string(port));
|
||||
|
||||
if (!host_and_port.empty())
|
||||
hosts_and_ports_arguments.push_back(std::move(host_and_port));
|
||||
}
|
||||
|
||||
void buildConnectionString(
|
||||
std::string_view host_and_port,
|
||||
std::string_view right_part,
|
||||
Poco::URI & uri,
|
||||
std::vector<std::vector<std::string>> & hosts_and_ports_arguments)
|
||||
{
|
||||
// User info does not matter in sub URI
|
||||
auto uri_string = std::string(CONNECTION_URI_SCHEME);
|
||||
if (!host_and_port.empty())
|
||||
{
|
||||
uri_string.append("//");
|
||||
uri_string.append(host_and_port);
|
||||
}
|
||||
|
||||
// Right part from string includes '/database?[params]'
|
||||
uri_string.append(right_part);
|
||||
try
|
||||
{
|
||||
uri = Poco::URI(uri_string);
|
||||
}
|
||||
catch (const Poco::URISyntaxException & invalid_uri_exception)
|
||||
{
|
||||
throw DB::Exception(DB::ErrorCodes::BAD_ARGUMENTS,
|
||||
"Invalid connection string syntax {}: {}", uri_string, invalid_uri_exception.what());
|
||||
}
|
||||
|
||||
getHostAndPort(uri, hosts_and_ports_arguments);
|
||||
}
|
||||
|
||||
std::string makeArgument(const std::string & connection_string_parameter_name)
|
||||
{
|
||||
return (connection_string_parameter_name.size() == 1 ? "-"s : "--"s) + connection_string_parameter_name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
bool tryParseConnectionString(
|
||||
std::string_view connection_string,
|
||||
std::vector<std::string> & common_arguments,
|
||||
std::vector<std::vector<std::string>> & hosts_and_ports_arguments)
|
||||
{
|
||||
if (connection_string == CONNECTION_URI_SCHEME)
|
||||
return true;
|
||||
|
||||
if (!connection_string.starts_with(CONNECTION_URI_SCHEME))
|
||||
return false;
|
||||
|
||||
size_t offset = CONNECTION_URI_SCHEME.size();
|
||||
if ((connection_string.substr(offset).starts_with("//")))
|
||||
offset += 2;
|
||||
|
||||
auto hosts_end_pos = std::string_view::npos;
|
||||
auto hosts_or_user_info_end_pos = connection_string.find_first_of("?/@", offset);
|
||||
|
||||
auto has_user_info = hosts_or_user_info_end_pos != std::string_view::npos && connection_string[hosts_or_user_info_end_pos] == '@';
|
||||
if (has_user_info)
|
||||
{
|
||||
// Move offset right after user info
|
||||
offset = hosts_or_user_info_end_pos + 1;
|
||||
hosts_end_pos = connection_string.find_first_of("?/@", offset);
|
||||
// Several '@' symbols in connection string is prohibited.
|
||||
// If user name contains '@' then it should be percent-encoded.
|
||||
// several users: 'usr1@host1,@usr2@host2' is invalid.
|
||||
if (hosts_end_pos != std::string_view::npos && connection_string[hosts_end_pos] == '@')
|
||||
{
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS,
|
||||
"Symbols '@' in URI in password or user name should be percent-encoded. Individual user names for different hosts also prohibited. {}",
|
||||
connection_string);
|
||||
}
|
||||
}
|
||||
else
|
||||
hosts_end_pos = hosts_or_user_info_end_pos;
|
||||
|
||||
const auto * hosts_end = hosts_end_pos != std::string_view::npos ? connection_string.begin() + hosts_end_pos
|
||||
: connection_string.end();
|
||||
|
||||
try
|
||||
{
|
||||
/** Poco::URI doesn't support several hosts in URI.
|
||||
* Split string clickhouse:[user[:password]@]host1:port1, ... , hostN:portN[database]?[query_parameters]
|
||||
* into multiple string for each host:
|
||||
* clickhouse:[user[:password]@]host1:port1[database]?[query_parameters]
|
||||
* ...
|
||||
* clickhouse:[user[:password]@]hostN:portN[database]?[query_parameters]
|
||||
*/
|
||||
Poco::URI uri;
|
||||
const auto * last_host_begin = connection_string.begin() + offset;
|
||||
for (const auto * it = last_host_begin; it != hosts_end; ++it)
|
||||
{
|
||||
if (*it == ',')
|
||||
{
|
||||
buildConnectionString({last_host_begin, it}, {hosts_end, connection_string.end()}, uri, hosts_and_ports_arguments);
|
||||
last_host_begin = it + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (uri.empty())
|
||||
{
|
||||
// URI has no host specified
|
||||
uri = std::string(connection_string);
|
||||
getHostAndPort(uri, hosts_and_ports_arguments);
|
||||
}
|
||||
else
|
||||
buildConnectionString({last_host_begin, hosts_end}, {hosts_end, connection_string.end()}, uri, hosts_and_ports_arguments);
|
||||
|
||||
Poco::URI::QueryParameters params = uri.getQueryParameters();
|
||||
for (const auto & param : params)
|
||||
{
|
||||
if (param.first == "secure" || param.first == "s")
|
||||
{
|
||||
if (!param.second.empty())
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS, "secure URI query parameter does not allow value");
|
||||
|
||||
common_arguments.push_back(makeArgument(param.first));
|
||||
}
|
||||
else
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS, "URI query parameter {} is not supported", param.first);
|
||||
}
|
||||
|
||||
auto user_info = uri.getUserInfo();
|
||||
if (!user_info.empty())
|
||||
{
|
||||
// Poco::URI doesn't decode user name/password by default.
|
||||
// But ClickHouse allows to have users with email user name like: 'john@some_mail.com'
|
||||
// john@some_mail.com should be percent-encoded: 'john%40some_mail.com'
|
||||
size_t pos = user_info.find(':');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
common_arguments.push_back("--user");
|
||||
common_arguments.push_back(uriDecode(user_info.substr(0, pos), true));
|
||||
|
||||
++pos; // Skip ':'
|
||||
common_arguments.push_back("--password");
|
||||
if (user_info.size() > pos + 1)
|
||||
common_arguments.push_back(uriDecode(user_info.substr(pos), true));
|
||||
else
|
||||
{
|
||||
// in case of user_info == 'user:', ':' is specified, but password is empty
|
||||
// then add password argument "\n" which means: Ask user for a password.
|
||||
common_arguments.push_back("\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
common_arguments.push_back("--user");
|
||||
common_arguments.push_back(uriDecode(user_info, true));
|
||||
}
|
||||
}
|
||||
|
||||
const auto & database_name = uri.getPath();
|
||||
size_t start_symbol = !database_name.empty() && database_name[0] == '/' ? 1u : 0u;
|
||||
if (database_name.size() > start_symbol)
|
||||
{
|
||||
common_arguments.push_back("--database");
|
||||
common_arguments.push_back(start_symbol == 0u ? database_name : database_name.substr(start_symbol));
|
||||
}
|
||||
}
|
||||
catch (const Poco::URISyntaxException & invalid_uri_exception)
|
||||
{
|
||||
throw DB::Exception(DB::ErrorCodes::BAD_ARGUMENTS,
|
||||
"Invalid connection string '{}': {}", connection_string, invalid_uri_exception.what());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void checkIfCmdLineOptionCanBeUsedWithConnectionString(std::string_view command_line_option)
|
||||
{
|
||||
if (PROHIBITED_CLIENT_OPTIONS.contains(command_line_option))
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS,
|
||||
"Mixing a connection string and {} option is prohibited", PROHIBITED_CLIENT_OPTIONS.at(command_line_option));
|
||||
}
|
||||
|
||||
}
|
27
src/Client/ConnectionString.h
Normal file
27
src/Client/ConnectionString.h
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace DB
|
||||
{
|
||||
/** Tries to parse ClickHouse connection string.
|
||||
* if @connection_string starts with 'clickhouse:' then connection string will be parsed
|
||||
* and converted into a set of arguments for the client.
|
||||
* Connection string format is similar to URI "clickhouse:[//[user[:password]@][hosts_and_ports]][/dbname][?query_parameters]"
|
||||
* with the difference that hosts_and_ports can contain multiple hosts separated by ','.
|
||||
* example: clickhouse://user@host1:port1,host2:port2
|
||||
* @return Returns false if no connection string was specified. If a connection string was specified, returns true if it is valid, and throws an exception if it is invalid.
|
||||
* @exception Throws DB::Exception if URI has valid scheme (clickhouse:), but invalid internals.
|
||||
*/
|
||||
bool tryParseConnectionString(
|
||||
std::string_view connection_string,
|
||||
std::vector<std::string> & common_arguments,
|
||||
std::vector<std::vector<std::string>> & hosts_and_ports_arguments);
|
||||
|
||||
// Throws DB::Exception with BAD_ARGUMENTS if the given command line argument
|
||||
// is not allowed to be used with a connection string.
|
||||
void checkIfCmdLineOptionCanBeUsedWithConnectionString(std::string_view command_line_option);
|
||||
|
||||
}
|
126
tests/queries/0_stateless/02784_connection_string.reference
Normal file
126
tests/queries/0_stateless/02784_connection_string.reference
Normal file
@ -0,0 +1,126 @@
|
||||
0
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
21
|
||||
22
|
||||
23
|
||||
24
|
||||
25
|
||||
26
|
||||
27
|
||||
28
|
||||
29
|
||||
30
|
||||
31
|
||||
32
|
||||
33
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
40
|
||||
41
|
||||
42
|
||||
43
|
||||
44
|
||||
45
|
||||
46
|
||||
47
|
||||
48
|
||||
49
|
||||
50
|
||||
51
|
||||
52
|
||||
53
|
||||
54
|
||||
55
|
||||
56
|
||||
57
|
||||
58
|
||||
59
|
||||
60
|
||||
61
|
||||
62
|
||||
63
|
||||
64
|
||||
65
|
||||
66
|
||||
67
|
||||
68
|
||||
500
|
||||
501
|
||||
502
|
||||
1000
|
||||
1001
|
||||
1002
|
||||
1003
|
||||
Bad arguments
|
||||
Bad arguments
|
||||
Bad arguments
|
||||
Bad arguments
|
||||
Bad arguments
|
||||
Bad arguments
|
||||
Bad arguments
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
BAD_ARGUMENTS
|
||||
Authentication failed
|
||||
Authentication failed
|
159
tests/queries/0_stateless/02784_connection_string.sh
Executable file
159
tests/queries/0_stateless/02784_connection_string.sh
Executable file
@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
# shellcheck source=../shell_config.sh
|
||||
. "$CUR_DIR"/../shell_config.sh
|
||||
|
||||
USER_INFOS=('default' '')
|
||||
HOSTS_PORTS=("$CLICKHOUSE_HOST:$CLICKHOUSE_PORT_TCP" "$CLICKHOUSE_HOST" "$CLICKHOUSE_HOST:" ":$CLICKHOUSE_PORT_TCP" "127.0.0.1" "127.0.0.1:$CLICKHOUSE_PORT_TCP" "$CLICKHOUSE_HOST:$CLICKHOUSE_PORT_TCP,invalid_host:9000" "[0000:0000:0000:0000:0000:0000:0000:0001]" "[::1]" "[::1]:$CLICKHOUSE_PORT_TCP" "" )
|
||||
DATABASES=("$CLICKHOUSE_DATABASE" "")
|
||||
|
||||
TEST_INDEX=0
|
||||
|
||||
function runClient()
|
||||
{
|
||||
$CLICKHOUSE_CLIENT_BINARY "$@" -q "SELECT $TEST_INDEX" --log_comment 02766_connection_string.sh --send_logs_level=warning
|
||||
((++TEST_INDEX))
|
||||
}
|
||||
|
||||
function testConnectionString()
|
||||
{
|
||||
if [ "$database" == "" ]; then
|
||||
runClient "clickhouse:$1"
|
||||
runClient "clickhouse:$1/"
|
||||
else
|
||||
runClient "clickhouse:$1/$database"
|
||||
fi
|
||||
}
|
||||
|
||||
function testConnectionWithUserName()
|
||||
{
|
||||
if [ "$user_info" == "" ] && [ "$host_port" == "" ]; then
|
||||
testConnectionString "//"
|
||||
testConnectionString ""
|
||||
else
|
||||
testConnectionString "//$user_info@$host_port"
|
||||
fi
|
||||
}
|
||||
|
||||
for user_info in "${USER_INFOS[@]}"
|
||||
do
|
||||
for host_port in "${HOSTS_PORTS[@]}"
|
||||
do
|
||||
for database in "${DATABASES[@]}"
|
||||
do
|
||||
testConnectionWithUserName
|
||||
done
|
||||
done
|
||||
done
|
||||
|
||||
# Specific user and password
|
||||
TEST_INDEX=500
|
||||
TEST_USER_NAME="test_user_02771_$$"
|
||||
TEST_USER_EMAIL_NAME="test_user_02771_$$@some_mail.com"
|
||||
TEST_USER_EMAIL_NAME_ENCODED="test_user_02771_$$%40some_mail.com"
|
||||
|
||||
TEST_USER_PASSWORD="zyx%$&abc"
|
||||
# %, $, & percent encoded
|
||||
TEST_USER_PASSWORD_ENCODED="zyx%25%24%26abc"
|
||||
|
||||
$CLICKHOUSE_CLIENT -q "CREATE USER '$TEST_USER_NAME'"
|
||||
$CLICKHOUSE_CLIENT -q "CREATE USER '$TEST_USER_EMAIL_NAME' IDENTIFIED WITH plaintext_password BY '$TEST_USER_PASSWORD'"
|
||||
|
||||
runClient "clickhouse://$TEST_USER_NAME@$CLICKHOUSE_HOST/$CLICKHOUSE_DATABASE"
|
||||
runClient "clickhouse://$TEST_USER_EMAIL_NAME_ENCODED:$TEST_USER_PASSWORD_ENCODED@$CLICKHOUSE_HOST/$CLICKHOUSE_DATABASE"
|
||||
|
||||
$CLICKHOUSE_CLIENT -q "DROP USER '$TEST_USER_NAME'"
|
||||
$CLICKHOUSE_CLIENT -q "DROP USER '$TEST_USER_EMAIL_NAME'"
|
||||
|
||||
# Percent-encoded database in non-ascii symbols
|
||||
UTF8_DATABASE="БазаДанных_$$"
|
||||
UTF8_DATABASE_PERCENT_ENCODED="%D0%91%D0%B0%D0%B7%D0%B0%D0%94%D0%B0%D0%BD%D0%BD%D1%8B%D1%85_$$"
|
||||
$CLICKHOUSE_CLIENT -q "CREATE DATABASE IF NOT EXISTS \`$UTF8_DATABASE\`"
|
||||
runClient "clickhouse://default@$CLICKHOUSE_HOST/$UTF8_DATABASE_PERCENT_ENCODED"
|
||||
$CLICKHOUSE_CLIENT -q "DROP DATABASE IF EXISTS \`$UTF8_DATABASE\`"
|
||||
|
||||
# clickhouse-client extra options cases
|
||||
TEST_INDEX=1000
|
||||
|
||||
runClient "clickhouse://$CLICKHOUSE_HOST/" --user 'default'
|
||||
runClient "clickhouse://$CLICKHOUSE_HOST/default" --user 'default'
|
||||
runClient "clickhouse:" --database "$CLICKHOUSE_DATABASE"
|
||||
|
||||
# User 'default' and default host
|
||||
runClient "clickhouse://default@"
|
||||
|
||||
# Invalid URI cases
|
||||
TEST_INDEX=10000
|
||||
runClient "clickhouse://default:@$CLICKHOUSE_HOST/" --user 'default' 2>&1 | grep -o 'Bad arguments'
|
||||
runClient "clickhouse://default:pswrd@$CLICKHOUSE_HOST/" --user 'default' 2>&1 | grep -o 'Bad arguments'
|
||||
runClient "clickhouse://default:pswrd@$CLICKHOUSE_HOST/" --password 'pswrd' 2>&1 | grep -o 'Bad arguments'
|
||||
runClient "clickhouse:///$CLICKHOUSE_DATABASE" --database "$CLICKHOUSE_DATABASE" 2>&1 | grep -o 'Bad arguments'
|
||||
runClient "clickhouse://$CLICKHOUSE_HOST/$CLICKHOUSE_DATABASE" --database "$CLICKHOUSE_DATABASE" 2>&1 | grep -o 'Bad arguments'
|
||||
runClient "clickhouse://$CLICKHOUSE_HOST/$CLICKHOUSE_DATABASE?s" --database "$CLICKHOUSE_DATABASE" 2>&1 | grep -o 'Bad arguments'
|
||||
runClient "clickhouse:/$CLICKHOUSE_DATABASE?s" --database "$CLICKHOUSE_DATABASE" 2>&1 | grep -o 'Bad arguments'
|
||||
|
||||
runClient "http://" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "click_house:" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
|
||||
TEST_INDEX=1000087
|
||||
# Using connection string prohibits to use --host and --port options
|
||||
runClient "clickhouse://default:@$CLICKHOUSE_HOST/" --host "$CLICKHOUSE_HOST" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://default:@$CLICKHOUSE_HOST:$CLICKHOUSE_PORT_TCP/" --host "$CLICKHOUSE_HOST" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://default:@$CLICKHOUSE_HOST:$CLICKHOUSE_PORT_TCP/" --port "$CLICKHOUSE_PORT_TCP" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://default:@$CLICKHOUSE_HOST:$CLICKHOUSE_PORT_TCP/" --host "$CLICKHOUSE_HOST" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://default:@$CLICKHOUSE_HOST:$CLICKHOUSE_PORT_TCP/" --host "$CLICKHOUSE_HOST" --host "$CLICKHOUSE_HOST" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://default:@$CLICKHOUSE_HOST:$CLICKHOUSE_PORT_TCP/" --port "$CLICKHOUSE_PORT_TCP" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://default:@$CLICKHOUSE_HOST/" --port "$CLICKHOUSE_PORT_TCP" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://$CLICKHOUSE_HOST/" --port "$CLICKHOUSE_PORT_TCP" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://:@$CLICKHOUSE_HOST/" --port "$CLICKHOUSE_PORT_TCP" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://$CLICKHOUSE_HOST/" --port "$CLICKHOUSE_PORT_TCP" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://" --host "$CLICKHOUSE_HOST" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse:" --port "$CLICKHOUSE_PORT_TCP" --host "$CLICKHOUSE_HOST" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://" --port "$CLICKHOUSE_PORT_TCP" --host "$CLICKHOUSE_HOST" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse:///" --port "$CLICKHOUSE_PORT_TCP" --host "$CLICKHOUSE_HOST" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse:///?" --port "$CLICKHOUSE_PORT_TCP" --host "$CLICKHOUSE_HOST" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://:/?" --port "$CLICKHOUSE_PORT_TCP" --host "$CLICKHOUSE_HOST" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse:" --database "$CLICKHOUSE_DATABASE" --port "$CLICKHOUSE_PORT_TCP" --host "$CLICKHOUSE_HOST" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
|
||||
# Using clickhouse-client and connection is prohibited
|
||||
runClient "clickhouse:" --connection "connection" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
|
||||
# Space is used in connection string (This is prohibited).
|
||||
runClient " clickhouse:" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse: " 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://host1 /" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://host1, host2/" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://host1 ,host2/" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://host1 host2/" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://host1/ database:" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://user :password@host1" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://user: password@host1" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
|
||||
# Connection string is not first argument
|
||||
runClient --multiline "clickhouse://default:@$CLICKHOUSE_HOST/" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
# Connection string used as the first and the second argument of client
|
||||
runClient "clickhouse://default:@$CLICKHOUSE_HOST/" "clickhouse://default:@$CLICKHOUSE_HOST/" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
|
||||
# Invalid hosts
|
||||
runClient "clickhouse://host1,,," 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse://," 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
|
||||
# Invalid parameters
|
||||
runClient "clickhouse:?invalid_parameter" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse:?invalid_parameter&secure" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse:?s&invalid_parameter" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse:?s&invalid_parameter=val" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse:?invalid_parameter=arg" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
runClient "clickhouse:?invalid_parameter=arg&s" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
# Several users prohibited
|
||||
runClient "clickhouse://user1@localhost,default@localhost/" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
# Using '@' in user name is prohibited. User name should be percent-encoded.
|
||||
runClient "clickhouse://my_mail@email.com@host/" 2>&1 | grep -o 'BAD_ARGUMENTS'
|
||||
|
||||
# Wrong input cases
|
||||
TEST_INDEX=100000
|
||||
# Invalid user name
|
||||
runClient "clickhouse://non_exist_user@$CLICKHOUSE_HOST:$CLICKHOUSE_PORT_TCP/" 2>&1 | grep -o 'Authentication failed'
|
||||
# Invalid password
|
||||
runClient "clickhouse://default:invalid_password@$CLICKHOUSE_HOST:$CLICKHOUSE_PORT_TCP/" 2>&1 | grep -o 'Authentication failed'
|
Loading…
Reference in New Issue
Block a user