Merge pull request #50689 from arenadata/ADQM-871

Added connection string to clickhouse-client
This commit is contained in:
Robert Schulze 2023-06-14 10:39:32 +02:00 committed by GitHub
commit 2643fd2c25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 805 additions and 4 deletions

View File

@ -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[,&param2=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}

View File

@ -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[,&param2=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}

View File

@ -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;

View 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));
}
}

View 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);
}

View 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

View 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'