Merge branch 'master' into run_func_tests_in_parallel

This commit is contained in:
alesapin 2020-10-20 17:15:56 +03:00
commit df4620b7bd
226 changed files with 11132 additions and 365 deletions

View File

@ -15,6 +15,10 @@ if (COMPILER_GCC)
elseif (COMPILER_CLANG)
# Require minimum version of clang/apple-clang
if (CMAKE_CXX_COMPILER_ID MATCHES "AppleClang")
# If you are developer you can figure out what exact versions of AppleClang are Ok,
# remove the following line and commit changes below.
message (FATAL_ERROR "AppleClang is not supported, you should install clang from brew.")
# AppleClang 10.0.1 (Xcode 10.2) corresponds to LLVM/Clang upstream version 7.0.0
# AppleClang 11.0.0 (Xcode 11.0) corresponds to LLVM/Clang upstream version 8.0.0
set (XCODE_MINIMUM_VERSION 10.2)

View File

@ -56,7 +56,6 @@ RUN apt-get update \
python3-lxml \
python3-requests \
python3-termcolor \
qemu-user-static \
rename \
software-properties-common \
tzdata \

View File

@ -191,63 +191,65 @@ stop_server ||:
start_server
TESTS_TO_SKIP=(
parquet
avro
h3
odbc
mysql
sha256
_orc_
arrow
01098_temporary_and_external_tables
01083_expressions_in_engine_arguments
hdfs
00911_tautological_compare
protobuf
capnproto
java_hash
hashing
secure
00490_special_line_separators_and_characters_outside_of_bmp
00436_convert_charset
00105_shard_collations
01354_order_by_tuple_collate_const
01292_create_user
01098_msgpack_format
00929_multi_match_edit_distance
00926_multimatch
00834_cancel_http_readonly_queries_on_client_close
brotli
parallel_alter
00109_shard_totals_after_having
00110_external_sort
00302_http_compression
00417_kill_query
01294_lazy_database_concurrent
01193_metadata_loading
base64
01031_mutations_interpreter_and_context
json
client
01305_replica_create_drop_zookeeper
01092_memory_profiler
01355_ilike
01281_unsucceeded_insert_select_queries_counter
live_view
limit_memory
memory_limit
memory_leak
00110_external_sort
00436_convert_charset
00490_special_line_separators_and_characters_outside_of_bmp
00652_replicated_mutations_zookeeper
00682_empty_parts_merge
00701_rollup
00109_shard_totals_after_having
ddl_dictionaries
00834_cancel_http_readonly_queries_on_client_close
00911_tautological_compare
00926_multimatch
00929_multi_match_edit_distance
01031_mutations_interpreter_and_context
01053_ssd_dictionary # this test mistakenly requires acces to /var/lib/clickhouse -- can't run this locally, disabled
01083_expressions_in_engine_arguments
01092_memory_profiler
01098_msgpack_format
01098_temporary_and_external_tables
01103_check_cpu_instructions_at_startup # avoid dependency on qemu -- invonvenient when running locally
01193_metadata_loading
01238_http_memory_tracking # max_memory_usage_for_user can interfere another queries running concurrently
01251_dict_is_in_infinite_loop
01259_dictionary_custom_settings_ddl
01268_dictionary_direct_layout
01280_ssd_complex_key_dictionary
00652_replicated_mutations_zookeeper
01411_bayesian_ab_testing
01238_http_memory_tracking # max_memory_usage_for_user can interfere another queries running concurrently
01281_group_by_limit_memory_tracking # max_memory_usage_for_user can interfere another queries running concurrently
01281_unsucceeded_insert_select_queries_counter
01292_create_user
01294_lazy_database_concurrent
01305_replica_create_drop_zookeeper
01354_order_by_tuple_collate_const
01355_ilike
01411_bayesian_ab_testing
_orc_
arrow
avro
base64
brotli
capnproto
client
ddl_dictionaries
h3
hashing
hdfs
java_hash
json
limit_memory
live_view
memory_leak
memory_limit
mysql
odbc
parallel_alter
parquet
protobuf
secure
sha256
# Not sure why these two fail even in sequential mode. Disabled for now
# to make some progress.
@ -258,7 +260,7 @@ TESTS_TO_SKIP=(
01460_DistributedFilesToInsert
)
time clickhouse-test -j 8 --no-long --testname --shard --zookeeper --skip "${TESTS_TO_SKIP[@]}" 2>&1 | ts '%Y-%m-%d %H:%M:%S' | tee "$FASTTEST_OUTPUT/test_log.txt"
time clickhouse-test -j 8 --order=random --no-long --testname --shard --zookeeper --skip "${TESTS_TO_SKIP[@]}" 2>&1 | ts '%Y-%m-%d %H:%M:%S' | tee "$FASTTEST_OUTPUT/test_log.txt"
# substr is to remove semicolon after test name
readarray -t FAILED_TESTS < <(awk '/FAIL|TIMEOUT|ERROR/ { print substr($3, 1, length($3)-1) }' "$FASTTEST_OUTPUT/test_log.txt" | tee "$FASTTEST_OUTPUT/failed-parallel-tests.txt")
@ -281,7 +283,7 @@ then
echo "Going to run again: ${FAILED_TESTS[*]}"
clickhouse-test --no-long --testname --shard --zookeeper "${FAILED_TESTS[@]}" 2>&1 | ts '%Y-%m-%d %H:%M:%S' | tee -a "$FASTTEST_OUTPUT/test_log.txt"
clickhouse-test --order=random --no-long --testname --shard --zookeeper "${FAILED_TESTS[@]}" 2>&1 | ts '%Y-%m-%d %H:%M:%S' | tee -a "$FASTTEST_OUTPUT/test_log.txt"
else
echo "No failed tests"
fi

View File

@ -37,7 +37,28 @@ RUN apt-get update \
ENV TZ=Europe/Moscow
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN python3 -m pip install urllib3==1.23 pytest docker-compose==1.22.0 docker dicttoxml kazoo PyMySQL psycopg2==2.7.5 pymongo tzlocal kafka-python protobuf redis aerospike pytest-timeout minio grpcio grpcio-tools cassandra-driver confluent-kafka avro
RUN python3 -m pip install \
PyMySQL \
aerospike \
avro \
cassandra-driver \
confluent-kafka \
dicttoxml \
docker \
docker-compose==1.22.0 \
grpcio \
grpcio-tools \
kafka-python \
kazoo \
minio \
protobuf \
psycopg2-binary==2.7.5 \
pymongo \
pytest \
pytest-timeout \
redis \
tzlocal \
urllib3
ENV DOCKER_CHANNEL stable
ENV DOCKER_VERSION 17.09.1-ce

View File

@ -45,7 +45,7 @@ function start()
# for clickhouse-server (via service)
echo "ASAN_OPTIONS='malloc_context_size=10 verbosity=1 allocator_release_to_os_interval_ms=10000'" >> /etc/environment
# for clickhouse-client
export ASAN_OPTIONS='malloc_context_size=10 verbosity=1 allocator_release_to_os_interval_ms=10000'
export ASAN_OPTIONS='malloc_context_size=10 allocator_release_to_os_interval_ms=10000'
start

View File

@ -28,8 +28,18 @@ def get_options(i):
options = ""
if 0 < i:
options += " --order=random"
if i % 2 == 1:
options += " --db-engine=Ordinary"
# If database name is not specified, new database is created for each functional test.
# Run some threads with one database for all tests.
if i % 3 == 1:
options += " --database=test_{}".format(i)
if i == 13:
options += " --client-option='memory_tracker_fault_probability=0.00001'"
return options

View File

@ -117,7 +117,9 @@ CREATE TABLE table_name
</details>
As the example shows, these parameters can contain substitutions in curly brackets. The substituted values are taken from the macros section of the configuration file. Example:
As the example shows, these parameters can contain substitutions in curly brackets. The substituted values are taken from the «[macros](../../../operations/server-configuration-parameters/settings/#macros) section of the configuration file.
Example:
``` xml
<macros>
@ -137,6 +139,9 @@ In this case, the path consists of the following parts:
`table_name` is the name of the node for the table in ZooKeeper. It is a good idea to make it the same as the table name. It is defined explicitly, because in contrast to the table name, it doesnt change after a RENAME query.
*HINT*: you could add a database name in front of `table_name` as well. E.g. `db_name.table_name`
The two built-in substitutions `{database}` and `{table}` can be used, they expand into the table name and the database name respectively (unless these macros are defined in the `macros` section). So the zookeeper path can be specified as `'/clickhouse/tables/{layer}-{shard}/{database}/{table}'`.
Be careful with table renames when using these built-in substitutions. The path in Zookeeper cannot be changed, and when the table is renamed, the macros will expand into a different path, the table will refer to a path that does not exist in Zookeeper, and will go into read-only mode.
The replica name identifies different replicas of the same table. You can use the server name for this, as in the example. The name only needs to be unique within each shard.
You can define the parameters explicitly instead of using substitutions. This might be convenient for testing and for configuring small clusters. However, you cant use distributed DDL queries (`ON CLUSTER`) in this case.

View File

@ -88,6 +88,7 @@ toc_title: Adopters
| <a href="https://smi2.ru/" class="favicon">SMI2</a> | News | Analytics | — | — | [Blog Post in Russian, November 2017](https://habr.com/ru/company/smi2/blog/314558/) |
| <a href="https://www.splunk.com/" class="favicon">Splunk</a> | Business Analytics | Main product | — | — | [Slides in English, January 2018](https://github.com/ClickHouse/clickhouse-presentations/blob/master/meetup12/splunk.pdf) |
| <a href="https://www.spotify.com" class="favicon">Spotify</a> | Music | Experimentation | — | — | [Slides, July 2018](https://www.slideshare.net/glebus/using-clickhouse-for-experimentation-104247173) |
| <a href="https://www.staffcop.ru/" class="favicon">Staffcop</a> | Information Security | Main Product | — | — | [Official website, Documentation](https://www.staffcop.ru/sce43) |
| <a href="https://www.tencent.com" class="favicon">Tencent</a> | Big Data | Data processing | — | — | [Slides in Chinese, October 2018](https://github.com/ClickHouse/clickhouse-presentations/blob/master/meetup19/5.%20ClickHouse大数据集群应用_李俊飞腾讯网媒事业部.pdf) |
| <a href="https://www.tencent.com" class="favicon">Tencent</a> | Messaging | Logging | — | — | [Talk in Chinese, November 2019](https://youtu.be/T-iVQRuw-QY?t=5050) |
| <a href="https://trafficstars.com/" class="favicon">Traffic Stars</a> | AD network | — | — | — | [Slides in Russian, May 2018](https://github.com/ClickHouse/clickhouse-presentations/blob/master/meetup15/lightning/ninja.pdf) |

View File

@ -461,6 +461,66 @@ For other regular expressions, the code is the same as for the match funct
The same thing as like, but negative.
## ilike {#ilike}
Case insensitive variant of [like](https://clickhouse.tech/docs/en/sql-reference/functions/string-search-functions/#function-like) function. You can use `ILIKE` operator instead of the `ilike` function.
**Syntax**
``` sql
ilike(haystack, pattern)
```
**Parameters**
- `haystack` — Input string. [String](../../sql-reference/syntax.md#syntax-string-literal).
- `pattern` — If `pattern` doesn't contain percent signs or underscores, then the `pattern` only represents the string itself. An underscore (`_`) in `pattern` stands for (matches) any single character. A percent sign (`%`) matches any sequence of zero or more characters.
Some `pattern` examples:
``` text
'abc' ILIKE 'abc' true
'abc' ILIKE 'a%' true
'abc' ILIKE '_b_' true
'abc' ILIKE 'c' false
```
**Returned values**
- True, if the string matches `pattern`.
- False, if the string doesn't match `pattern`.
**Example**
Input table:
``` text
┌─id─┬─name─────┬─days─┐
│ 1 │ January │ 31 │
│ 2 │ February │ 29 │
│ 3 │ March │ 31 │
│ 4 │ April │ 30 │
└────┴──────────┴──────┘
```
Query:
``` sql
SELECT * FROM Months WHERE ilike(name, '%j%')
```
Result:
``` text
┌─id─┬─name────┬─days─┐
│ 1 │ January │ 31 │
└────┴─────────┴──────┘
```
**See Also**
- [like](https://clickhouse.tech/docs/en/sql-reference/functions/string-search-functions/#function-like) <!--hide-->
## ngramDistance(haystack, needle) {#ngramdistancehaystack-needle}
Calculates the 4-gram distance between `haystack` and `needle`: counts the symmetric difference between two multisets of 4-grams and normalizes it by the sum of their cardinalities. Returns float number from 0 to 1 the closer to zero, the more strings are similar to each other. If the constant `needle` or `haystack` is more than 32Kb, throws an exception. If some of the non-constant `haystack` or `needle` strings are more than 32Kb, the distance is always one.

View File

@ -53,6 +53,8 @@ ClickHouse transforms operators to their corresponding functions at the query pa
`a NOT LIKE s` The `notLike(a, b)` function.
`a ILIKE s` The `ilike(a, b)` function.
`a BETWEEN b AND c` The same as `a >= b AND a <= c`.
`a NOT BETWEEN b AND c` The same as `a < b OR a > c`.

View File

@ -139,7 +139,7 @@ ENGINE = <Engine>
```
The `Default` codec can be specified to reference default compression which may dependend on different settings (and properties of data) in runtime.
Example: `value UInt64 CODEC(Default)` - the same as lack of codec specification.
Example: `value UInt64 CODEC(Default)` the same as lack of codec specification.
Also you can remove current CODEC from the column and use default compression from config.xml:

View File

@ -0,0 +1,67 @@
---
toc_priority: 51
toc_title: view
---
## view {#view}
Turns a subquery into a table. The function implements views (see [CREATE VIEW](https://clickhouse.tech/docs/en/sql-reference/statements/create/view/#create-view)). The resulting table doesn't store data, but only stores the specified `SELECT` query. When reading from the table, ClickHouse executes the query and deletes all unnecessary columns from the result.
**Syntax**
``` sql
view(subquery)
```
**Parameters**
- `subquery``SELECT` query.
**Returned value**
- A table.
**Example**
Input table:
``` text
┌─id─┬─name─────┬─days─┐
│ 1 │ January │ 31 │
│ 2 │ February │ 29 │
│ 3 │ March │ 31 │
│ 4 │ April │ 30 │
└────┴──────────┴──────┘
```
Query:
``` sql
SELECT * FROM view(SELECT name FROM months)
```
Result:
``` text
┌─name─────┐
│ January │
│ February │
│ March │
│ April │
└──────────┘
```
You can use the `view` function as a parameter of the [remote](https://clickhouse.tech/docs/en/sql-reference/table-functions/remote/#remote-remotesecure) and [cluster](https://clickhouse.tech/docs/en/sql-reference/table-functions/cluster/#cluster-clusterallreplicas) table functions:
``` sql
SELECT * FROM remote(`127.0.0.1`, view(SELECT a, b, c FROM table_name))
```
``` sql
SELECT * FROM cluster(`cluster_name`, view(SELECT a, b, c FROM table_name))
```
**See Also**
- [View Table Engine](https://clickhouse.tech/docs/en/engines/table-engines/special/view/)
[Original article](https://clickhouse.tech/docs/en/query_language/table_functions/view/) <!--hide-->

View File

@ -113,7 +113,9 @@ CREATE TABLE table_name
</details>
Как видно в примере, эти параметры могут содержать подстановки в фигурных скобках. Подставляемые значения достаются из конфигурационного файла, из секции `macros`. Пример:
Как видно в примере, эти параметры могут содержать подстановки в фигурных скобках. Подставляемые значения достаются из конфигурационного файла, из секции «[macros](../../../operations/server-configuration-parameters/settings/#macros)».
Пример:
``` xml
<macros>
@ -133,6 +135,9 @@ CREATE TABLE table_name
`table_name` - имя узла для таблицы в ZooKeeper. Разумно делать его таким же, как имя таблицы. Оно указывается явно, так как, в отличие от имени таблицы, оно не меняется после запроса RENAME.
*Подсказка*: можно также указать имя базы данных перед `table_name`, например `db_name.table_name`
Можно использовать две встроенных подстановки `{database}` и `{table}`, они раскрываются в имя таблицы и в имя базы данных соответственно (если эти подстановки не переопределены в секции `macros`). Т.о. Zookeeper путь можно задать как `'/clickhouse/tables/{layer}-{shard}/{database}/{table}'`.
Будьте осторожны с переименованиями таблицы при использовании этих автоматических подстановок. Путь в Zookeeper-е нельзя изменить, а подстановка при переименовании таблицы раскроется в другой путь, таблица будет обращаться к несуществующему в Zookeeper-е пути и перейдет в режим только для чтения.
Имя реплики — то, что идентифицирует разные реплики одной и той же таблицы. Можно использовать для него имя сервера, как показано в примере. Впрочем, достаточно, чтобы имя было уникально лишь в пределах каждого шарда.
Можно не использовать подстановки, а указать соответствующие параметры явно. Это может быть удобным для тестирования и при настройке маленьких кластеров. Однако в этом случае нельзя пользоваться распределенными DDL-запросами (`ON CLUSTER`).

View File

@ -387,7 +387,7 @@ ClickHouse проверяет условия для `min_part_size` и `min_part
Можно не указывать, если реплицируемых таблицы не используются.
Подробнее смотрите в разделе «[Создание реплицируемых таблиц](../../operations/server-configuration-parameters/settings.md)».
Подробнее смотрите в разделе «[Создание реплицируемых таблиц](../../engines/table-engines/mergetree-family/replication.md)».
**Пример**

View File

@ -1164,9 +1164,9 @@ ClickHouse генерирует исключение
## insert_quorum_timeout {#settings-insert_quorum_timeout}
Время ожидания кворумной записи в секундах. Если время прошло, а запись так не состоялась, то ClickHouse сгенерирует исключение и клиент должен повторить запрос на запись того же блока на эту же или любую другую реплику.
Время ожидания кворумной записи в миллисекундах. Если время прошло, а запись так не состоялась, то ClickHouse сгенерирует исключение и клиент должен повторить запрос на запись того же блока на эту же или любую другую реплику.
Значение по умолчанию: 60 секунд.
Значение по умолчанию: 600000 миллисекунд (10 минут).
См. также:

View File

@ -442,6 +442,66 @@ SELECT extractAllGroupsVertical('abc=111, def=222, ghi=333', '("[^"]+"|\\w+)=("[
То же, что like, но с отрицанием.
## ilike {#ilike}
Нечувствительный к регистру вариант функции [like](https://clickhouse.tech/docs/ru/sql-reference/functions/string-search-functions/#function-like). Вы можете использовать оператор `ILIKE` вместо функции `ilike`.
**Синтаксис**
``` sql
ilike(haystack, pattern)
```
**Параметры**
- `haystack` — Входная строка. [String](../../sql-reference/syntax.md#syntax-string-literal).
- `pattern` — Если `pattern` не содержит процента или нижнего подчеркивания, тогда `pattern` представляет саму строку. Нижнее подчеркивание (`_`) в `pattern` обозначает любой отдельный символ. Знак процента (`%`) соответствует последовательности из любого количества символов: от нуля и более.
Некоторые примеры `pattern`:
``` text
'abc' ILIKE 'abc' true
'abc' ILIKE 'a%' true
'abc' ILIKE '_b_' true
'abc' ILIKE 'c' false
```
**Возвращаемые значения**
- Правда, если строка соответствует `pattern`.
- Ложь, если строка не соответствует `pattern`.
**Пример**
Входная таблица:
``` text
┌─id─┬─name─────┬─days─┐
│ 1 │ January │ 31 │
│ 2 │ February │ 29 │
│ 3 │ March │ 31 │
│ 4 │ April │ 30 │
└────┴──────────┴──────┘
```
Запрос:
``` sql
SELECT * FROM Months WHERE ilike(name, '%j%')
```
Результат:
``` text
┌─id─┬─name────┬─days─┐
│ 1 │ January │ 31 │
└────┴─────────┴──────┘
```
**Смотрите также**
- [like](https://clickhouse.tech/docs/ru/sql-reference/functions/string-search-functions/#function-like) <!--hide-->
## ngramDistance(haystack, needle) {#ngramdistancehaystack-needle}
Вычисление 4-граммного расстояния между `haystack` и `needle`: считается симметрическая разность между двумя мультимножествами 4-грамм и нормализуется на сумму их мощностей. Возвращает число float от 0 до 1 чем ближе к нулю, тем больше строки похожи друг на друга. Если константный `needle` или `haystack` больше чем 32КБ, кидается исключение. Если некоторые строки из неконстантного `haystack` или `needle` больше 32КБ, расстояние всегда равно единице.

View File

@ -49,6 +49,8 @@
`a NOT LIKE s` - функция `notLike(a, b)`
`a ILIKE s` функция `ilike(a, b)`
`a BETWEEN b AND c` - равнозначно `a >= b AND a <= c`
`a NOT BETWEEN b AND c` - равнозначно `a < b OR a > c`

View File

@ -119,7 +119,18 @@ ENGINE = <Engine>
...
```
Если задать кодек для столбца, то кодек по умолчанию не применяется. Кодеки можно последовательно комбинировать, например, `CODEC(Delta, ZSTD)`. Чтобы выбрать наиболее подходящую для вашего проекта комбинацию кодеков, необходимо провести сравнительные тесты, подобные тем, что описаны в статье Altinity [New Encodings to Improve ClickHouse Efficiency](https://www.altinity.com/blog/2019/7/new-encodings-to-improve-clickhouse).
Если кодек `Default` задан для столбца, используется сжатие по умолчанию, которое может зависеть от различных настроек (и свойств данных) во время выполнения.
Пример: `value UInt64 CODEC(Default)` — то же самое, что не указать кодек.
Также можно подменить кодек столбца сжатием по умолчанию, определенным в config.xml:
``` sql
ALTER TABLE codec_example MODIFY COLUMN float_value CODEC(Default);
```
Кодеки можно последовательно комбинировать, например, `CODEC(Delta, Default)`.
Чтобы выбрать наиболее подходящую для вашего проекта комбинацию кодеков, необходимо провести сравнительные тесты, подобные тем, что описаны в статье Altinity [New Encodings to Improve ClickHouse Efficiency](https://www.altinity.com/blog/2019/7/new-encodings-to-improve-clickhouse). Для столбцов типа `ALIAS` кодеки не применяются.
!!! warning "Предупреждение"
Нельзя распаковать базу данных ClickHouse с помощью сторонних утилит наподобие `lz4`. Необходимо использовать специальную утилиту [clickhouse-compressor](https://github.com/ClickHouse/ClickHouse/tree/master/programs/compressor).
@ -195,4 +206,4 @@ CREATE TEMPORARY TABLE [IF NOT EXISTS] table_name
[Оригинальная статья](https://clickhouse.tech/docs/ru/sql-reference/statements/create/table)
<!--hide-->
<!--hide-->

View File

@ -0,0 +1,62 @@
## view {#view}
Преобразовывает подзапрос в таблицу. Функция реализовывает представления (смотрите [CREATE VIEW](https://clickhouse.tech/docs/ru/sql-reference/statements/create/view/#create-view)). Результирующая таблица не хранит данные, а только сохраняет указанный запрос `SELECT`. При чтении из таблицы, ClickHouse выполняет запрос и удаляет все ненужные столбцы из результата.
**Синтаксис**
``` sql
view(subquery)
```
**Входные параметры**
- `subquery` — запрос `SELECT`.
**Возвращаемое значение**
- Таблица.
**Пример**
Входная таблица:
``` text
┌─id─┬─name─────┬─days─┐
│ 1 │ January │ 31 │
│ 2 │ February │ 29 │
│ 3 │ March │ 31 │
│ 4 │ April │ 30 │
└────┴──────────┴──────┘
```
Запрос:
``` sql
SELECT * FROM view(SELECT name FROM months)
```
Результат:
``` text
┌─name─────┐
│ January │
│ February │
│ March │
│ April │
└──────────┘
```
Вы можете использовать функцию `view` как параметр табличных функций [remote](https://clickhouse.tech/docs/ru/sql-reference/table-functions/remote/#remote-remotesecure) и [cluster](https://clickhouse.tech/docs/ru/sql-reference/table-functions/cluster/#cluster-clusterallreplicas):
``` sql
SELECT * FROM remote(`127.0.0.1`, view(SELECT a, b, c FROM table_name))
```
``` sql
SELECT * FROM cluster(`cluster_name`, view(SELECT a, b, c FROM table_name))
```
**Смотрите также**
- [view](https://clickhouse.tech/docs/ru/engines/table-engines/special/view/#table_engines-view)
[Оригинальная статья](https://clickhouse.tech/docs/ru/query_language/table_functions/view/) <!--hide-->

View File

@ -1202,8 +1202,15 @@ private:
}
catch (...)
{
// Some functions (e.g. protocol parsers) don't throw, but
// set last_exception instead, so we'll also do it here for
// uniformity.
last_exception_received_from_server = std::make_unique<Exception>(getCurrentExceptionMessage(true), getCurrentExceptionCode());
received_exception_from_server = true;
}
if (received_exception_from_server)
{
fmt::print(stderr, "Error on processing query '{}': {}\n",
ast_to_process->formatForErrorMessage(),
last_exception_received_from_server->message());
@ -1213,29 +1220,30 @@ private:
{
// Probably the server is dead because we found an assertion
// failure. Fail fast.
fmt::print(stderr, "Lost connection to the server\n");
return begin;
}
// The server is still alive so we're going to continue fuzzing.
// Determine what we're going to use as the starting AST.
if (received_exception_from_server)
{
// Query completed with error, ignore it and fuzz again.
fprintf(stderr, "Got error, will fuzz again\n");
// Query completed with error, keep the previous starting AST.
// Also discard the exception that we now know to be non-fatal,
// so that it doesn't influence the exit code.
last_exception_received_from_server.reset(nullptr);
received_exception_from_server = false;
last_exception_received_from_server.reset();
continue;
}
else if (ast_to_process->formatForErrorMessage().size() > 500)
{
// ast too long, start from original ast
fprintf(stderr, "current ast too long, won't elaborate\n");
fprintf(stderr, "Current AST is too long, discarding it and using the original AST as a start\n");
fuzz_base = orig_ast;
}
else
{
// fuzz starting from this successful query
fprintf(stderr, "using this ast as etalon\n");
fprintf(stderr, "Query succeeded, using this AST as a start\n");
fuzz_base = ast_to_process;
}
}

View File

@ -4,7 +4,7 @@ set(CLICKHOUSE_SERVER_SOURCES
)
if (OS_LINUX)
set (LINK_CONFIG_LIB INTERFACE "-Wl,${WHOLE_ARCHIVE} $<TARGET_FILE:clickhouse_server_configs> -Wl,${NO_WHOLE_ARCHIVE}")
set (LINK_RESOURCE_LIB INTERFACE "-Wl,${WHOLE_ARCHIVE} $<TARGET_FILE:clickhouse_server_configs> -Wl,${NO_WHOLE_ARCHIVE}")
endif ()
set (CLICKHOUSE_SERVER_LINK
@ -20,7 +20,7 @@ set (CLICKHOUSE_SERVER_LINK
clickhouse_table_functions
string_utils
${LINK_CONFIG_LIB}
${LINK_RESOURCE_LIB}
PUBLIC
daemon
@ -37,20 +37,20 @@ if (OS_LINUX)
# 1. Allow to run the binary without download of any other files.
# 2. Allow to implement "sudo clickhouse install" tool.
foreach(CONFIG_FILE config users embedded)
set(CONFIG_OBJ ${CONFIG_FILE}.o)
set(CONFIG_OBJS ${CONFIG_OBJS} ${CONFIG_OBJ})
foreach(RESOURCE_FILE config.xml users.xml embedded.xml play.html)
set(RESOURCE_OBJ ${RESOURCE_FILE}.o)
set(RESOURCE_OBJS ${RESOURCE_OBJS} ${RESOURCE_OBJ})
# https://stackoverflow.com/questions/14776463/compile-and-add-an-object-file-from-a-binary-with-cmake
add_custom_command(OUTPUT ${CONFIG_OBJ}
COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${OBJCOPY_PATH} -I binary ${OBJCOPY_ARCH_OPTIONS} ${CONFIG_FILE}.xml ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_OBJ}
add_custom_command(OUTPUT ${RESOURCE_OBJ}
COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${OBJCOPY_PATH} -I binary ${OBJCOPY_ARCH_OPTIONS} ${RESOURCE_FILE} ${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_OBJ}
COMMAND ${OBJCOPY_PATH} --rename-section .data=.rodata,alloc,load,readonly,data,contents
${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_OBJ} ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_OBJ})
${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_OBJ} ${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_OBJ})
set_source_files_properties(${CONFIG_OBJ} PROPERTIES EXTERNAL_OBJECT true GENERATED true)
endforeach(CONFIG_FILE)
set_source_files_properties(${RESOURCE_OBJ} PROPERTIES EXTERNAL_OBJECT true GENERATED true)
endforeach(RESOURCE_FILE)
add_library(clickhouse_server_configs STATIC ${CONFIG_OBJS})
add_library(clickhouse_server_configs STATIC ${RESOURCE_OBJS})
set_target_properties(clickhouse_server_configs PROPERTIES LINKER_LANGUAGE C)
# whole-archive prevents symbols from being discarded for unknown reason

View File

@ -212,22 +212,10 @@
<!-- Directory with user provided files that are accessible by 'file' table function. -->
<user_files_path>/var/lib/clickhouse/user_files/</user_files_path>
<!-- Sources to read users, roles, access rights, profiles of settings, quotas. -->
<user_directories>
<users_xml>
<!-- Path to configuration file with predefined users. -->
<path>users.xml</path>
</users_xml>
<local_directory>
<!-- Path to folder where users created by SQL commands are stored. -->
<path>/var/lib/clickhouse/access/</path>
</local_directory>
</user_directories>
<!-- External user directories (LDAP). -->
<!-- LDAP server definitions. -->
<ldap_servers>
<!-- List LDAP servers with their connection parameters here to later use them as authenticators for dedicated users,
who have 'ldap' authentication mechanism specified instead of 'password'.
<!-- List LDAP servers with their connection parameters here to later 1) use them as authenticators for dedicated local users,
who have 'ldap' authentication mechanism specified instead of 'password', or to 2) use them as remote user directories.
Parameters:
host - LDAP server hostname or IP, this parameter is mandatory and cannot be empty.
port - LDAP server port, default is 636 if enable_tls is set to true, 389 otherwise.
@ -246,7 +234,7 @@
tls_key_file - path to certificate key file.
tls_ca_cert_file - path to CA certificate file.
tls_ca_cert_dir - path to the directory containing CA certificates.
tls_cipher_suite - allowed cipher suite.
tls_cipher_suite - allowed cipher suite (in OpenSSL notation).
Example:
<my_ldap_server>
<host>localhost</host>
@ -265,6 +253,36 @@
-->
</ldap_servers>
<!-- Sources to read users, roles, access rights, profiles of settings, quotas. -->
<user_directories>
<users_xml>
<!-- Path to configuration file with predefined users. -->
<path>users.xml</path>
</users_xml>
<local_directory>
<!-- Path to folder where users created by SQL commands are stored. -->
<path>/var/lib/clickhouse/access/</path>
</local_directory>
<!-- To add an LDAP server as a remote user directory of users that are not defined locally, define a single 'ldap' section
with the following parameters:
server - one of LDAP server names defined in 'ldap_servers' config section above.
This parameter is mandatory and cannot be empty.
roles - section with a list of locally defined roles that will be assigned to each user retrieved from the LDAP server.
If no roles are specified, user will not be able to perform any actions after authentication.
If any of the listed roles is not defined locally at the time of authentication, the authenthication attept
will fail as if the provided password was incorrect.
Example:
<ldap>
<server>my_ldap_server</server>
<roles>
<my_local_role1 />
<my_local_role2 />
</roles>
</ldap>
-->
</user_directories>
<!-- Default profile of settings. -->
<default_profile>default</default_profile>

437
programs/server/play.html Normal file
View File

@ -0,0 +1,437 @@
<html> <!-- TODO If I write DOCTYPE HTML something changes but I don't know what. -->
<head>
<meta charset="UTF-8">
<title>ClickHouse Query</title>
<!-- Code style:
Do not use any JavaScript or CSS frameworks or preprocessors.
This HTML page should not require any build systems (node.js, npm, gulp, etc.)
This HTML page should not be minified, instead it should be reasonably minimalistic by itself.
This HTML page should not load any external resources
(CSS and JavaScript must be embedded directly to the page. No external fonts or images should be loaded).
This UI should look as lightweight, clean and fast as possible.
All UI elements must be aligned in pixel-perfect way.
There should not be any animations.
No unexpected changes in positions of elements while the page is loading.
Navigation by keyboard should work.
64-bit numbers must display correctly.
-->
<style type="text/css">
:root {
--background-color: #DDF8FF; /* Or #FFFBEF; actually many pastel colors look great for light theme. */
--element-background-color: #FFF;
--border-color: #EEE;
--shadow-color: rgba(0, 0, 0, 0.1);
--button-color: #FFAA00; /* Orange on light-cyan is especially good. */
--text-color: #000;
--button-active-color: #F00;
--button-active-text-color: #FFF;
--misc-text-color: #888;
--error-color: #FEE; /* Light-pink on light-cyan is so neat, I even want to trigger errors to see this cool combination of colors. */
--table-header-color: #F8F8F8;
--table-hover-color: #FFF8EF;
--null-color: #A88;
}
[data-theme="dark"] {
--background-color: #000;
--element-background-color: #102030;
--border-color: #111;
--shadow-color: rgba(255, 255, 255, 0.1);
--text-color: #CCC;
--button-color: #FFAA00;
--button-text-color: #000;
--button-active-color: #F00;
--button-active-text-color: #FFF;
--misc-text-color: #888;
--error-color: #400; /* Light-pink on light-cyan is so neat, I even want to trigger errors to see this cool combination of colors. */
--table-header-color: #102020;
--table-hover-color: #003333;
--null-color: #A88;
}
html, body
{
/* Personal choice. */
font-family: Sans-Serif;
background: var(--background-color);
color: var(--text-color);
}
/* Otherwise Webkit based browsers will display ugly border on focus. */
textarea, input, button
{
outline: none;
border: none;
color: var(--text-color);
}
/* Otherwise scrollbar may appear dynamically and it will alter viewport height,
then relative heights of elements will change suddenly, and it will break overall impression. */
/* html
{
overflow-x: scroll;
}*/
div
{
width: 100%;
}
.monospace
{
/* Prefer fonts that have full hinting info. This is important for non-retina displays.
Also I personally dislike "Ubuntu" font due to the similarity of 'r' and 'г' (it looks very ignorant).
*/
font-family: Liberation Mono, DejaVu Sans Mono, MonoLisa, Consolas, Monospace;
}
.shadow
{
box-shadow: 0 0 1rem var(--shadow-color);
}
input, textarea
{
border: 1px solid var(--border-color);
/* The font must be not too small (to be inclusive) and not too large (it's less practical and make general feel of insecurity) */
font-size: 11pt;
padding: 0.25rem;
background-color: var(--element-background-color);
}
#query
{
/* Make enough space for even huge queries. */
height: 20%;
width: 100%;
}
#inputs
{
white-space: nowrap;
width: 100%;
}
#url
{
width: 70%;
}
#user
{
width: 15%;
}
#password
{
width: 15%;
}
#run_div
{
margin-top: 1rem;
}
#run
{
color: var(--button-text-color);
background-color: var(--button-color);
padding: 0.25rem 1rem;
cursor: pointer;
font-weight: bold;
font-size: 100%; /* Otherwise button element will have lower font size. */
}
#run:hover, #run:focus
{
color: var(--button-active-text-color);
background-color: var(--button-active-color);
}
#stats
{
float: right;
color: var(--misc-text-color);
}
#toggle-light, #toggle-dark
{
float: right;
padding-right: 0.5rem;
cursor: pointer;
}
.hint
{
color: var(--misc-text-color);
}
#data_div
{
margin-top: 1rem;
}
#data-table
{
border-collapse: collapse;
border-spacing: 0;
/* I need pixel-perfect alignment but not sure the following is correct, please help */
min-width: calc(100vw - 2rem);
}
/* Will be displayed when user specified custom format. */
#data-unparsed
{
background-color: var(--element-background-color);
margin-top: 0rem;
padding: 0.25rem 0.5rem;
display: none;
}
td
{
background-color: var(--element-background-color);
white-space: nowrap;
/* For wide tables any individual column will be no more than 50% of page width. */
max-width: 50vw;
/* The content is cut unless you hover. */
overflow: hidden;
padding: 0.25rem 0.5rem;
border: 1px solid var(--border-color);
white-space: pre;
}
td.right
{
text-align: right;
}
th
{
padding: 0.25rem 0.5rem;
text-align: middle;
background-color: var(--table-header-color);
border: 1px solid var(--border-color);
}
/* The row under mouse pointer is highlight for better legibility. */
tr:hover, tr:hover td
{
background-color: var(--table-hover-color);
}
tr:hover
{
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.1);
}
#error
{
background: var(--error-color);
white-space: pre-wrap;
padding: 0.5rem 1rem;
display: none;
}
/* When mouse pointer is over table cell, will display full text (with wrap) instead of cut.
TODO Find a way to make it work on touch devices. */
td.left:hover
{
white-space: pre-wrap;
}
/* The style for SQL NULL */
.null
{
color: var(--null-color);
}
</style>
</head>
<body>
<div id="inputs">
<input class="monospace shadow" id="url" type="text" value="http://localhost:8123/" /><input class="monospace shadow" id="user" type="text" value="default" /><input class="monospace shadow" id="password" type="password" />
</div>
<div>
<textarea autofocus spellcheck="false" class="monospace shadow" id="query"></textarea>
</div>
<div id="run_div">
<button class="shadow" id="run">Run</button>
<span class="hint">&nbsp;(Ctrl+Enter)</span>
<span id="stats"></span>
<span id="toggle-dark">🌑</span><span id="toggle-light">🌞</span>
</div>
<div id="data_div">
<table class="monospace shadow" id="data-table"></table>
<pre class="monospace shadow" id="data-unparsed"></pre>
</div>
<p id="error" class="monospace shadow">
</p>
</body>
<script type="text/javascript">
/// Substitute the address of the server where the page is served.
if (location.protocol != 'file:') {
document.getElementById('url').value = location.origin;
}
function post()
{
var url = document.getElementById('url').value +
/// Ask server to allow cross-domain requests.
'?add_http_cors_header=1' +
'&user=' + encodeURIComponent(document.getElementById('user').value) +
'&password=' + encodeURIComponent(document.getElementById('password').value) +
'&default_format=JSONCompact' +
/// Safety settings to prevent results that browser cannot display.
'&max_result_rows=1000&max_result_bytes=10000000&result_overflow_mode=break';
var query = document.getElementById('query').value;
var xhr = new XMLHttpRequest;
xhr.open('POST', url, true);
xhr.send(query);
xhr.onreadystatechange = function()
{
if (this.readyState === XMLHttpRequest.DONE) {
if (this.status === 200) {
var json;
try { json = JSON.parse(this.response); } catch (e) {}
if (json !== undefined && json.statistics !== undefined) {
renderResult(json);
} else {
renderUnparsedResult(this.response);
}
} else {
renderError(this.response);
}
} else {
//console.log(this);
}
}
}
document.getElementById('run').onclick = function()
{
post();
}
document.getElementById('query').onkeypress = function(event)
{
/// Firefox has code 13 for Enter and Chromium has code 10.
if (event.ctrlKey && (event.charCode == 13 || event.charCode == 10)) {
post();
}
}
function clear()
{
var table = document.getElementById('data-table');
while (table.firstChild) {
table.removeChild(table.lastChild);
}
document.getElementById('data-unparsed').innerText = '';
document.getElementById('data-unparsed').style.display = 'none';
document.getElementById('error').innerText = '';
document.getElementById('error').style.display = 'none';
document.getElementById('stats').innerText = '';
}
function renderResult(response)
{
//console.log(response);
clear();
var stats = document.getElementById('stats');
stats.innerText = 'Elapsed: ' + response.statistics.elapsed.toFixed(3) + " sec, read " + response.statistics.rows_read + " rows.";
var thead = document.createElement('thead');
for (var idx in response.meta) {
var th = document.createElement('th');
var name = document.createTextNode(response.meta[idx].name);
th.appendChild(name);
thead.appendChild(th);
}
/// To prevent hanging the browser, limit the number of cells in a table.
/// It's important to have the limit on number of cells, not just rows, because tables may be wide or narrow.
var max_rows = 10000 / response.meta.length;
var row_num = 0;
var tbody = document.createElement('tbody');
for (var row_idx in response.data) {
var tr = document.createElement('tr');
for (var col_idx in response.data[row_idx]) {
var td = document.createElement('td');
var cell = response.data[row_idx][col_idx];
var is_null = (cell === null);
var content = document.createTextNode(is_null ? 'ᴺᵁᴸᴸ' : cell);
td.appendChild(content);
td.className = response.meta[col_idx].type.match(/^(U?Int|Decimal|Float)/) ? 'right' : 'left';
if (is_null) {
td.className += ' null';
}
tr.appendChild(td);
}
tbody.appendChild(tr);
++row_num;
if (row_num >= max_rows) {
break;
}
}
var table = document.getElementById('data-table');
table.appendChild(thead);
table.appendChild(tbody);
}
/// A function to render raw data when non-default format is specified.
function renderUnparsedResult(response)
{
clear();
var data = document.getElementById('data-unparsed')
data.innerText = response;
/// inline-block make width adjust to the size of content.
data.style.display = 'inline-block';
}
function renderError(response)
{
clear();
document.getElementById('error').innerText = response;
document.getElementById('error').style.display = 'block';
}
function setColorTheme(theme)
{
window.localStorage.setItem('theme', theme);
document.documentElement.setAttribute('data-theme', theme);
}
/// The choice of color theme is saved in browser.
var theme = window.localStorage.getItem('theme');
if (theme) {
setColorTheme(theme);
}
document.getElementById('toggle-light').onclick = function()
{
setColorTheme('light');
}
document.getElementById('toggle-dark').onclick = function()
{
setColorTheme('dark');
}
</script>
</html>

View File

@ -3,6 +3,7 @@
#include <Access/MemoryAccessStorage.h>
#include <Access/UsersConfigAccessStorage.h>
#include <Access/DiskAccessStorage.h>
#include <Access/LDAPAccessStorage.h>
#include <Access/ContextAccess.h>
#include <Access/RoleCache.h>
#include <Access/RowPolicyCache.h>
@ -253,6 +254,12 @@ void AccessControlManager::addMemoryStorage(const String & storage_name_)
}
void AccessControlManager::addLDAPStorage(const String & storage_name_, const Poco::Util::AbstractConfiguration & config_, const String & prefix_)
{
addStorage(std::make_shared<LDAPAccessStorage>(storage_name_, this, config_, prefix_));
}
void AccessControlManager::addStoragesFromUserDirectoriesConfig(
const Poco::Util::AbstractConfiguration & config,
const String & key,
@ -275,6 +282,8 @@ void AccessControlManager::addStoragesFromUserDirectoriesConfig(
type = UsersConfigAccessStorage::STORAGE_TYPE;
else if ((type == "local") || (type == "local_directory"))
type = DiskAccessStorage::STORAGE_TYPE;
else if (type == "ldap")
type = LDAPAccessStorage::STORAGE_TYPE;
String name = config.getString(prefix + ".name", type);
@ -295,6 +304,10 @@ void AccessControlManager::addStoragesFromUserDirectoriesConfig(
bool readonly = config.getBool(prefix + ".readonly", false);
addDiskStorage(name, path, readonly);
}
else if (type == LDAPAccessStorage::STORAGE_TYPE)
{
addLDAPStorage(name, config, prefix);
}
else
throw Exception("Unknown storage type '" + type + "' at " + prefix + " in config", ErrorCodes::UNKNOWN_ELEMENT_IN_CONFIG);
}
@ -346,7 +359,7 @@ UUID AccessControlManager::login(const String & user_name, const String & passwo
void AccessControlManager::setExternalAuthenticatorsConfig(const Poco::Util::AbstractConfiguration & config)
{
external_authenticators->setConfig(config, getLogger());
external_authenticators->setConfiguration(config, getLogger());
}

View File

@ -82,6 +82,9 @@ public:
void addMemoryStorage();
void addMemoryStorage(const String & storage_name_);
/// Adds LDAPAccessStorage which allows querying remote LDAP server for user info.
void addLDAPStorage(const String & storage_name_, const Poco::Util::AbstractConfiguration & config_, const String & prefix_);
/// Adds storages from <users_directories> config.
void addStoragesFromUserDirectoriesConfig(const Poco::Util::AbstractConfiguration & config,
const String & key,

View File

@ -156,7 +156,7 @@ void ExternalAuthenticators::reset()
ldap_server_params.clear();
}
void ExternalAuthenticators::setConfig(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log)
void ExternalAuthenticators::setConfiguration(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log)
{
std::scoped_lock lock(mutex);
reset();

View File

@ -26,7 +26,7 @@ class ExternalAuthenticators
{
public:
void reset();
void setConfig(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log);
void setConfiguration(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log);
void setLDAPServerParams(const String & server, const LDAPServerParams & params);
LDAPServerParams getLDAPServerParams(const String & server) const;

View File

@ -14,6 +14,8 @@ namespace ErrorCodes
extern const int ACCESS_ENTITY_ALREADY_EXISTS;
extern const int ACCESS_ENTITY_NOT_FOUND;
extern const int ACCESS_STORAGE_READONLY;
extern const int WRONG_PASSWORD;
extern const int IP_ADDRESS_NOT_ALLOWED;
extern const int AUTHENTICATION_FAILED;
extern const int LOGICAL_ERROR;
}
@ -418,9 +420,21 @@ UUID IAccessStorage::login(
const String & user_name,
const String & password,
const Poco::Net::IPAddress & address,
const ExternalAuthenticators & external_authenticators) const
const ExternalAuthenticators & external_authenticators,
bool replace_exception_with_cannot_authenticate) const
{
return loginImpl(user_name, password, address, external_authenticators);
try
{
return loginImpl(user_name, password, address, external_authenticators);
}
catch (...)
{
if (!replace_exception_with_cannot_authenticate)
throw;
tryLogCurrentException(getLogger(), user_name + ": Authentication failed");
throwCannotAuthenticate(user_name);
}
}
@ -434,11 +448,16 @@ UUID IAccessStorage::loginImpl(
{
if (auto user = tryRead<User>(*id))
{
if (isPasswordCorrectImpl(*user, password, external_authenticators) && isAddressAllowedImpl(*user, address))
return *id;
if (!isPasswordCorrectImpl(*user, password, external_authenticators))
throwInvalidPassword();
if (!isAddressAllowedImpl(*user, address))
throwAddressNotAllowed(address);
return *id;
}
}
throwCannotAuthenticate(user_name);
throwNotFound(EntityType::USER, user_name);
}
@ -554,6 +573,15 @@ void IAccessStorage::throwReadonlyCannotRemove(EntityType type, const String & n
ErrorCodes::ACCESS_STORAGE_READONLY);
}
void IAccessStorage::throwAddressNotAllowed(const Poco::Net::IPAddress & address)
{
throw Exception("Connections from " + address.toString() + " are not allowed", ErrorCodes::IP_ADDRESS_NOT_ALLOWED);
}
void IAccessStorage::throwInvalidPassword()
{
throw Exception("Invalid password", ErrorCodes::WRONG_PASSWORD);
}
void IAccessStorage::throwCannotAuthenticate(const String & user_name)
{

View File

@ -144,7 +144,7 @@ public:
/// Finds an user, check its password and returns the ID of the user.
/// Throws an exception if no such user or password is incorrect.
UUID login(const String & user_name, const String & password, const Poco::Net::IPAddress & address, const ExternalAuthenticators & external_authenticators) const;
UUID login(const String & user_name, const String & password, const Poco::Net::IPAddress & address, const ExternalAuthenticators & external_authenticators, bool replace_exception_with_cannot_authenticate = true) const;
/// Returns the ID of an user who has logged in (maybe on another node).
/// The function assumes that the password has been already checked somehow, so we can skip checking it now.
@ -182,6 +182,8 @@ protected:
[[noreturn]] void throwReadonlyCannotInsert(EntityType type, const String & name) const;
[[noreturn]] void throwReadonlyCannotUpdate(EntityType type, const String & name) const;
[[noreturn]] void throwReadonlyCannotRemove(EntityType type, const String & name) const;
[[noreturn]] static void throwAddressNotAllowed(const Poco::Net::IPAddress & address);
[[noreturn]] static void throwInvalidPassword();
[[noreturn]] static void throwCannotAuthenticate(const String & user_name);
using Notification = std::tuple<OnChangedHandler, UUID, AccessEntityPtr>;

View File

@ -0,0 +1,313 @@
#include <Access/LDAPAccessStorage.h>
#include <Access/AccessControlManager.h>
#include <Access/User.h>
#include <Access/Role.h>
#include <Common/Exception.h>
#include <common/logger_useful.h>
#include <ext/scope_guard.h>
#include <Poco/Util/AbstractConfiguration.h>
#include <Poco/JSON/JSON.h>
#include <Poco/JSON/Object.h>
#include <Poco/JSON/Stringifier.h>
#include <boost/range/algorithm/copy.hpp>
#include <iterator>
#include <sstream>
namespace DB
{
namespace ErrorCodes
{
extern const int BAD_ARGUMENTS;
}
LDAPAccessStorage::LDAPAccessStorage(const String & storage_name_, AccessControlManager * access_control_manager_, const Poco::Util::AbstractConfiguration & config, const String & prefix)
: IAccessStorage(storage_name_)
{
setConfiguration(access_control_manager_, config, prefix);
}
void LDAPAccessStorage::setConfiguration(AccessControlManager * access_control_manager_, const Poco::Util::AbstractConfiguration & config, const String & prefix)
{
std::scoped_lock lock(mutex);
// TODO: switch to passing config as a ConfigurationView and remove this extra prefix once a version of Poco with proper implementation is available.
const String prefix_str = (prefix.empty() ? "" : prefix + ".");
const bool has_server = config.has(prefix_str + "server");
const bool has_roles = config.has(prefix_str + "roles");
if (!has_server)
throw Exception("Missing 'server' field for LDAP user directory.", ErrorCodes::BAD_ARGUMENTS);
const auto ldap_server_cfg = config.getString(prefix_str + "server");
if (ldap_server_cfg.empty())
throw Exception("Empty 'server' field for LDAP user directory.", ErrorCodes::BAD_ARGUMENTS);
std::set<String> roles_cfg;
if (has_roles)
{
Poco::Util::AbstractConfiguration::Keys role_names;
config.keys(prefix_str + "roles", role_names);
// Currently, we only extract names of roles from the section names and assign them directly and unconditionally.
roles_cfg.insert(role_names.begin(), role_names.end());
}
access_control_manager = access_control_manager_;
ldap_server = ldap_server_cfg;
default_role_names.swap(roles_cfg);
roles_of_interest.clear();
role_change_subscription = access_control_manager->subscribeForChanges<Role>(
[this] (const UUID & id, const AccessEntityPtr & entity)
{
return this->processRoleChange(id, entity);
}
);
/// Update `roles_of_interests` with initial values.
for (const auto & role_name : default_role_names)
{
if (auto role_id = access_control_manager->find<Role>(role_name))
roles_of_interest.emplace(*role_id, role_name);
}
}
void LDAPAccessStorage::processRoleChange(const UUID & id, const AccessEntityPtr & entity)
{
std::scoped_lock lock(mutex);
/// Update `roles_of_interests`.
auto role = typeid_cast<std::shared_ptr<const Role>>(entity);
bool need_to_update_users = false;
if (role && default_role_names.count(role->getName()))
{
/// If a role was created with one of the `default_role_names` or renamed to one of the `default_role_names`,
/// then set `need_to_update_users`.
need_to_update_users = roles_of_interest.insert_or_assign(id, role->getName()).second;
}
else
{
/// If a role was removed or renamed to a name which isn't contained in the `default_role_names`,
/// then set `need_to_update_users`.
need_to_update_users = roles_of_interest.erase(id) > 0;
}
/// Update users which have been created.
if (need_to_update_users)
{
auto update_func = [this] (const AccessEntityPtr & entity_) -> AccessEntityPtr
{
if (auto user = typeid_cast<std::shared_ptr<const User>>(entity_))
{
auto changed_user = typeid_cast<std::shared_ptr<User>>(user->clone());
auto & granted_roles = changed_user->granted_roles.roles;
granted_roles.clear();
boost::range::copy(roles_of_interest | boost::adaptors::map_keys, std::inserter(granted_roles, granted_roles.end()));
return changed_user;
}
return entity_;
};
memory_storage.update(memory_storage.findAll<User>(), update_func);
}
}
void LDAPAccessStorage::checkAllDefaultRoleNamesFoundNoLock() const
{
boost::container::flat_set<std::string_view> role_names_of_interest;
boost::range::copy(roles_of_interest | boost::adaptors::map_values, std::inserter(role_names_of_interest, role_names_of_interest.end()));
for (const auto & role_name : default_role_names)
{
if (!role_names_of_interest.count(role_name))
throwDefaultRoleNotFound(role_name);
}
}
const char * LDAPAccessStorage::getStorageType() const
{
return STORAGE_TYPE;
}
String LDAPAccessStorage::getStorageParamsJSON() const
{
std::scoped_lock lock(mutex);
Poco::JSON::Object params_json;
params_json.set("server", ldap_server);
params_json.set("roles", default_role_names);
std::ostringstream oss;
Poco::JSON::Stringifier::stringify(params_json, oss);
return oss.str();
}
std::optional<UUID> LDAPAccessStorage::findImpl(EntityType type, const String & name) const
{
std::scoped_lock lock(mutex);
return memory_storage.find(type, name);
}
std::vector<UUID> LDAPAccessStorage::findAllImpl(EntityType type) const
{
std::scoped_lock lock(mutex);
return memory_storage.findAll(type);
}
bool LDAPAccessStorage::existsImpl(const UUID & id) const
{
std::scoped_lock lock(mutex);
return memory_storage.exists(id);
}
AccessEntityPtr LDAPAccessStorage::readImpl(const UUID & id) const
{
std::scoped_lock lock(mutex);
return memory_storage.read(id);
}
String LDAPAccessStorage::readNameImpl(const UUID & id) const
{
std::scoped_lock lock(mutex);
return memory_storage.readName(id);
}
bool LDAPAccessStorage::canInsertImpl(const AccessEntityPtr &) const
{
return false;
}
UUID LDAPAccessStorage::insertImpl(const AccessEntityPtr & entity, bool)
{
throwReadonlyCannotInsert(entity->getType(), entity->getName());
}
void LDAPAccessStorage::removeImpl(const UUID & id)
{
std::scoped_lock lock(mutex);
auto entity = read(id);
throwReadonlyCannotRemove(entity->getType(), entity->getName());
}
void LDAPAccessStorage::updateImpl(const UUID & id, const UpdateFunc &)
{
std::scoped_lock lock(mutex);
auto entity = read(id);
throwReadonlyCannotUpdate(entity->getType(), entity->getName());
}
ext::scope_guard LDAPAccessStorage::subscribeForChangesImpl(const UUID & id, const OnChangedHandler & handler) const
{
std::scoped_lock lock(mutex);
return memory_storage.subscribeForChanges(id, handler);
}
ext::scope_guard LDAPAccessStorage::subscribeForChangesImpl(EntityType type, const OnChangedHandler & handler) const
{
std::scoped_lock lock(mutex);
return memory_storage.subscribeForChanges(type, handler);
}
bool LDAPAccessStorage::hasSubscriptionImpl(const UUID & id) const
{
std::scoped_lock lock(mutex);
return memory_storage.hasSubscription(id);
}
bool LDAPAccessStorage::hasSubscriptionImpl(EntityType type) const
{
std::scoped_lock lock(mutex);
return memory_storage.hasSubscription(type);
}
UUID LDAPAccessStorage::loginImpl(const String & user_name, const String & password, const Poco::Net::IPAddress & address, const ExternalAuthenticators & external_authenticators) const
{
std::scoped_lock lock(mutex);
auto id = memory_storage.find<User>(user_name);
if (id)
{
auto user = memory_storage.read<User>(*id);
if (!isPasswordCorrectImpl(*user, password, external_authenticators))
throwInvalidPassword();
if (!isAddressAllowedImpl(*user, address))
throwAddressNotAllowed(address);
return *id;
}
else
{
// User does not exist, so we create one, and will add it if authentication is successful.
auto user = std::make_shared<User>();
user->setName(user_name);
user->authentication = Authentication(Authentication::Type::LDAP_SERVER);
user->authentication.setServerName(ldap_server);
if (!isPasswordCorrectImpl(*user, password, external_authenticators))
throwInvalidPassword();
if (!isAddressAllowedImpl(*user, address))
throwAddressNotAllowed(address);
checkAllDefaultRoleNamesFoundNoLock();
auto & granted_roles = user->granted_roles.roles;
boost::range::copy(roles_of_interest | boost::adaptors::map_keys, std::inserter(granted_roles, granted_roles.end()));
return memory_storage.insert(user);
}
}
UUID LDAPAccessStorage::getIDOfLoggedUserImpl(const String & user_name) const
{
std::scoped_lock lock(mutex);
auto id = memory_storage.find<User>(user_name);
if (id)
{
return *id;
}
else
{
// User does not exist, so we create one, and add it pretending that the authentication is successful.
auto user = std::make_shared<User>();
user->setName(user_name);
user->authentication = Authentication(Authentication::Type::LDAP_SERVER);
user->authentication.setServerName(ldap_server);
checkAllDefaultRoleNamesFoundNoLock();
auto & granted_roles = user->granted_roles.roles;
boost::range::copy(roles_of_interest | boost::adaptors::map_keys, std::inserter(granted_roles, granted_roles.end()));
return memory_storage.insert(user);
}
}
void LDAPAccessStorage::throwDefaultRoleNotFound(const String & role_name)
{
throw Exception("One of the default roles, the role '" + role_name + "', is not found", IAccessEntity::TypeInfo::get(IAccessEntity::Type::ROLE).not_found_error_code);
}
}

View File

@ -0,0 +1,71 @@
#pragma once
#include <Access/MemoryAccessStorage.h>
#include <Core/Types.h>
#include <ext/scope_guard.h>
#include <map>
#include <mutex>
#include <set>
namespace Poco
{
namespace Util
{
class AbstractConfiguration;
}
}
namespace DB
{
class AccessControlManager;
/// Implementation of IAccessStorage which allows attaching users from a remote LDAP server.
/// Currently, any user name will be treated as a name of an existing remote user,
/// a user info entity will be created, with LDAP_SERVER authentication type.
class LDAPAccessStorage : public IAccessStorage
{
public:
static constexpr char STORAGE_TYPE[] = "ldap";
explicit LDAPAccessStorage(const String & storage_name_, AccessControlManager * access_control_manager_, const Poco::Util::AbstractConfiguration & config, const String & prefix);
virtual ~LDAPAccessStorage() override = default;
public: // IAccessStorage implementations.
virtual const char * getStorageType() const override;
virtual String getStorageParamsJSON() const override;
private: // IAccessStorage implementations.
virtual std::optional<UUID> findImpl(EntityType type, const String & name) const override;
virtual std::vector<UUID> findAllImpl(EntityType type) const override;
virtual bool existsImpl(const UUID & id) const override;
virtual AccessEntityPtr readImpl(const UUID & id) const override;
virtual String readNameImpl(const UUID & id) const override;
virtual bool canInsertImpl(const AccessEntityPtr &) const override;
virtual UUID insertImpl(const AccessEntityPtr & entity, bool replace_if_exists) override;
virtual void removeImpl(const UUID & id) override;
virtual void updateImpl(const UUID & id, const UpdateFunc & update_func) override;
virtual ext::scope_guard subscribeForChangesImpl(const UUID & id, const OnChangedHandler & handler) const override;
virtual ext::scope_guard subscribeForChangesImpl(EntityType type, const OnChangedHandler & handler) const override;
virtual bool hasSubscriptionImpl(const UUID & id) const override;
virtual bool hasSubscriptionImpl(EntityType type) const override;
virtual UUID loginImpl(const String & user_name, const String & password, const Poco::Net::IPAddress & address, const ExternalAuthenticators & external_authenticators) const override;
virtual UUID getIDOfLoggedUserImpl(const String & user_name) const override;
private:
void setConfiguration(AccessControlManager * access_control_manager_, const Poco::Util::AbstractConfiguration & config, const String & prefix);
void processRoleChange(const UUID & id, const AccessEntityPtr & entity);
void checkAllDefaultRoleNamesFoundNoLock() const;
[[noreturn]] static void throwDefaultRoleNotFound(const String & role_name);
mutable std::recursive_mutex mutex;
AccessControlManager * access_control_manager = nullptr;
String ldap_server;
std::set<String> default_role_names;
std::map<UUID, String> roles_of_interest;
ext::scope_guard role_change_subscription;
mutable MemoryAccessStorage memory_storage;
};
}

View File

@ -2,6 +2,8 @@
#include <Common/Exception.h>
#include <ext/scope_guard.h>
#include <mutex>
#include <cstring>
#include <sys/time.h>
@ -27,16 +29,13 @@ LDAPClient::~LDAPClient()
closeConnection();
}
void LDAPClient::openConnection()
{
const bool graceful_bind_failure = false;
diag(openConnection(graceful_bind_failure));
}
#if USE_LDAP
namespace
{
std::recursive_mutex ldap_global_mutex;
auto escapeForLDAP(const String & src)
{
String dest;
@ -63,10 +62,13 @@ namespace
return dest;
}
}
void LDAPClient::diag(const int rc)
{
std::scoped_lock lock(ldap_global_mutex);
if (rc != LDAP_SUCCESS)
{
String text;
@ -100,8 +102,10 @@ void LDAPClient::diag(const int rc)
}
}
int LDAPClient::openConnection(const bool graceful_bind_failure)
void LDAPClient::openConnection()
{
std::scoped_lock lock(ldap_global_mutex);
closeConnection();
{
@ -232,8 +236,6 @@ int LDAPClient::openConnection(const bool graceful_bind_failure)
if (params.enable_tls == LDAPServerParams::TLSEnable::YES_STARTTLS)
diag(ldap_start_tls_s(handle, nullptr, nullptr));
int rc = LDAP_OTHER;
switch (params.sasl_mechanism)
{
case LDAPServerParams::SASLMechanism::SIMPLE:
@ -244,20 +246,21 @@ int LDAPClient::openConnection(const bool graceful_bind_failure)
cred.bv_val = const_cast<char *>(params.password.c_str());
cred.bv_len = params.password.size();
rc = ldap_sasl_bind_s(handle, dn.c_str(), LDAP_SASL_SIMPLE, &cred, nullptr, nullptr, nullptr);
if (!graceful_bind_failure)
diag(rc);
diag(ldap_sasl_bind_s(handle, dn.c_str(), LDAP_SASL_SIMPLE, &cred, nullptr, nullptr, nullptr));
break;
}
default:
{
throw Exception("Unknown SASL mechanism", ErrorCodes::LDAP_ERROR);
}
}
return rc;
}
void LDAPClient::closeConnection() noexcept
{
std::scoped_lock lock(ldap_global_mutex);
if (!handle)
return;
@ -267,42 +270,21 @@ void LDAPClient::closeConnection() noexcept
bool LDAPSimpleAuthClient::check()
{
if (params.user.empty())
throw Exception("LDAP authentication of a user with an empty name is not allowed", ErrorCodes::BAD_ARGUMENTS);
std::scoped_lock lock(ldap_global_mutex);
if (params.user.empty())
throw Exception("LDAP authentication of a user with empty name is not allowed", ErrorCodes::BAD_ARGUMENTS);
// Silently reject authentication attempt if the password is empty as if it didn't match.
if (params.password.empty())
return false; // Silently reject authentication attempt if the password is empty as if it didn't match.
return false;
SCOPE_EXIT({ closeConnection(); });
const bool graceful_bind_failure = true;
const auto rc = openConnection(graceful_bind_failure);
// Will throw on any error, including invalid credentials.
openConnection();
bool result = false;
switch (rc)
{
case LDAP_SUCCESS:
{
result = true;
break;
}
case LDAP_INVALID_CREDENTIALS:
{
result = false;
break;
}
default:
{
result = false;
diag(rc);
break;
}
}
return result;
return true;
}
#else // USE_LDAP
@ -312,7 +294,7 @@ void LDAPClient::diag(const int)
throw Exception("ClickHouse was built without LDAP support", ErrorCodes::FEATURE_IS_NOT_ENABLED_AT_BUILD_TIME);
}
int LDAPClient::openConnection(const bool)
void LDAPClient::openConnection()
{
throw Exception("ClickHouse was built without LDAP support", ErrorCodes::FEATURE_IS_NOT_ENABLED_AT_BUILD_TIME);
}

View File

@ -32,7 +32,6 @@ public:
protected:
MAYBE_NORETURN void diag(const int rc);
MAYBE_NORETURN void openConnection();
int openConnection(const bool graceful_bind_failure = false);
void closeConnection() noexcept;
protected:

View File

@ -42,6 +42,7 @@ struct LDAPServerParams
enum class SASLMechanism
{
UNKNOWN,
SIMPLE
};

View File

@ -69,7 +69,7 @@ UUID MemoryAccessStorage::insertImpl(const AccessEntityPtr & new_entity, bool re
UUID id = generateRandomID();
std::lock_guard lock{mutex};
insertNoLock(generateRandomID(), new_entity, replace_if_exists, notifications);
insertNoLock(id, new_entity, replace_if_exists, notifications);
return id;
}

View File

@ -2,6 +2,7 @@
#include <Common/Exception.h>
#include <ext/range.h>
#include <boost/range/adaptor/map.hpp>
#include <boost/range/adaptor/reversed.hpp>
#include <boost/range/algorithm/copy.hpp>
#include <boost/range/algorithm/find.hpp>
@ -27,6 +28,15 @@ MultipleAccessStorage::MultipleAccessStorage(const String & storage_name_)
{
}
MultipleAccessStorage::~MultipleAccessStorage()
{
/// It's better to remove the storages in the reverse order because they could depend on each other somehow.
const auto storages = getStoragesPtr();
for (const auto & storage : *storages | boost::adaptors::reversed)
{
removeStorage(storage);
}
}
void MultipleAccessStorage::setStorages(const std::vector<StoragePtr> & storages)
{
@ -400,7 +410,7 @@ UUID MultipleAccessStorage::loginImpl(const String & user_name, const String & p
{
try
{
auto id = storage->login(user_name, password, address, external_authenticators);
auto id = storage->login(user_name, password, address, external_authenticators, /* replace_exception_with_cannot_authenticate = */ false);
std::lock_guard lock{mutex};
ids_cache.set(id, storage);
return id;
@ -416,7 +426,7 @@ UUID MultipleAccessStorage::loginImpl(const String & user_name, const String & p
throw;
}
}
throwCannotAuthenticate(user_name);
throwNotFound(EntityType::USER, user_name);
}

View File

@ -18,6 +18,7 @@ public:
using ConstStoragePtr = std::shared_ptr<const Storage>;
MultipleAccessStorage(const String & storage_name_ = STORAGE_TYPE);
~MultipleAccessStorage() override;
const char * getStorageType() const override { return STORAGE_TYPE; }

View File

@ -24,6 +24,7 @@ SRCS(
GrantedRoles.cpp
IAccessEntity.cpp
IAccessStorage.cpp
LDAPAccessStorage.cpp
LDAPClient.cpp
MemoryAccessStorage.cpp
MultipleAccessStorage.cpp

View File

@ -2,6 +2,7 @@
#include <string.h>
#include <cxxabi.h>
#include <cstdlib>
#include <Poco/String.h>
#include <common/logger_useful.h>
#include <IO/WriteHelpers.h>
@ -36,13 +37,13 @@ namespace ErrorCodes
Exception::Exception(const std::string & msg, int code)
: Poco::Exception(msg, code)
{
// In debug builds, treat LOGICAL_ERROR as an assertion failure.
// In debug builds and builds with sanitizers, treat LOGICAL_ERROR as an assertion failure.
// Log the message before we fail.
#ifndef NDEBUG
#ifdef ABORT_ON_LOGICAL_ERROR
if (code == ErrorCodes::LOGICAL_ERROR)
{
LOG_ERROR(&Poco::Logger::root(), "Logical error: '{}'.", msg);
assert(false);
LOG_FATAL(&Poco::Logger::root(), "Logical error: '{}'.", msg);
abort();
}
#endif
}

View File

@ -10,6 +10,10 @@
#include <fmt/format.h>
#if !defined(NDEBUG) || defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) || defined(MEMORY_SANITIZER) || defined(UNDEFINED_BEHAVIOR_SANITIZER)
#define ABORT_ON_LOGICAL_ERROR
#endif
namespace Poco { class Logger; }

View File

@ -502,8 +502,8 @@ Float NO_INLINE really_unrolled(const PODArray<UInt8> & keys, const PODArray<Flo
struct State4
{
Float sum[4] = {0, 0, 0, 0};
size_t count[4] = {0, 0, 0, 0};
Float sum[4]{};
size_t count[4]{};
template <UInt32 idx>
void add(Float value)
@ -522,13 +522,13 @@ Float NO_INLINE another_unrolled_x4(const PODArray<UInt8> & keys, const PODArray
{
State4 map[256]{};
size_t size = keys.size() & ~size_t(3);
for (size_t i = 0; i < size; i+=4)
size_t size = keys.size() / 4 * 4;
for (size_t i = 0; i < size; i += 4)
{
map[keys[i]].add<0>(values[i]);
map[keys[i+1]].add<1>(values[i]);
map[keys[i+2]].add<2>(values[i]);
map[keys[i+3]].add<3>(values[i]);
map[keys[i + 1]].add<1>(values[i]);
map[keys[i + 2]].add<2>(values[i]);
map[keys[i + 3]].add<3>(values[i]);
}
/// tail

View File

@ -131,7 +131,10 @@ TEST(Common, RWLockRecursive)
auto lock2 = fifo_lock->getLock(RWLockImpl::Read, "q2");
#ifndef ABORT_ON_LOGICAL_ERROR
/// It throws LOGICAL_ERROR
EXPECT_ANY_THROW({fifo_lock->getLock(RWLockImpl::Write, "q2");});
#endif
}
fifo_lock->getLock(RWLockImpl::Write, "q2");

View File

@ -180,4 +180,9 @@ void DiskDecorator::sync(int fd) const
delegate->sync(fd);
}
Executor & DiskDecorator::getExecutor()
{
return delegate->getExecutor();
}
}

View File

@ -4,6 +4,10 @@
namespace DB
{
/** Forwards all methods to another disk.
* Methods can be overridden by descendants.
*/
class DiskDecorator : public IDisk
{
public:
@ -46,6 +50,7 @@ public:
void close(int fd) const override;
void sync(int fd) const override;
const String getType() const override { return delegate->getType(); }
Executor & getExecutor() override;
protected:
DiskPtr delegate;

View File

@ -195,10 +195,10 @@ public:
/// Invoked when Global Context is shutdown.
virtual void shutdown() { }
private:
/// Returns executor to perform asynchronous operations.
Executor & getExecutor() { return *executor; }
virtual Executor & getExecutor() { return *executor; }
private:
std::unique_ptr<Executor> executor;
};

View File

@ -787,12 +787,6 @@ void DDLWorker::processTask(DDLTask & task)
storage = DatabaseCatalog::instance().tryGetTable(table_id, context);
}
/// For some reason we check consistency of cluster definition only
/// in case of ALTER query, but not in case of CREATE/DROP etc.
/// It's strange, but this behaviour exits for a long and we cannot change it.
if (storage && query_with_table->as<ASTAlterQuery>())
checkShardConfig(query_with_table->table, task, storage);
if (storage && taskShouldBeExecutedOnLeader(rewritten_ast, storage) && !is_circular_replicated)
tryExecuteQueryOnLeaderReplica(task, storage, rewritten_query, task.entry_path, zookeeper);
else
@ -837,35 +831,6 @@ bool DDLWorker::taskShouldBeExecutedOnLeader(const ASTPtr ast_ddl, const Storage
return storage->supportsReplication();
}
void DDLWorker::checkShardConfig(const String & table, const DDLTask & task, StoragePtr storage) const
{
const auto & shard_info = task.cluster->getShardsInfo().at(task.host_shard_num);
bool config_is_replicated_shard = shard_info.hasInternalReplication();
if (dynamic_cast<const StorageDistributed *>(storage.get()))
{
LOG_TRACE(log, "Table {} is distributed, skip checking config.", backQuote(table));
return;
}
if (storage->supportsReplication() && !config_is_replicated_shard)
{
throw Exception(ErrorCodes::INCONSISTENT_CLUSTER_DEFINITION,
"Table {} is replicated, but shard #{} isn't replicated according to its cluster definition. "
"Possibly <internal_replication>true</internal_replication> is forgotten in the cluster config.",
backQuote(table), task.host_shard_num + 1);
}
if (!storage->supportsReplication() && config_is_replicated_shard)
{
throw Exception(ErrorCodes::INCONSISTENT_CLUSTER_DEFINITION,
"Table {} isn't replicated, but shard #{} is replicated according to its cluster definition",
backQuote(table), task.host_shard_num + 1);
}
}
bool DDLWorker::tryExecuteQueryOnLeaderReplica(
DDLTask & task,
StoragePtr storage,

View File

@ -75,9 +75,6 @@ private:
/// Check that query should be executed on leader replica only
static bool taskShouldBeExecutedOnLeader(const ASTPtr ast_ddl, StoragePtr storage);
/// Check that shard has consistent config with table
void checkShardConfig(const String & table, const DDLTask & task, StoragePtr storage) const;
/// Executes query only on leader replica in case of replicated table.
/// Queries like TRUNCATE/ALTER .../OPTIMIZE have to be executed only on one node of shard.
/// Most of these queries can be executed on non-leader replica, but actually they still send

View File

@ -117,11 +117,14 @@ ExpressionAnalyzer::ExpressionAnalyzer(
const TreeRewriterResultPtr & syntax_analyzer_result_,
const Context & context_,
size_t subquery_depth_,
bool do_global)
bool do_global,
SubqueriesForSets subqueries_for_sets_)
: query(query_), context(context_), settings(context.getSettings())
, subquery_depth(subquery_depth_)
, syntax(syntax_analyzer_result_)
{
subqueries_for_sets = std::move(subqueries_for_sets_);
/// external_tables, subqueries_for_sets for global subqueries.
/// Replaces global subqueries with the generated names of temporary tables that will be sent to remote servers.
initGlobalSubqueriesAndExternalTables(do_global);
@ -421,11 +424,17 @@ bool ExpressionAnalyzer::makeAggregateDescriptions(ActionsDAGPtr & actions)
aggregate.argument_names.resize(arguments.size());
DataTypes types(arguments.size());
const auto & index = actions->getIndex();
for (size_t i = 0; i < arguments.size(); ++i)
{
getRootActionsNoMakeSet(arguments[i], true, actions);
const std::string & name = arguments[i]->getColumnName();
types[i] = actions->getIndex().find(name)->second->result_type;
auto it = index.find(name);
if (it == index.end())
throw Exception(ErrorCodes::UNKNOWN_IDENTIFIER, "Unknown identifier (in aggregate function '{}'): {}", node->name, name);
types[i] = it->second->result_type;
aggregate.argument_names[i] = name;
}

View File

@ -93,7 +93,7 @@ public:
const ASTPtr & query_,
const TreeRewriterResultPtr & syntax_analyzer_result_,
const Context & context_)
: ExpressionAnalyzer(query_, syntax_analyzer_result_, context_, 0, false)
: ExpressionAnalyzer(query_, syntax_analyzer_result_, context_, 0, false, {})
{}
void appendExpression(ExpressionActionsChain & chain, const ASTPtr & expr, bool only_types);
@ -124,7 +124,8 @@ protected:
const TreeRewriterResultPtr & syntax_analyzer_result_,
const Context & context_,
size_t subquery_depth_,
bool do_global_);
bool do_global_,
SubqueriesForSets subqueries_for_sets_);
ASTPtr query;
const Context & context;
@ -244,8 +245,9 @@ public:
const StorageMetadataPtr & metadata_snapshot_,
const NameSet & required_result_columns_ = {},
bool do_global_ = false,
const SelectQueryOptions & options_ = {})
: ExpressionAnalyzer(query_, syntax_analyzer_result_, context_, options_.subquery_depth, do_global_)
const SelectQueryOptions & options_ = {},
SubqueriesForSets subqueries_for_sets_ = {})
: ExpressionAnalyzer(query_, syntax_analyzer_result_, context_, options_.subquery_depth, do_global_, std::move(subqueries_for_sets_))
, metadata_snapshot(metadata_snapshot_)
, required_result_columns(required_result_columns_)
, query_options(options_)

View File

@ -304,6 +304,8 @@ InterpreterSelectQuery::InterpreterSelectQuery(
if (storage)
view = dynamic_cast<StorageView *>(storage.get());
SubqueriesForSets subquery_for_sets;
auto analyze = [&] (bool try_move_to_prewhere)
{
/// Allow push down and other optimizations for VIEW: replace with subquery and rewrite it.
@ -344,7 +346,7 @@ InterpreterSelectQuery::InterpreterSelectQuery(
query_analyzer = std::make_unique<SelectQueryExpressionAnalyzer>(
query_ptr, syntax_analyzer_result, *context, metadata_snapshot,
NameSet(required_result_column_names.begin(), required_result_column_names.end()),
!options.only_analyze, options);
!options.only_analyze, options, std::move(subquery_for_sets));
if (!options.only_analyze)
{
@ -430,6 +432,7 @@ InterpreterSelectQuery::InterpreterSelectQuery(
if (need_analyze_again)
{
subquery_for_sets = std::move(query_analyzer->getSubqueriesForSets());
/// Do not try move conditions to PREWHERE for the second time.
/// Otherwise, we won't be able to fallback from inefficient PREWHERE to WHERE later.
analyze(/* try_move_to_prewhere = */ false);

View File

@ -124,6 +124,8 @@ ASTPtr ASTColumns::clone() const
res->set(res->indices, indices->clone());
if (constraints)
res->set(res->constraints, constraints->clone());
if (primary_key)
res->set(res->primary_key, primary_key->clone());
return res;
}

View File

@ -41,6 +41,7 @@ public:
ASTExpressionList * columns = nullptr;
ASTExpressionList * indices = nullptr;
ASTExpressionList * constraints = nullptr;
IAST * primary_key = nullptr;
String getID(char) const override { return "Columns definition"; }

View File

@ -19,6 +19,11 @@
namespace DB
{
namespace ErrorCodes
{
extern const int BAD_ARGUMENTS;
}
bool ParserNestedTable::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
{
ParserToken open(TokenType::OpeningRoundBracket);
@ -150,10 +155,12 @@ bool ParserTablePropertyDeclaration::parseImpl(Pos & pos, ASTPtr & node, Expecte
{
ParserKeyword s_index("INDEX");
ParserKeyword s_constraint("CONSTRAINT");
ParserKeyword s_primary_key("PRIMARY KEY");
ParserIndexDeclaration index_p;
ParserConstraintDeclaration constraint_p;
ParserColumnDeclaration column_p{true, true};
ParserExpression primary_key_p;
ASTPtr new_node = nullptr;
@ -167,6 +174,11 @@ bool ParserTablePropertyDeclaration::parseImpl(Pos & pos, ASTPtr & node, Expecte
if (!constraint_p.parse(pos, new_node, expected))
return false;
}
else if (s_primary_key.ignore(pos, expected))
{
if (!primary_key_p.parse(pos, new_node, expected))
return false;
}
else
{
if (!column_p.parse(pos, new_node, expected))
@ -201,6 +213,7 @@ bool ParserTablePropertiesDeclarationList::parseImpl(Pos & pos, ASTPtr & node, E
ASTPtr columns = std::make_shared<ASTExpressionList>();
ASTPtr indices = std::make_shared<ASTExpressionList>();
ASTPtr constraints = std::make_shared<ASTExpressionList>();
ASTPtr primary_key;
for (const auto & elem : list->children)
{
@ -210,6 +223,14 @@ bool ParserTablePropertiesDeclarationList::parseImpl(Pos & pos, ASTPtr & node, E
indices->children.push_back(elem);
else if (elem->as<ASTConstraintDeclaration>())
constraints->children.push_back(elem);
else if (elem->as<ASTIdentifier>() || elem->as<ASTFunction>())
{
if (primary_key)
{
throw Exception("Multiple primary keys are not allowed.", ErrorCodes::BAD_ARGUMENTS);
}
primary_key = elem;
}
else
return false;
}
@ -222,6 +243,8 @@ bool ParserTablePropertiesDeclarationList::parseImpl(Pos & pos, ASTPtr & node, E
res->set(res->indices, indices);
if (!constraints->children.empty())
res->set(res->constraints, constraints);
if (primary_key)
res->set(res->primary_key, primary_key);
node = res;
@ -472,6 +495,15 @@ bool ParserCreateTableQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expe
query->set(query->columns_list, columns_list);
query->set(query->storage, storage);
if (query->storage && query->columns_list && query->columns_list->primary_key)
{
if (query->storage->primary_key)
{
throw Exception("Multiple primary keys are not allowed.", ErrorCodes::BAD_ARGUMENTS);
}
query->storage->primary_key = query->columns_list->primary_key;
}
tryGetIdentifierNameInto(as_database, query->as_database);
tryGetIdentifierNameInto(as_table, query->as_table);
query->set(query->select, select);

View File

@ -391,6 +391,7 @@ protected:
* ...
* INDEX name1 expr TYPE type1(args) GRANULARITY value,
* ...
* PRIMARY KEY expr
* ) ENGINE = engine
*
* Or:

View File

@ -8,6 +8,7 @@
#include "ReplicasStatusHandler.h"
#include "InterserverIOHTTPHandler.h"
#include "PrometheusRequestHandler.h"
#include "WebUIRequestHandler.h"
namespace DB
@ -78,7 +79,9 @@ static inline auto createHandlersFactoryFromConfig(
for (const auto & key : keys)
{
if (key == "defaults")
{
addDefaultHandlersFactory(*main_handler_factory, server, async_metrics);
}
else if (startsWith(key, "rule"))
{
const auto & handler_type = server.config().getString(prefix + "." + key + ".handler.type", "");
@ -112,7 +115,9 @@ static inline auto createHandlersFactoryFromConfig(
static inline Poco::Net::HTTPRequestHandlerFactory * createHTTPHandlerFactory(IServer & server, const std::string & name, AsynchronousMetrics & async_metrics)
{
if (server.config().has("http_handlers"))
{
return createHandlersFactoryFromConfig(server, name, "http_handlers", async_metrics);
}
else
{
auto factory = std::make_unique<HTTPRequestHandlerFactoryMain>(name);
@ -168,6 +173,10 @@ void addCommonDefaultHandlersFactory(HTTPRequestHandlerFactoryMain & factory, IS
auto replicas_status_handler = std::make_unique<HandlingRuleHTTPHandlerFactory<ReplicasStatusHandler>>(server);
replicas_status_handler->attachNonStrictPath("/replicas_status")->allowGetAndHeadRequest();
factory.addHandler(replicas_status_handler.release());
auto web_ui_handler = std::make_unique<HandlingRuleHTTPHandlerFactory<WebUIRequestHandler>>(server, "play.html");
web_ui_handler->attachNonStrictPath("/play")->allowGetAndHeadRequest();
factory.addHandler(web_ui_handler.release());
}
void addDefaultHandlersFactory(HTTPRequestHandlerFactoryMain & factory, IServer & server, AsynchronousMetrics & async_metrics)

View File

@ -1,4 +1,5 @@
#include "StaticRequestHandler.h"
#include "IServer.h"
#include "HTTPHandlerFactory.h"
#include "HTTPHandlerRequestFilter.h"
@ -17,6 +18,8 @@
#include <Poco/Net/HTTPServerRequest.h>
#include <Poco/Net/HTTPServerResponse.h>
#include <Poco/Net/HTTPRequestHandlerFactory.h>
#include <Poco/Util/LayeredConfiguration.h>
namespace DB
{

View File

@ -1,16 +1,15 @@
#pragma once
#include "IServer.h"
#include <Poco/Net/HTTPRequestHandler.h>
#include <Common/StringUtils/StringUtils.h>
#include <common/types.h>
#include <IO/WriteBuffer.h>
namespace DB
{
class IServer;
class WriteBuffer;
/// Response with custom string. Can be used for browser.
class StaticRequestHandler : public Poco::Net::HTTPRequestHandler
{
@ -22,7 +21,11 @@ private:
String response_expression;
public:
StaticRequestHandler(IServer & server, const String & expression, int status_ = 200, const String & content_type_ = "text/html; charset=UTF-8");
StaticRequestHandler(
IServer & server,
const String & expression,
int status_ = 200,
const String & content_type_ = "text/html; charset=UTF-8");
void writeResponse(WriteBuffer & out);

View File

@ -0,0 +1,35 @@
#include "WebUIRequestHandler.h"
#include "IServer.h"
#include <Poco/Net/HTTPServerRequest.h>
#include <Poco/Net/HTTPServerResponse.h>
#include <Poco/Util/LayeredConfiguration.h>
#include <IO/HTTPCommon.h>
#include <common/getResource.h>
namespace DB
{
WebUIRequestHandler::WebUIRequestHandler(IServer & server_, std::string resource_name_)
: server(server_), resource_name(std::move(resource_name_))
{
}
void WebUIRequestHandler::handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response)
{
auto keep_alive_timeout = server.config().getUInt("keep_alive_timeout", 10);
response.setContentType("text/html; charset=UTF-8");
if (request.getVersion() == Poco::Net::HTTPServerRequest::HTTP_1_1)
response.setChunkedTransferEncoding(true);
setResponseDefaultHeaders(response, keep_alive_timeout);
response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_OK);
response.send() << getResource(resource_name);
}
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <Poco/Net/HTTPRequestHandler.h>
namespace DB
{
class IServer;
/// Response with HTML page that allows to send queries and show results in browser.
class WebUIRequestHandler : public Poco::Net::HTTPRequestHandler
{
private:
IServer & server;
std::string resource_name;
public:
WebUIRequestHandler(IServer & server_, std::string resource_name_);
void handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response) override;
};
}

View File

@ -22,6 +22,7 @@ SRCS(
ReplicasStatusHandler.cpp
StaticRequestHandler.cpp
TCPHandler.cpp
WebUIRequestHandler.cpp
)

View File

@ -943,21 +943,26 @@ void IMergeTreeDataPart::makeCloneInDetached(const String & prefix, const Storag
volume->getDisk()->removeIfExists(destination_path + "/" + DELETE_ON_DESTROY_MARKER_FILE_NAME);
}
void IMergeTreeDataPart::makeCloneOnDiskDetached(const ReservationPtr & reservation) const
void IMergeTreeDataPart::makeCloneOnDisk(const DiskPtr & disk, const String & directory_name) const
{
assertOnDisk();
auto reserved_disk = reservation->getDisk();
if (reserved_disk->getName() == volume->getDisk()->getName())
if (disk->getName() == volume->getDisk()->getName())
throw Exception("Can not clone data part " + name + " to same disk " + volume->getDisk()->getName(), ErrorCodes::LOGICAL_ERROR);
if (directory_name.empty())
throw Exception("Can not clone data part " + name + " to empty directory.", ErrorCodes::LOGICAL_ERROR);
String path_to_clone = storage.relative_data_path + "detached/";
String path_to_clone = storage.relative_data_path + directory_name + '/';
if (reserved_disk->exists(path_to_clone + relative_path))
throw Exception("Path " + fullPath(reserved_disk, path_to_clone + relative_path) + " already exists. Can not clone ", ErrorCodes::DIRECTORY_ALREADY_EXISTS);
reserved_disk->createDirectory(path_to_clone);
if (disk->exists(path_to_clone + relative_path))
{
LOG_WARNING(storage.log, "Path " + fullPath(disk, path_to_clone + relative_path) + " already exists. Will remove it and clone again.");
disk->removeRecursive(path_to_clone + relative_path + '/');
}
disk->createDirectories(path_to_clone);
volume->getDisk()->copy(getFullRelativePath(), reserved_disk, path_to_clone);
volume->getDisk()->removeIfExists(path_to_clone + "/" + DELETE_ON_DESTROY_MARKER_FILE_NAME);
volume->getDisk()->copy(getFullRelativePath(), disk, path_to_clone);
volume->getDisk()->removeIfExists(path_to_clone + '/' + DELETE_ON_DESTROY_MARKER_FILE_NAME);
}
void IMergeTreeDataPart::checkConsistencyBase() const

View File

@ -316,8 +316,8 @@ public:
/// Makes clone of a part in detached/ directory via hard links
virtual void makeCloneInDetached(const String & prefix, const StorageMetadataPtr & metadata_snapshot) const;
/// Makes full clone of part in detached/ on another disk
void makeCloneOnDiskDetached(const ReservationPtr & reservation) const;
/// Makes full clone of part in specified subdirectory (relative to storage data directory, e.g. "detached") on another disk
void makeCloneOnDisk(const DiskPtr & disk, const String & directory_name) const;
/// Checks that .bin and .mrk files exist.
///

View File

@ -195,11 +195,13 @@ MergeTreeData::DataPartPtr MergeTreePartsMover::clonePart(const MergeTreeMoveEnt
throw Exception("Cancelled moving parts.", ErrorCodes::ABORTED);
LOG_TRACE(log, "Cloning part {}", moving_part.part->name);
moving_part.part->makeCloneOnDiskDetached(moving_part.reserved_space);
const String directory_to_move = "moving";
moving_part.part->makeCloneOnDisk(moving_part.reserved_space->getDisk(), directory_to_move);
auto single_disk_volume = std::make_shared<SingleDiskVolume>("volume_" + moving_part.part->name, moving_part.reserved_space->getDisk());
MergeTreeData::MutableDataPartPtr cloned_part =
data->createPart(moving_part.part->name, single_disk_volume, "detached/" + moving_part.part->name);
data->createPart(moving_part.part->name, single_disk_volume, directory_to_move + '/' + moving_part.part->name);
LOG_TRACE(log, "Part {} was cloned to {}", moving_part.part->name, cloned_part->getFullPath());
cloned_part->loadColumnsChecksumsIndexes(true, true);

View File

@ -41,6 +41,7 @@ namespace ErrorCodes
extern const int INCORRECT_DATA;
extern const int CANNOT_ASSIGN_OPTIMIZE;
extern const int TIMEOUT_EXCEEDED;
extern const int UNKNOWN_POLICY;
}
namespace ActionLocks
@ -1331,7 +1332,7 @@ void StorageMergeTree::movePartitionToTable(const StoragePtr & dest_table, const
throw Exception("Destination table " + dest_table_storage->getStorageID().getNameForLogs() +
" should have the same storage policy of source table " + getStorageID().getNameForLogs() + ". " +
getStorageID().getNameForLogs() + ": " + this->getStoragePolicy()->getName() + ", " +
dest_table_storage->getStorageID().getNameForLogs() + ": " + dest_table_storage->getStoragePolicy()->getName(), ErrorCodes::LOGICAL_ERROR);
dest_table_storage->getStorageID().getNameForLogs() + ": " + dest_table_storage->getStoragePolicy()->getName(), ErrorCodes::UNKNOWN_POLICY);
auto dest_metadata_snapshot = dest_table->getInMemoryMetadataPtr();
auto metadata_snapshot = getInMemoryMetadataPtr();

View File

@ -114,6 +114,7 @@ namespace ErrorCodes
extern const int CANNOT_ASSIGN_ALTER;
extern const int DIRECTORY_ALREADY_EXISTS;
extern const int ILLEGAL_TYPE_OF_ARGUMENT;
extern const int UNKNOWN_POLICY;
}
namespace ActionLocks
@ -833,7 +834,7 @@ void StorageReplicatedMergeTree::checkTableStructure(const String & zookeeper_pr
}
void StorageReplicatedMergeTree::setTableStructure(
ColumnsDescription new_columns, const ReplicatedMergeTreeTableMetadata::Diff & metadata_diff)
ColumnsDescription new_columns, const ReplicatedMergeTreeTableMetadata::Diff & metadata_diff)
{
StorageInMemoryMetadata new_metadata = getInMemoryMetadata();
StorageInMemoryMetadata old_metadata = getInMemoryMetadata();
@ -856,7 +857,7 @@ ColumnsDescription new_columns, const ReplicatedMergeTreeTableMetadata::Diff & m
if (!metadata_diff.empty())
{
auto parse_key_expr = [](const String & key_expr)
auto parse_key_expr = [] (const String & key_expr)
{
ParserNotEmptyExpressionList parser(false);
auto new_sorting_key_expr_list = parseQuery(parser, key_expr, 0, DBMS_DEFAULT_MAX_PARSER_DEPTH);
@ -3936,13 +3937,19 @@ void StorageReplicatedMergeTree::alter(
ReplicatedMergeTreeTableMetadata future_metadata_in_zk(*this, current_metadata);
if (ast_to_str(future_metadata.sorting_key.definition_ast) != ast_to_str(current_metadata->sorting_key.definition_ast))
future_metadata_in_zk.sorting_key = serializeAST(*future_metadata.sorting_key.expression_list_ast);
{
/// We serialize definition_ast as list, because code which apply ALTER (setTableStructure) expect serialized non empty expression
/// list here and we cannot change this representation for compatibility. Also we have preparsed AST `sorting_key.expression_list_ast`
/// in KeyDescription, but it contain version column for VersionedCollapsingMergeTree, which shouldn't be defined as a part of key definition AST.
/// So the best compatible way is just to convert definition_ast to list and serialize it. In all other places key.expression_list_ast should be used.
future_metadata_in_zk.sorting_key = serializeAST(*extractKeyExpressionList(future_metadata.sorting_key.definition_ast));
}
if (ast_to_str(future_metadata.sampling_key.definition_ast) != ast_to_str(current_metadata->sampling_key.definition_ast))
future_metadata_in_zk.sampling_expression = serializeAST(*future_metadata.sampling_key.expression_list_ast);
future_metadata_in_zk.sampling_expression = serializeAST(*extractKeyExpressionList(future_metadata.sampling_key.definition_ast));
if (ast_to_str(future_metadata.partition_key.definition_ast) != ast_to_str(current_metadata->partition_key.definition_ast))
future_metadata_in_zk.partition_key = serializeAST(*future_metadata.partition_key.expression_list_ast);
future_metadata_in_zk.partition_key = serializeAST(*extractKeyExpressionList(future_metadata.partition_key.definition_ast));
if (ast_to_str(future_metadata.table_ttl.definition_ast) != ast_to_str(current_metadata->table_ttl.definition_ast))
{
@ -5604,7 +5611,7 @@ void StorageReplicatedMergeTree::movePartitionToTable(const StoragePtr & dest_ta
throw Exception("Destination table " + dest_table_storage->getStorageID().getNameForLogs() +
" should have the same storage policy of source table " + getStorageID().getNameForLogs() + ". " +
getStorageID().getNameForLogs() + ": " + this->getStoragePolicy()->getName() + ", " +
getStorageID().getNameForLogs() + ": " + dest_table_storage->getStoragePolicy()->getName(), ErrorCodes::LOGICAL_ERROR);
getStorageID().getNameForLogs() + ": " + dest_table_storage->getStoragePolicy()->getName(), ErrorCodes::UNKNOWN_POLICY);
auto dest_metadata_snapshot = dest_table->getInMemoryMetadataPtr();
auto metadata_snapshot = getInMemoryMetadataPtr();

View File

@ -353,20 +353,22 @@ def run_tests_array(all_tests_with_params):
if os.path.isfile(stdout_file):
print(", result:\n")
print(open(stdout_file).read())
print('\n'.join(open(stdout_file).read().split('\n')[:100]))
elif stderr:
failures += 1
failures_chain += 1
print(MSG_FAIL, end='')
print_test_time(total_time)
print(" - having stderror:\n{}".format(stderr))
print(" - having stderror:\n{}".format(
'\n'.join(stderr.split('\n')[:100])))
elif 'Exception' in stdout:
failures += 1
failures_chain += 1
print(MSG_FAIL, end='')
print_test_time(total_time)
print(" - having exception:\n{}".format(stdout))
print(" - having exception:\n{}".format(
'\n'.join(stdout.split('\n')[:100])))
elif not os.path.isfile(reference_file):
print(MSG_UNKNOWN, end='')
print_test_time(total_time)

View File

@ -0,0 +1 @@
#!/usr/bin/env python3

View File

@ -0,0 +1,28 @@
<yandex>
<remote_servers>
<test_cluster_mixed>
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>node1</host>
<port>9000</port>
</replica>
<replica>
<host>node2</host>
<port>9000</port>
</replica>
</shard>
<shard>
<internal_replication>false</internal_replication>
<replica>
<host>node3</host>
<port>9000</port>
</replica>
<replica>
<host>node4</host>
<port>9000</port>
</replica>
</shard>
</test_cluster_mixed>
</remote_servers>
</yandex>

View File

@ -0,0 +1,93 @@
import pytest
from helpers.cluster import ClickHouseCluster
from helpers.test_tools import assert_eq_with_retry
cluster = ClickHouseCluster(__file__)
node1 = cluster.add_instance('node1', main_configs=['configs/remote_servers.xml'], with_zookeeper=True)
node2 = cluster.add_instance('node2', main_configs=['configs/remote_servers.xml'], with_zookeeper=True)
node3 = cluster.add_instance('node3', main_configs=['configs/remote_servers.xml'], with_zookeeper=True)
node4 = cluster.add_instance('node4', main_configs=['configs/remote_servers.xml'], with_zookeeper=True)
@pytest.fixture(scope="module")
def started_cluster():
try:
cluster.start()
for node in [node1, node2]:
node.query('''
CREATE TABLE test_table_replicated(date Date, id UInt32, value Int32)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/sometable', '{replica}') ORDER BY id;
'''.format(replica=node.name))
node.query('''CREATE TABLE test_table(date Date, id UInt32, value Int32) ENGINE=MergeTree ORDER BY id''')
for node in [node3, node4]:
node.query('''
CREATE TABLE test_table_replicated(date Date, id UInt32, value Int32)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/1/someotable', '{replica}') ORDER BY id;
'''.format(replica=node.name))
node.query('''CREATE TABLE test_table(date Date, id UInt32, value Int32) ENGINE=MergeTree ORDER BY id''')
yield cluster
finally:
cluster.shutdown()
def test_alter_on_cluter_non_replicated(started_cluster):
for node in [node1, node2, node3, node4]:
node.query("INSERT INTO test_table VALUES(toDate('2019-10-01'), 1, 1)")
assert node1.query("SELECT COUNT() FROM test_table") == "1\n"
assert node2.query("SELECT COUNT() FROM test_table") == "1\n"
assert node3.query("SELECT COUNT() FROM test_table") == "1\n"
assert node4.query("SELECT COUNT() FROM test_table") == "1\n"
node1.query("ALTER TABLE test_table ON CLUSTER 'test_cluster_mixed' MODIFY COLUMN date DateTime")
assert node1.query("SELECT date FROM test_table") == '2019-10-01 00:00:00\n'
assert node2.query("SELECT date FROM test_table") == '2019-10-01 00:00:00\n'
assert node3.query("SELECT date FROM test_table") == '2019-10-01 00:00:00\n'
assert node4.query("SELECT date FROM test_table") == '2019-10-01 00:00:00\n'
node3.query("ALTER TABLE test_table ON CLUSTER 'test_cluster_mixed' MODIFY COLUMN value String")
for node in [node1, node2, node3, node4]:
node.query("INSERT INTO test_table VALUES(toDateTime('2019-10-02 00:00:00'), 2, 'Hello')")
assert node1.query("SELECT COUNT() FROM test_table") == "2\n"
assert node2.query("SELECT COUNT() FROM test_table") == "2\n"
assert node3.query("SELECT COUNT() FROM test_table") == "2\n"
assert node4.query("SELECT COUNT() FROM test_table") == "2\n"
def test_alter_replicated_on_cluster(started_cluster):
for node in [node1, node3]:
node.query("INSERT INTO test_table_replicated VALUES(toDate('2019-10-01'), 1, 1)")
for node in [node2, node4]:
node.query("SYSTEM SYNC REPLICA test_table_replicated", timeout=20)
node1.query("ALTER TABLE test_table_replicated ON CLUSTER 'test_cluster_mixed' MODIFY COLUMN date DateTime", settings={"replication_alter_partitions_sync": "2"})
assert node1.query("SELECT date FROM test_table_replicated") == '2019-10-01 00:00:00\n'
assert node2.query("SELECT date FROM test_table_replicated") == '2019-10-01 00:00:00\n'
assert node3.query("SELECT date FROM test_table_replicated") == '2019-10-01 00:00:00\n'
assert node4.query("SELECT date FROM test_table_replicated") == '2019-10-01 00:00:00\n'
node3.query("ALTER TABLE test_table_replicated ON CLUSTER 'test_cluster_mixed' MODIFY COLUMN value String", settings={"replication_alter_partitions_sync": "2"})
for node in [node2, node4]:
node.query("INSERT INTO test_table_replicated VALUES(toDateTime('2019-10-02 00:00:00'), 2, 'Hello')")
for node in [node1, node3]:
node.query("SYSTEM SYNC REPLICA test_table_replicated", timeout=20)
assert node1.query("SELECT COUNT() FROM test_table_replicated") == "2\n"
assert node2.query("SELECT COUNT() FROM test_table_replicated") == "2\n"
assert node3.query("SELECT COUNT() FROM test_table_replicated") == "2\n"
assert node4.query("SELECT COUNT() FROM test_table_replicated") == "2\n"

View File

@ -0,0 +1,4 @@
<yandex>
<background_move_processing_pool_thread_sleep_seconds>0.5</background_move_processing_pool_thread_sleep_seconds>
<background_move_processing_pool_task_sleep_seconds_when_no_work_max>0.5</background_move_processing_pool_task_sleep_seconds_when_no_work_max>
</yandex>

View File

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<yandex>
<part_log>
<database>system</database>
<table>part_log</table>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
</part_log>
</yandex>

View File

@ -12,6 +12,7 @@
<!-- Avoid extra retries to speed up tests -->
<retry_attempts>0</retry_attempts>
</s3>
<default/>
</disks>
<policies>
<s3>
@ -21,6 +22,16 @@
</main>
</volumes>
</s3>
<s3_cold>
<volumes>
<main>
<disk>default</disk>
</main>
<external>
<disk>s3</disk>
</external>
</volumes>
</s3_cold>
</policies>
</storage_configuration>
</yandex>

View File

@ -45,7 +45,10 @@ def cluster():
try:
cluster = ClickHouseCluster(__file__)
cluster.add_instance("node",
main_configs=["configs/config.d/log_conf.xml", "configs/config.d/storage_conf.xml"],
main_configs=["configs/config.d/log_conf.xml",
"configs/config.d/storage_conf.xml",
"configs/config.d/instant_moves.xml",
"configs/config.d/part_log.xml"],
with_minio=True)
logging.info("Starting cluster...")
cluster.start()
@ -115,3 +118,60 @@ def test_write_failover(cluster, min_bytes_for_wide_part, request_count):
assert node.query("CHECK TABLE s3_failover_test") == '1\n'
assert node.query("SELECT * FROM s3_failover_test FORMAT Values") == data
# Check that second data part move is ended successfully if first attempt was failed.
def test_move_failover(cluster):
node = cluster.instances["node"]
node.query(
"""
CREATE TABLE s3_failover_test (
dt DateTime,
id Int64,
data String
) ENGINE=MergeTree()
ORDER BY id
TTL dt + INTERVAL 3 SECOND TO VOLUME 'external'
SETTINGS storage_policy='s3_cold'
"""
)
# Fail a request to S3 to break first TTL move.
fail_request(cluster, 1)
node.query("INSERT INTO s3_failover_test VALUES (now() - 2, 0, 'data'), (now() - 2, 1, 'data')")
# Wait for part move to S3.
max_attempts = 10
for attempt in range(max_attempts + 1):
disk = node.query("SELECT disk_name FROM system.parts WHERE table='s3_failover_test' LIMIT 1")
if disk != "s3\n":
if attempt == max_attempts:
assert disk == "s3\n", "Expected move to S3 while part still on disk " + disk
else:
time.sleep(1)
else:
break
# Ensure part_log is created.
node.query("SYSTEM FLUSH LOGS")
# There should be 2 attempts to move part.
assert node.query("""
SELECT count(*) FROM system.part_log
WHERE event_type='MovePart' AND table='s3_failover_test'
""") == '2\n'
# First attempt should be failed with expected error.
exception = node.query("""
SELECT exception FROM system.part_log
WHERE event_type='MovePart' AND table='s3_failover_test' AND notEmpty(exception)
ORDER BY event_time
LIMIT 1
""")
assert exception.find("Expected Error") != -1, exception
# Ensure data is not corrupted.
assert node.query("CHECK TABLE s3_failover_test") == '1\n'
assert node.query("SELECT id,data FROM s3_failover_test FORMAT Values") == "(0,'data'),(1,'data')"

View File

@ -1,8 +1,2 @@
test_cluster_two_shards
test_cluster_two_shards_different_databases
test_cluster_two_shards_localhost
test_shard_localhost
test_shard_localhost_secure
test_unavailable_shard
test_cluster_two_shards
test_shard_localhost 1 1 1 localhost ::1 9000 1 default 0 0

View File

@ -1,3 +1,3 @@
show clusters;
show clusters like 'test%' limit 1;
-- don't show all clusters to reduce dependency on the configuration of server
show clusters like 'test_shard%' limit 1;
show cluster 'test_shard_localhost';

View File

@ -0,0 +1,6 @@
1
1
1 1
1 1
2 2
7 14

View File

@ -0,0 +1,53 @@
DROP DATABASE IF EXISTS test_01516;
CREATE DATABASE test_01516 ENGINE=Ordinary; -- Full ATTACH requires UUID with Atomic
USE test_01516;
DROP TABLE IF EXISTS primary_key_test;
CREATE TABLE primary_key_test(v Int32, PRIMARY KEY(v)) ENGINE=ReplacingMergeTree ORDER BY v;
INSERT INTO primary_key_test VALUES (1), (1), (1);
DETACH TABLE primary_key_test;
ATTACH TABLE primary_key_test(v Int32, PRIMARY KEY(v)) ENGINE=ReplacingMergeTree ORDER BY v;
SELECT * FROM primary_key_test FINAL;
DROP TABLE primary_key_test;
CREATE TABLE primary_key_test(v Int32) ENGINE=ReplacingMergeTree ORDER BY v PRIMARY KEY(v);
INSERT INTO primary_key_test VALUES (1), (1), (1);
DETACH TABLE primary_key_test;
ATTACH TABLE primary_key_test(v Int32) ENGINE=ReplacingMergeTree ORDER BY v PRIMARY KEY(v);
SELECT * FROM primary_key_test FINAL;
DROP TABLE primary_key_test;
CREATE TABLE primary_key_test(v Int32, PRIMARY KEY(v), PRIMARY KEY(v)) ENGINE=ReplacingMergeTree ORDER BY v; -- { clientError 36; }
CREATE TABLE primary_key_test(v Int32, PRIMARY KEY(v)) ENGINE=ReplacingMergeTree ORDER BY v PRIMARY KEY(v); -- { clientError 36; }
CREATE TABLE primary_key_test(v1 Int32, v2 Int32, PRIMARY KEY(v1, v2)) ENGINE=ReplacingMergeTree ORDER BY (v1, v2);
INSERT INTO primary_key_test VALUES (1, 1), (1, 1), (1, 1);
DETACH TABLE primary_key_test;
ATTACH TABLE primary_key_test(v1 Int32, v2 Int32, PRIMARY KEY(v1, v2)) ENGINE=ReplacingMergeTree ORDER BY (v1, v2);
SELECT * FROM primary_key_test FINAL;
DROP TABLE primary_key_test;
CREATE TABLE primary_key_test(v1 Int32, v2 Int32) ENGINE=ReplacingMergeTree ORDER BY (v1, v2) PRIMARY KEY(v1, v2);
INSERT INTO primary_key_test VALUES (1, 1), (1, 1), (1, 1);
DETACH TABLE primary_key_test;
ATTACH TABLE primary_key_test(v1 Int32, v2 Int32) ENGINE=ReplacingMergeTree ORDER BY (v1, v2) PRIMARY KEY(v1, v2);
SELECT * FROM primary_key_test FINAL;
DROP TABLE primary_key_test;
CREATE TABLE primary_key_test(v1 Int32, v2 Int32, PRIMARY KEY(v1, v2), PRIMARY KEY(v1, v2)) ENGINE=ReplacingMergeTree ORDER BY (v1, v2); -- { clientError 36; }
CREATE TABLE primary_key_test(v1 Int32, v2 Int32, PRIMARY KEY(v1, v2)) ENGINE=ReplacingMergeTree ORDER BY (v1, v2) PRIMARY KEY(v1, v2); -- { clientError 36; }
CREATE TABLE primary_key_test(v1 Int64, v2 Int32, v3 String, PRIMARY KEY(v1, gcd(v1, v2))) ENGINE=ReplacingMergeTree ORDER BY v1; -- { serverError 36; }
CREATE TABLE primary_key_test(v1 Int64, v2 Int32, v3 String, PRIMARY KEY(v1, gcd(v1, v2))) ENGINE=ReplacingMergeTree ORDER BY (v1, gcd(v1, v2));
INSERT INTO primary_key_test VALUES(7, 14, 'hello'), (2, 2, 'world'), (7, 14, 'duplicate');
SELECT v1, v2 FROM primary_key_test FINAL ORDER BY v1, v2;
DROP TABLE primary_key_test;
DROP DATABASE test_01516;

View File

@ -0,0 +1 @@
all tests passed

View File

@ -0,0 +1,7 @@
SELECT database FROM system.tables WHERE database LIKE '%' format Null;
SELECT database AS db FROM system.tables WHERE db LIKE '%' format Null;
SELECT CAST(database, 'String') AS db FROM system.tables WHERE db LIKE '%' format Null;
SELECT CAST('a string', 'Nullable(String)') AS str WHERE str LIKE '%' format Null;
SELECT CAST(database, 'Nullable(String)') AS ndb FROM system.tables WHERE ndb LIKE '%' format Null;
SELECT 'all tests passed';

View File

@ -0,0 +1,14 @@
DROP TABLE IF EXISTS logs;
CREATE TABLE logs(
date_visited DateTime,
date Date MATERIALIZED toDate(date_visited)
) ENGINE = MergeTree() ORDER BY tuple();
SELECT count() FROM logs AS plogs WHERE plogs.date = '2019-11-20';
INSERT INTO logs VALUES('2019-11-20 00:00:00');
SELECT count() FROM logs AS plogs WHERE plogs.date = '2019-11-20';
DROP TABLE logs;

View File

@ -0,0 +1,5 @@
1 0 \N \N \N \N \N
1 0 \N \N \N \N \N
--- empty resultset ---
0 0 \N \N \N \N \N
0 0 \N \N \N \N \N

View File

@ -0,0 +1,17 @@
select count(), count(a), max(a), min(a), avg(a), sum(a), any(a)
from (select cast(Null,'Nullable(Float64)') a);
select countMerge(cnts), countMerge(cntsa), maxMerge(maxs), minMerge(mins), avgMerge(avgs), sumMerge(sums), anyMerge(anys) from (
select countState() cnts, countState(a) cntsa, maxState(a) maxs, minState(a) mins, avgState(a) avgs, sumState(a) sums, anyState(a) anys
from (select cast(Null,'Nullable(Float64)') a));
select '--- empty resultset ---';
select count(), count(a), max(a), min(a), avg(a), sum(a), any(a)
from (select cast(1,'Nullable(Float64)') a) where a =0;
select countMerge(cnts), countMerge(cntsa), maxMerge(maxs), minMerge(mins), avgMerge(avgs), sumMerge(sums), anyMerge(anys) from (
select countState() cnts, countState(a) cntsa, maxState(a) maxs, minState(a) mins, avgState(a) avgs, sumState(a) sums, anyState(a) anys
from (select cast(1,'Nullable(Float64)') a) where a =0 );

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,411 @@
DROP TABLE IF EXISTS testNullableStates;
DROP TABLE IF EXISTS testNullableStatesAgg;
CREATE TABLE testNullableStates (
ts DateTime,
id String,
string Nullable(String),
float64 Nullable(Float64),
float32 Nullable(Float32),
decimal325 Nullable(Decimal32(5)),
date Nullable(Date),
datetime Nullable(DateTime),
datetime64 Nullable(DateTime64),
int64 Nullable(Int64),
int32 Nullable(Int32),
int16 Nullable(Int16),
int8 Nullable(Int8))
ENGINE=MergeTree PARTITION BY toStartOfDay(ts) ORDER BY id;
INSERT INTO testNullableStates SELECT
toDateTime('2020-01-01 00:00:00') + number AS ts,
toString(number % 999) AS id,
toString(number) AS string,
number / 333 AS float64,
number / 333 AS float32,
number / 333 AS decimal325,
toDate(ts),
ts,
ts,
number,
toInt32(number),
toInt16(number),
toInt8(number)
FROM numbers(100000);
INSERT INTO testNullableStates SELECT
toDateTime('2020-01-01 00:00:00') + number AS ts,
toString(number % 999 - 5) AS id,
NULL AS string,
NULL AS float64,
NULL AS float32,
NULL AS decimal325,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
FROM numbers(500);
CREATE TABLE testNullableStatesAgg
(
`ts` DateTime,
`id` String,
`stringMin` AggregateFunction(min, Nullable(String)),
`stringMax` AggregateFunction(max, Nullable(String)),
`float64Min` AggregateFunction(min, Nullable(Float64)),
`float64Max` AggregateFunction(max, Nullable(Float64)),
`float64Avg` AggregateFunction(avg, Nullable(Float64)),
`float64Sum` AggregateFunction(sum, Nullable(Float64)),
`float32Min` AggregateFunction(min, Nullable(Float32)),
`float32Max` AggregateFunction(max, Nullable(Float32)),
`float32Avg` AggregateFunction(avg, Nullable(Float32)),
`float32Sum` AggregateFunction(sum, Nullable(Float32)),
`decimal325Min` AggregateFunction(min, Nullable(Decimal32(5))),
`decimal325Max` AggregateFunction(max, Nullable(Decimal32(5))),
`decimal325Avg` AggregateFunction(avg, Nullable(Decimal32(5))),
`decimal325Sum` AggregateFunction(sum, Nullable(Decimal32(5))),
`dateMin` AggregateFunction(min, Nullable(Date)),
`dateMax` AggregateFunction(max, Nullable(Date)),
`datetimeMin` AggregateFunction(min, Nullable(DateTime)),
`datetimeMax` AggregateFunction(max, Nullable(DateTime)),
`datetime64Min` AggregateFunction(min, Nullable(datetime64)),
`datetime64Max` AggregateFunction(max, Nullable(datetime64)),
`int64Min` AggregateFunction(min, Nullable(Int64)),
`int64Max` AggregateFunction(max, Nullable(Int64)),
`int64Avg` AggregateFunction(avg, Nullable(Int64)),
`int64Sum` AggregateFunction(sum, Nullable(Int64)),
`int32Min` AggregateFunction(min, Nullable(Int32)),
`int32Max` AggregateFunction(max, Nullable(Int32)),
`int32Avg` AggregateFunction(avg, Nullable(Int32)),
`int32Sum` AggregateFunction(sum, Nullable(Int32)),
`int16Min` AggregateFunction(min, Nullable(Int16)),
`int16Max` AggregateFunction(max, Nullable(Int16)),
`int16Avg` AggregateFunction(avg, Nullable(Int16)),
`int16Sum` AggregateFunction(sum, Nullable(Int16)),
`int8Min` AggregateFunction(min, Nullable(Int8)),
`int8Max` AggregateFunction(max, Nullable(Int8)),
`int8Avg` AggregateFunction(avg, Nullable(Int8)),
`int8Sum` AggregateFunction(sum, Nullable(Int8))
)
ENGINE = AggregatingMergeTree()
PARTITION BY toStartOfDay(ts)
ORDER BY id;
insert into testNullableStatesAgg
select
ts DateTime,
id String,
minState(string) stringMin,
maxState(string) stringMax,
minState(float64) float64Min,
maxState(float64) float64Max,
avgState(float64) float64Avg,
sumState(float64) float64Sum,
minState(float32) float32Min,
maxState(float32) float32Max,
avgState(float32) float32Avg,
sumState(float32) float32Sum,
minState(decimal325) decimal325Min,
maxState(decimal325) decimal325Max,
avgState(decimal325) decimal325Avg,
sumState(decimal325) decimal325Sum,
minState(date) dateMin,
maxState(date) dateMax,
minState(datetime) datetimeMin,
maxState(datetime) datetimeMax,
minState(datetime64) datetime64Min,
maxState(datetime64) datetime64Max,
minState(int64) int64Min,
maxState(int64) int64Max,
avgState(int64) int64Avg,
sumState(int64) int64Sum,
minState(int32) int32Min,
maxState(int32) int32Max,
avgState(int32) int32Avg,
sumState(int32) int32Sum,
minState(int16) int16Min,
maxState(int16) int16Max,
avgState(int16) int16Avg,
sumState(int16) int16Sum,
minState(int8) int8Min,
maxState(int8) int8Max,
avgState(int8) int8Avg,
sumState(int8) int8Sum
from testNullableStates
group by ts, id;
OPTIMIZE TABLE testNullableStatesAgg FINAL;
select count() from testNullableStates;
select count() from testNullableStatesAgg;
select ' ---- select without states ---- ';
SELECT id, count(),
min(string),
max(string),
floor(min(float64),5),
floor(max(float64),5),
floor(avg(float64),5),
floor(sum(float64),5),
floor(min(float32),5),
floor(max(float32),5),
floor(avg(float32),5),
floor(sum(float32),5),
min(decimal325),
max(decimal325),
avg(decimal325),
sum(decimal325),
min(date),
max(date),
min(datetime),
max(datetime),
min(datetime64),
max(datetime64),
min(int64),
max(int64),
avg(int64),
sum(int64),
min(int32),
max(int32),
avg(int32),
sum(int32),
min(int16),
max(int16),
avg(int16),
sum(int16),
min(int8),
max(int8),
avg(int8),
sum(int8)
FROM testNullableStates
GROUP BY id
ORDER BY id ASC;
select ' ---- select with states ---- ';
SELECT id, count(),
minMerge(stringMin),
maxMerge(stringMax),
floor(minMerge(float64Min),5),
floor(maxMerge(float64Max),5),
floor(avgMerge(float64Avg),5),
floor(sumMerge(float64Sum),5),
floor(minMerge(float32Min),5),
floor(maxMerge(float32Max),5),
floor(avgMerge(float32Avg),5),
floor(sumMerge(float32Sum),5),
minMerge(decimal325Min),
maxMerge(decimal325Max),
avgMerge(decimal325Avg),
sumMerge(decimal325Sum),
minMerge(dateMin),
maxMerge(dateMax),
minMerge(datetimeMin),
maxMerge(datetimeMax),
minMerge(datetime64Min),
maxMerge(datetime64Max),
minMerge(int64Min),
maxMerge(int64Max),
avgMerge(int64Avg),
sumMerge(int64Sum),
minMerge(int32Min),
maxMerge(int32Max),
avgMerge(int32Avg),
sumMerge(int32Sum),
minMerge(int16Min),
maxMerge(int16Max),
avgMerge(int16Avg),
sumMerge(int16Sum),
minMerge(int8Min),
maxMerge(int8Max),
avgMerge(int8Avg),
sumMerge(int8Sum)
FROM testNullableStatesAgg
GROUP BY id
ORDER BY id ASC;
select ' ---- select row with nulls without states ---- ';
SELECT id, count(),
min(string),
max(string),
floor(min(float64),5),
floor(max(float64),5),
floor(avg(float64),5),
floor(sum(float64),5),
floor(min(float32),5),
floor(max(float32),5),
floor(avg(float32),5),
floor(sum(float32),5),
min(decimal325),
max(decimal325),
avg(decimal325),
sum(decimal325),
min(date),
max(date),
min(datetime),
max(datetime),
min(datetime64),
max(datetime64),
min(int64),
max(int64),
avg(int64),
sum(int64),
min(int32),
max(int32),
avg(int32),
sum(int32),
min(int16),
max(int16),
avg(int16),
sum(int16),
min(int8),
max(int8),
avg(int8),
sum(int8)
FROM testNullableStates
WHERE id = '-2'
GROUP BY id
ORDER BY id ASC;
select ' ---- select row with nulls with states ---- ';
SELECT id, count(),
minMerge(stringMin),
maxMerge(stringMax),
floor(minMerge(float64Min),5),
floor(maxMerge(float64Max),5),
floor(avgMerge(float64Avg),5),
floor(sumMerge(float64Sum),5),
floor(minMerge(float32Min),5),
floor(maxMerge(float32Max),5),
floor(avgMerge(float32Avg),5),
floor(sumMerge(float32Sum),5),
minMerge(decimal325Min),
maxMerge(decimal325Max),
avgMerge(decimal325Avg),
sumMerge(decimal325Sum),
minMerge(dateMin),
maxMerge(dateMax),
minMerge(datetimeMin),
maxMerge(datetimeMax),
minMerge(datetime64Min),
maxMerge(datetime64Max),
minMerge(int64Min),
maxMerge(int64Max),
avgMerge(int64Avg),
sumMerge(int64Sum),
minMerge(int32Min),
maxMerge(int32Max),
avgMerge(int32Avg),
sumMerge(int32Sum),
minMerge(int16Min),
maxMerge(int16Max),
avgMerge(int16Avg),
sumMerge(int16Sum),
minMerge(int8Min),
maxMerge(int8Max),
avgMerge(int8Avg),
sumMerge(int8Sum)
FROM testNullableStatesAgg
WHERE id = '-2'
GROUP BY id
ORDER BY id ASC;
select ' ---- select no rows without states ---- ';
SELECT count(),
min(string),
max(string),
floor(min(float64),5),
floor(max(float64),5),
floor(avg(float64),5),
floor(sum(float64),5),
floor(min(float32),5),
floor(max(float32),5),
floor(avg(float32),5),
floor(sum(float32),5),
min(decimal325),
max(decimal325),
avg(decimal325),
sum(decimal325),
min(date),
max(date),
min(datetime),
max(datetime),
min(datetime64),
max(datetime64),
min(int64),
max(int64),
avg(int64),
sum(int64),
min(int32),
max(int32),
avg(int32),
sum(int32),
min(int16),
max(int16),
avg(int16),
sum(int16),
min(int8),
max(int8),
avg(int8),
sum(int8)
FROM testNullableStates
WHERE id = '-22';
select ' ---- select no rows with states ---- ';
SELECT count(),
minMerge(stringMin),
maxMerge(stringMax),
floor(minMerge(float64Min),5),
floor(maxMerge(float64Max),5),
floor(avgMerge(float64Avg),5),
floor(sumMerge(float64Sum),5),
floor(minMerge(float32Min),5),
floor(maxMerge(float32Max),5),
floor(avgMerge(float32Avg),5),
floor(sumMerge(float32Sum),5),
minMerge(decimal325Min),
maxMerge(decimal325Max),
avgMerge(decimal325Avg),
sumMerge(decimal325Sum),
minMerge(dateMin),
maxMerge(dateMax),
minMerge(datetimeMin),
maxMerge(datetimeMax),
minMerge(datetime64Min),
maxMerge(datetime64Max),
minMerge(int64Min),
maxMerge(int64Max),
avgMerge(int64Avg),
sumMerge(int64Sum),
minMerge(int32Min),
maxMerge(int32Max),
avgMerge(int32Avg),
sumMerge(int32Sum),
minMerge(int16Min),
maxMerge(int16Max),
avgMerge(int16Avg),
sumMerge(int16Sum),
minMerge(int8Min),
maxMerge(int8Max),
avgMerge(int8Avg),
sumMerge(int8Sum)
FROM testNullableStatesAgg
WHERE id = '-22';
DROP TABLE testNullableStates;
DROP TABLE testNullableStatesAgg;

View File

@ -0,0 +1,80 @@
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1

View File

@ -0,0 +1,90 @@
DROP TABLE IF EXISTS t1;
CREATE TABLE t1 (`cA` String, `c1` String) ENGINE = MergeTree ORDER BY (cA, c1);
insert into t1 select 'AAAAAAAAAAA', 'BBBBBB';
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where c1 in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
select count() from t1 where cast(c1 as Nullable(String)) in (select 'BBBBBB' union all select null);
DROP TABLE t1;

View File

@ -0,0 +1,6 @@
30
30
10
10
3
3

View File

@ -0,0 +1,16 @@
CREATE TABLE IF NOT EXISTS topXtest(A Int64) ENGINE = Memory;
INSERT INTO topXtest SELECT number FROM numbers(100);
INSERT INTO topXtest SELECT number FROM numbers(30);
INSERT INTO topXtest SELECT number FROM numbers(10);
SELECT length(topK(30)(A)) FROM topXtest;
SELECT length(topK(30)(A)) FROM remote('localhost,127.0.0.1', currentDatabase(), topXtest);
SELECT length(topK(A)) FROM topXtest;
SELECT length(topK(A)) FROM remote('localhost,127.0.0.1', currentDatabase(), topXtest);
SELECT length(topK(3)(A)) FROM topXtest;
SELECT length(topK(3)(A)) FROM remote('localhost,127.0.0.1', currentDatabase(), topXtest);
DROP TABLE topXtest;

View File

@ -0,0 +1 @@
100000

View File

@ -0,0 +1,12 @@
drop table if exists xp;
drop table if exists xp_d;
create table xp(A Date, B Int64, S String) Engine=MergeTree partition by toYYYYMM(A) order by B;
insert into xp select '2020-01-01', number , '' from numbers(100000);
create table xp_d as xp Engine=Distributed(test_shard_localhost, currentDatabase(), xp);
select count() from xp_d prewhere toYYYYMM(A) global in (select toYYYYMM(min(A)) from xp_d) where B > -1;
drop table if exists xp;
drop table if exists xp_d;

View File

@ -0,0 +1,6 @@
2019-10-01 a 1 aa 1 1 1
2019-10-01 a 1 aa 1 1 1 0
CREATE TABLE default.table_for_alter\n(\n `d` Date,\n `a` String,\n `b` UInt8,\n `x` String,\n `y` Int8,\n `version` UInt64,\n `sign` Int8 DEFAULT 1,\n `order` UInt32\n)\nENGINE = ReplicatedVersionedCollapsingMergeTree(\'/clickhouse/tables/01526_alter_add/t1\', \'1\', sign, version)\nPARTITION BY y\nPRIMARY KEY d\nORDER BY (d, order)\nSETTINGS index_granularity = 8192
2019-10-01 a 1 aa 1 1 1 0 0
2019-10-02 b 2 bb 2 2 2 1 2
CREATE TABLE default.table_for_alter\n(\n `d` Date,\n `a` String,\n `b` UInt8,\n `x` String,\n `y` Int8,\n `version` UInt64,\n `sign` Int8 DEFAULT 1,\n `order` UInt32,\n `datum` UInt32\n)\nENGINE = ReplicatedVersionedCollapsingMergeTree(\'/clickhouse/tables/01526_alter_add/t1\', \'1\', sign, version)\nPARTITION BY y\nPRIMARY KEY d\nORDER BY (d, order, datum)\nSETTINGS index_granularity = 8192

View File

@ -0,0 +1,38 @@
DROP TABLE IF EXISTS table_for_alter;
SET replication_alter_partitions_sync = 2;
CREATE TABLE table_for_alter
(
`d` Date,
`a` String,
`b` UInt8,
`x` String,
`y` Int8,
`version` UInt64,
`sign` Int8 DEFAULT 1
)
ENGINE = ReplicatedVersionedCollapsingMergeTree('/clickhouse/tables/01526_alter_add/t1', '1', sign, version)
PARTITION BY y
ORDER BY d
SETTINGS index_granularity = 8192;
INSERT INTO table_for_alter VALUES(toDate('2019-10-01'), 'a', 1, 'aa', 1, 1, 1);
SELECT * FROM table_for_alter;
ALTER TABLE table_for_alter ADD COLUMN order UInt32, MODIFY ORDER BY (d, order);
SELECT * FROM table_for_alter;
SHOW CREATE TABLE table_for_alter;
ALTER TABLE table_for_alter ADD COLUMN datum UInt32, MODIFY ORDER BY (d, order, datum);
INSERT INTO table_for_alter VALUES(toDate('2019-10-02'), 'b', 2, 'bb', 2, 2, 2, 1, 2);
SELECT * FROM table_for_alter ORDER BY d;
SHOW CREATE TABLE table_for_alter;
DROP TABLE IF EXISTS table_for_alter;

View File

@ -0,0 +1 @@
ffffffff-ffff-ffff-ffff-ffffffffffff

View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
. "$CURDIR"/../shell_config.sh
${CLICKHOUSE_CLIENT} --param_p1='ffffffff-ffff-ffff-ffff-ffffffffffff' --query "SELECT {p1:UUID}"

View File

@ -0,0 +1 @@
SELECT arrayMap(x -> x * sum(x), range(10)); -- { serverError 47 }

View File

@ -0,0 +1 @@
🌞

View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
. "$CURDIR"/../shell_config.sh
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_PORT_HTTP_PROTO}://${CLICKHOUSE_HOST}:${CLICKHOUSE_PORT_HTTP}/play" | grep -o '🌞'

View File

@ -53,7 +53,7 @@ class ClickHouseNode(Node):
continue
assert False, "container is not healthy"
def restart(self, timeout=120, safe=True):
def restart(self, timeout=120, safe=True, wait_healthy=True):
"""Restart node.
"""
if safe:
@ -73,7 +73,8 @@ class ClickHouseNode(Node):
self.cluster.command(None, f'{self.cluster.docker_compose} restart {self.name}', timeout=timeout)
self.wait_healthy(timeout)
if wait_healthy:
self.wait_healthy(timeout)
def query(self, sql, message=None, exitcode=None, steps=True, no_checks=False,
raise_on_exception=False, step=By, settings=None, *args, **kwargs):
@ -295,12 +296,12 @@ class Cluster(object):
:param steps: don't break command into steps, default: True
"""
debug(f"command() {node}, {command}")
with By("executing command", description=command) if steps else NullStep():
with By("executing command", description=command, format_description=False) if steps else NullStep():
r = self.bash(node)(command, *args, **kwargs)
if exitcode is not None:
with Then(f"exitcode should be {exitcode}") if steps else NullStep():
with Then(f"exitcode should be {exitcode}", format_name=False) if steps else NullStep():
assert r.exitcode == exitcode, error(r.output)
if message is not None:
with Then(f"output should contain message", description=message) if steps else NullStep():
with Then(f"output should contain message", description=message, format_description=False) if steps else NullStep():
assert message in r.output, error(r.output)
return r

Some files were not shown because too many files have changed in this diff Show More