Merge branch 'master' into async-loader-integration

This commit is contained in:
serxa 2023-11-02 14:56:32 +00:00
commit e97edf5285
67 changed files with 1433 additions and 590 deletions

View File

@ -4,10 +4,10 @@ services:
azurite1: azurite1:
image: mcr.microsoft.com/azure-storage/azurite image: mcr.microsoft.com/azure-storage/azurite
ports: ports:
- "10000:10000" - "${AZURITE_PORT}:${AZURITE_PORT}"
volumes: volumes:
- data1-1:/data1 - data1-1:/data1
command: azurite-blob --blobHost 0.0.0.0 --blobPort 10000 --debug /azurite_log command: azurite-blob --blobHost 0.0.0.0 --blobPort ${AZURITE_PORT} --debug /azurite_log
volumes: volumes:
data1-1: data1-1:

View File

@ -14,3 +14,8 @@ services:
LDAP_PORT_NUMBER: ${LDAP_INTERNAL_PORT:-1389} LDAP_PORT_NUMBER: ${LDAP_INTERNAL_PORT:-1389}
ports: ports:
- ${LDAP_EXTERNAL_PORT:-1389}:${LDAP_INTERNAL_PORT:-1389} - ${LDAP_EXTERNAL_PORT:-1389}:${LDAP_INTERNAL_PORT:-1389}
healthcheck:
test: "ldapsearch -x -b dc=example,dc=org cn > /dev/null"
interval: 10s
retries: 10
timeout: 2s

View File

@ -84,5 +84,5 @@ SELECT * FROM WatchLog;
**See Also** **See Also**
- [Virtual columns](../../../engines/table-engines/special/index.md#table_engines-virtual_columns) - [Virtual columns](../../../engines/table-engines/index.md#table_engines-virtual_columns)
- [merge](../../../sql-reference/table-functions/merge.md) table function - [merge](../../../sql-reference/table-functions/merge.md) table function

View File

@ -4155,6 +4155,18 @@ Possible values:
Default value: `0`. Default value: `0`.
## date_time_overflow_behavior {#date_time_overflow_behavior}
Defines the behavior when [Date](../../sql-reference/data-types/date.md), [Date32](../../sql-reference/data-types/date32.md), [DateTime](../../sql-reference/data-types/datetime.md), [DateTime64](../../sql-reference/data-types/datetime64.md) or integers are converted into Date, Date32, DateTime or DateTime64 but the value cannot be represented in the result type.
Possible values:
- `ignore` — Silently ignore overflows. The result is random.
- `throw` — Throw an exception in case of conversion overflow.
- `saturate` — Silently saturate the result. If the value is smaller than the smallest value that can be represented by the target type, the result is chosen as the smallest representable value. If the value is bigger than the largest value that can be represented by the target type, the result is chosen as the largest representable value.
Default value: `ignore`.
## optimize_move_to_prewhere {#optimize_move_to_prewhere} ## optimize_move_to_prewhere {#optimize_move_to_prewhere}
Enables or disables automatic [PREWHERE](../../sql-reference/statements/select/prewhere.md) optimization in [SELECT](../../sql-reference/statements/select/index.md) queries. Enables or disables automatic [PREWHERE](../../sql-reference/statements/select/prewhere.md) optimization in [SELECT](../../sql-reference/statements/select/index.md) queries.

View File

@ -43,7 +43,7 @@ ClickHouse — полноценная столбцовая СУБД. Данны
## Блоки (Block) {#block} ## Блоки (Block) {#block}
`Block` — это контейнер, который представляет фрагмент (chunk) таблицы в памяти. Это набор троек — `(IColumn, IDataType, имя столбца)`. В процессе выполнения запроса, данные обрабатываются блоками (`Block`). Если есть `Block`, значит у нас есть данные (в объекте `IColumn`), информация о типе (в `IDataType`), которая говорит, как работать со столбцов, и имя столбца (оригинальное имя столбца таблицы или служебное имя, присвоенное для получения промежуточных результатов вычислений). `Block` — это контейнер, который представляет фрагмент (chunk) таблицы в памяти. Это набор троек — `(IColumn, IDataType, имя столбца)`. В процессе выполнения запроса, данные обрабатываются блоками (`Block`). Если есть `Block`, значит у нас есть данные (в объекте `IColumn`), информация о типе (в `IDataType`), которая говорит, как работать со столбцом, и имя столбца (оригинальное имя столбца таблицы или служебное имя, присвоенное для получения промежуточных результатов вычислений).
При вычислении некоторой функции на столбцах в блоке добавляется ещё один столбец с результатами в блок, не трогая колонки аргументов функции, потому что операции иммутабельные. Позже ненужные столбцы могут быть удалены из блока, но не модифицированы. Это удобно для устранения общих подвыражений. При вычислении некоторой функции на столбцах в блоке добавляется ещё один столбец с результатами в блок, не трогая колонки аргументов функции, потому что операции иммутабельные. Позже ненужные столбцы могут быть удалены из блока, но не модифицированы. Это удобно для устранения общих подвыражений.
@ -67,15 +67,15 @@ ClickHouse — полноценная столбцовая СУБД. Данны
## Форматы {#formats} ## Форматы {#formats}
Форматы данных реализуются с помощью потоков блоков. Есть форматы представления (presentational), пригодные только для вывода данных клиенту, такие как `Pretty` формат, который предоставляет только `IBlockOutputStream`. И есть форматы ввода/вывода, такие как `TabSeparated` или `JSONEachRow`. Форматы данных реализуются с помощью потоков блоков. Есть форматы представления (presentational), пригодные только для вывода данных клиенту, такие как `Pretty`-формат, который предоставляет только `IBlockOutputStream`. И есть форматы ввода-вывода, такие как `TabSeparated` или `JSONEachRow`.
Существуют также потоки строк: `IRowInputStream` и `IRowOutputStream`. Они позволяют вытягивать/выталкивать данные отдельными строками, а не блоками. Они нужны только для упрощения реализации ориентированных на строки форматов. Обертка `BlockInputStreamFromRowInputStream` и `BlockOutputStreamFromRowOutputStream` позволяет конвертировать потоки, ориентированные на строки, в обычные потоки, ориентированные на блоки. Существуют также потоки строк: `IRowInputStream` и `IRowOutputStream`. Они позволяют вытягивать и выталкивать данные отдельными строками, а не блоками. Они нужны только для упрощения реализации ориентированных на строки форматов. Обертка `BlockInputStreamFromRowInputStream` и `BlockOutputStreamFromRowOutputStream` позволяет конвертировать потоки, ориентированные на строки, в обычные потоки, ориентированные на блоки.
## I/O {#io} ## I/O {#io}
Для байт-ориентированного ввода-вывода существуют абстрактные классы `ReadBuffer` и `WriteBuffer`. Они используются вместо `iostream`. Не волнуйтесь: каждый зрелый проект C++ использует что-то другое вместо `iostream` по уважительным причинам. Для байт-ориентированного ввода-вывода существуют абстрактные классы `ReadBuffer` и `WriteBuffer`. Они используются вместо `iostream`. Не волнуйтесь: каждый зрелый проект C++ использует что-то другое вместо `iostream` по уважительным причинам.
`ReadBuffer` и `WriteBuffer` это просто непрерывный буфер и курсор, указывающий на позицию в этом буфере. Реализации могут как владеть так и не владеть памятью буфера. Существует виртуальный метод заполнения буфера следующими данными (для `ReadBuffer`) или сброса буфера куда-нибудь (например `WriteBuffer`). Виртуальные методы редко вызываются. `ReadBuffer` и `WriteBuffer` это просто непрерывный буфер и курсор, указывающий на позицию в этом буфере. Реализации могут как владеть так и не владеть памятью буфера. Существует виртуальный метод заполнения буфера следующими данными (для `ReadBuffer`) или сброса буфера куда-нибудь (например `WriteBuffer`). Виртуальные методы редко вызываются.
Реализации `ReadBuffer`/`WriteBuffer` используются для работы с файлами и файловыми дескрипторами, а также сетевыми сокетами, для реализации сжатия (`CompressedWriteBuffer` инициализируется вместе с другим `WriteBuffer` и осуществляет сжатие данных перед записью в него), и для других целей названия `ConcatReadBuffer`, `LimitReadBuffer`, и `HashingWriteBuffer` говорят сами за себя. Реализации `ReadBuffer`/`WriteBuffer` используются для работы с файлами и файловыми дескрипторами, а также сетевыми сокетами, для реализации сжатия (`CompressedWriteBuffer` инициализируется вместе с другим `WriteBuffer` и осуществляет сжатие данных перед записью в него), и для других целей названия `ConcatReadBuffer`, `LimitReadBuffer`, и `HashingWriteBuffer` говорят сами за себя.
@ -87,7 +87,7 @@ ClickHouse — полноценная столбцовая СУБД. Данны
Интерфейс `IStorage` служит для отображения таблицы. Различные движки таблиц являются реализациями этого интерфейса. Примеры `StorageMergeTree`, `StorageMemory` и так далее. Экземпляры этих классов являются просто таблицами. Интерфейс `IStorage` служит для отображения таблицы. Различные движки таблиц являются реализациями этого интерфейса. Примеры `StorageMergeTree`, `StorageMemory` и так далее. Экземпляры этих классов являются просто таблицами.
Ключевые методы `IStorage` это `read` и `write`. Есть и другие варианты - `alter`, `rename`, `drop` и так далее. Метод `read` принимает следующие аргументы: набор столбцов для чтения из таблицы, `AST` запрос и желаемое количество потоков для вывода. Он возвращает один или несколько объектов `IBlockInputStream` и информацию о стадии обработки данных, которая была завершена внутри табличного движка во время выполнения запроса. Ключевые методы `IStorage` это `read` и `write`. Есть и другие варианты `alter`, `rename`, `drop` и так далее. Метод `read` принимает следующие аргументы: набор столбцов для чтения из таблицы, `AST` запрос и желаемое количество потоков для вывода. Он возвращает один или несколько объектов `IBlockInputStream` и информацию о стадии обработки данных, которая была завершена внутри табличного движка во время выполнения запроса.
В большинстве случаев метод read отвечает только за чтение указанных столбцов из таблицы, а не за дальнейшую обработку данных. Вся дальнейшая обработка данных осуществляется интерпретатором запросов и не входит в сферу ответственности `IStorage`. В большинстве случаев метод read отвечает только за чтение указанных столбцов из таблицы, а не за дальнейшую обработку данных. Вся дальнейшая обработка данных осуществляется интерпретатором запросов и не входит в сферу ответственности `IStorage`.
@ -112,7 +112,7 @@ ClickHouse — полноценная столбцовая СУБД. Данны
## Интерпретаторы {#interpreters} ## Интерпретаторы {#interpreters}
Интерпретаторы отвечают за создание конвейера выполнения запроса из `AST`. Есть простые интерпретаторы, такие как `InterpreterExistsQuery` и `InterpreterDropQuery` или более сложный `InterpreterSelectQuery`. Конвейер выполнения запроса представляет собой комбинацию входных и выходных потоков блоков. Например, результатом интерпретации `SELECT` запроса является `IBlockInputStream` для чтения результирующего набора данных; результат интерпретации `INSERT` запроса - это `IBlockOutputStream`, для записи данных, предназначенных для вставки; результат интерпретации `INSERT SELECT` запроса - это `IBlockInputStream`, который возвращает пустой результирующий набор при первом чтении, но копирует данные из `SELECT` к `INSERT`. Интерпретаторы отвечают за создание конвейера выполнения запроса из `AST`. Есть простые интерпретаторы, такие как `InterpreterExistsQuery` и `InterpreterDropQuery` или более сложный `InterpreterSelectQuery`. Конвейер выполнения запроса представляет собой комбинацию входных и выходных потоков блоков. Например, результатом интерпретации `SELECT` запроса является `IBlockInputStream` для чтения результирующего набора данных; результат интерпретации `INSERT` запроса это `IBlockOutputStream`, для записи данных, предназначенных для вставки; результат интерпретации `INSERT SELECT` запроса это `IBlockInputStream`, который возвращает пустой результирующий набор при первом чтении, но копирует данные из `SELECT` к `INSERT`.
`InterpreterSelectQuery` использует `ExpressionAnalyzer` и `ExpressionActions` механизмы для анализа запросов и преобразований. Именно здесь выполняется большинство оптимизаций запросов на основе правил. `ExpressionAnalyzer` написан довольно грязно и должен быть переписан: различные преобразования запросов и оптимизации должны быть извлечены в отдельные классы, чтобы позволить модульные преобразования или запросы. `InterpreterSelectQuery` использует `ExpressionAnalyzer` и `ExpressionActions` механизмы для анализа запросов и преобразований. Именно здесь выполняется большинство оптимизаций запросов на основе правил. `ExpressionAnalyzer` написан довольно грязно и должен быть переписан: различные преобразования запросов и оптимизации должны быть извлечены в отдельные классы, чтобы позволить модульные преобразования или запросы.
@ -120,9 +120,9 @@ ClickHouse — полноценная столбцовая СУБД. Данны
Существуют обычные функции и агрегатные функции. Агрегатные функции смотрите в следующем разделе. Существуют обычные функции и агрегатные функции. Агрегатные функции смотрите в следующем разделе.
Обычные функции не изменяют число строк и работают так, как если бы обрабатывали каждую строку независимо. В действительности же, функции вызываются не к отдельным строкам, а блокам данных для реализации векторизованного выполнения запросов. Обычные функции не изменяют число строк и работают так, как если бы обрабатывали каждую строку независимо. В действительности же функции вызываются не к отдельным строкам, а блокам данных для реализации векторизованного выполнения запросов.
Некоторые функции, такие как [blockSize](../sql-reference/functions/other-functions.md#function-blocksize), [rowNumberInBlock](../sql-reference/functions/other-functions.md#function-rownumberinblock), и [runningAccumulate](../sql-reference/functions/other-functions.md#runningaccumulate), эксплуатируют блочную обработку и нарушают независимость строк. Некоторые функции, такие как [blockSize](../sql-reference/functions/other-functions.md#function-blocksize), [rowNumberInBlock](../sql-reference/functions/other-functions.md#function-rownumberinblock), и [runningAccumulate](../sql-reference/functions/other-functions.md#runningaccumulate), используют блочную обработку и нарушают независимость строк.
ClickHouse имеет сильную типизацию, поэтому нет никакого неявного преобразования типов. Если функция не поддерживает определенную комбинацию типов, она создает исключение. Но функции могут работать (перегружаться) для многих различных комбинаций типов. Например, функция `plus` (для реализации `+` оператор) работает для любой комбинации числовых типов: `UInt8` + `Float32`, `UInt16` + `Int8` и так далее. Кроме того, некоторые вариадические функции, такие как `concat`, могут принимать любое количество аргументов. ClickHouse имеет сильную типизацию, поэтому нет никакого неявного преобразования типов. Если функция не поддерживает определенную комбинацию типов, она создает исключение. Но функции могут работать (перегружаться) для многих различных комбинаций типов. Например, функция `plus` (для реализации `+` оператор) работает для любой комбинации числовых типов: `UInt8` + `Float32`, `UInt16` + `Int8` и так далее. Кроме того, некоторые вариадические функции, такие как `concat`, могут принимать любое количество аргументов.
@ -161,23 +161,23 @@ ClickHouse имеет сильную типизацию, поэтому нет
::: :::
## Выполнение распределенных запросов (Distributed Query Execution) {#distributed-query-execution} ## Выполнение распределенных запросов (Distributed Query Execution) {#distributed-query-execution}
Сервера в кластере в основном независимы. Вы можете создать `Распределенную` (`Distributed`) таблицу на одном или всех серверах в кластере. Такая таблица сама по себе не хранит данные - она только предоставляет возможность "просмотра" всех локальных таблиц на нескольких узлах кластера. При выполнении `SELECT` распределенная таблица переписывает запрос, выбирает удаленные узлы в соответствии с настройками балансировки нагрузки и отправляет им запрос. Распределенная таблица просит удаленные сервера обработать запрос до той стадии, когда промежуточные результаты с разных серверов могут быть объединены. Затем он получает промежуточные результаты и объединяет их. Распределенная таблица пытается возложить как можно больше работы на удаленные серверы и сократить объем промежуточных данных, передаваемых по сети. Сервера в кластере в основном независимы. Вы можете создать `распределённую` (`Distributed`) таблицу на одном или всех серверах в кластере. Такая таблица сама по себе не хранит данные — она только предоставляет возможность “просмотра” всех локальных таблиц на нескольких узлах кластера. При выполнении `SELECT` распределенная таблица переписывает запрос, выбирает удаленные узлы в соответствии с настройками балансировки нагрузки и отправляет им запрос. Распределенная таблица просит удаленные сервера обработать запрос до той стадии, когда промежуточные результаты с разных серверов могут быть объединены. Затем он получает промежуточные результаты и объединяет их. Распределенная таблица пытается возложить как можно больше работы на удаленные серверы и сократить объем промежуточных данных, передаваемых по сети.
Ситуация усложняется при использовании подзапросов в случае `IN` или `JOIN`, когда каждый из них использует таблицу `Distributed`. Есть разные стратегии для выполнения таких запросов. Ситуация усложняется при использовании подзапросов в случае `IN` или `JOIN`, когда каждый из них использует таблицу `Distributed`. Есть разные стратегии для выполнения таких запросов.
Глобального плана выполнения распределенных запросов не существует. Каждый узел имеет собственный локальный план для своей части работы. У нас есть простое однонаправленное выполнение распределенных запросов: мы отправляем запросы на удаленные узлы и затем объединяем результаты. Но это невозможно для сложных запросов `GROUP BY` высокой кардинальности или запросов с большим числом временных данных в `JOIN`: в таких случаях нам необходимо перераспределить («reshuffle») данные между серверами, что требует дополнительной координации. ClickHouse не поддерживает выполнение запросов такого рода, и нам нужно работать над этим. Глобального плана выполнения распределённых запросов не существует. Каждый узел имеет собственный локальный план для своей части работы. У нас есть простое однонаправленное выполнение распределенных запросов: мы отправляем запросы на удаленные узлы и затем объединяем результаты. Но это невозможно для сложных запросов `GROUP BY` высокой кардинальности или запросов с большим числом временных данных в `JOIN`: в таких случаях нам необходимо перераспределить (“reshuffle”) данные между узлами, что требует дополнительной координации. ClickHouse не поддерживает выполнение запросов такого рода, и нам нужно работать над этим.
## Merge Tree {#merge-tree} ## Merge Tree {#merge-tree}
`MergeTree` — это семейство движков хранения, поддерживающих индексацию по первичному ключу. Первичный ключ может быть произвольным набором (кортежем) столбцов или выражений. Данные в таблице `MergeTree` хранятся "частями" (“parts”). Каждая часть хранит данные отсортированные по первичному ключу (данные упорядочены лексикографически). Все столбцы таблицы хранятся в отдельных файлах `column.bin` в этих частях. Файлы состоят из сжатых блоков. Каждый блок обычно содержит от 64 КБ до 1 МБ несжатых данных, в зависимости от среднего значения размера данных. Блоки состоят из значений столбцов, расположенных последовательно один за другим. Значения столбцов находятся в одинаковом порядке для каждого столбца (порядок определяется первичным ключом), поэтому, когда вы выполняете итерацию по многим столбцам, вы получаете значения для соответствующих строк. `MergeTree` — это семейство движков хранения, поддерживающих индексацию по первичному ключу. Первичный ключ может быть произвольным набором (кортежем) столбцов или выражений. Данные в таблице `MergeTree` хранятся “частями” (“parts”). Каждая часть хранит данные отсортированные по первичному ключу (данные упорядочены лексикографически). Все столбцы таблицы хранятся в отдельных файлах `column.bin` в этих частях. Файлы состоят из сжатых блоков. Каждый блок обычно содержит от 64 КБ до 1 МБ несжатых данных, в зависимости от среднего значения размера данных. Блоки состоят из значений столбцов, расположенных последовательно один за другим. Значения столбцов находятся в одинаковом порядке для каждого столбца (порядок определяется первичным ключом), поэтому, когда вы выполняете итерацию по многим столбцам, вы получаете значения для соответствующих строк.
Сам первичный ключ является “разреженным” (sparse). Он не относится к каждой отдельной строке, а только к некоторым диапазонам данных. Отдельный файл «primary.idx» имеет значение первичного ключа для каждой N-й строки, где N называется гранулярностью индекса (index_granularity, обычно N = 8192). Также для каждого столбца у нас есть файлы `column.mrk` с "метками" ("marks"), которые обозначают смещение для каждой N-й строки в файле данных. Каждая метка представляет собой пару: смещение начала сжатого блока от начала файла и смещение к началу данных в распакованном блоке. Обычно сжатые блоки выравниваются по меткам, а смещение в распакованном блоке равно нулю. Данные для `primary.idx` всегда находятся в памяти, а данные для файлов `column.mrk` кэшируются. Сам первичный ключ является “разреженным” (sparse). Он не относится к каждой отдельной строке, а только к некоторым диапазонам данных. Отдельный файл «primary.idx» имеет значение первичного ключа для каждой N-й строки, где N называется гранулярностью индекса (index_granularity, обычно N = 8192). Также для каждого столбца у нас есть файлы `column.mrk` с “метками” (“marks”), которые обозначают смещение для каждой N-й строки в файле данных. Каждая метка представляет собой пару: смещение начала сжатого блока от начала файла и смещение к началу данных в распакованном блоке. Обычно сжатые блоки выравниваются по меткам, а смещение в распакованном блоке равно нулю. Данные для `primary.idx` всегда находятся в памяти, а данные для файлов `column.mrk` кэшируются.
Когда мы собираемся читать что-то из части данных `MergeTree`, мы смотрим содержимое `primary.idx` и определяем диапазоны, которые могут содержать запрошенные данные, затем просматриваем содержимое `column.mrk` и вычисляем смещение, чтобы начать чтение этих диапазонов. Из-за разреженности могут быть прочитаны лишние данные. ClickHouse не подходит для простых точечных запросов высокой интенсивности, потому что весь диапазон строк размером `index_granularity` должен быть прочитан для каждого ключа, а сжатый блок должен быть полностью распакован для каждого столбца. Мы сделали индекс разреженным, потому что мы должны иметь возможность поддерживать триллионы строк на один сервер без существенных расходов памяти на индексацию. Кроме того, поскольку первичный ключ является разреженным, он не уникален: он не может проверить наличие ключа в таблице во время INSERT. Вы можете иметь множество строк с одним и тем же ключом в таблице. Когда мы собираемся читать что-то из части данных `MergeTree`, мы смотрим содержимое `primary.idx` и определяем диапазоны, которые могут содержать запрошенные данные, затем просматриваем содержимое `column.mrk` и вычисляем смещение, чтобы начать чтение этих диапазонов. Из-за разреженности могут быть прочитаны лишние данные. ClickHouse не подходит для простых точечных запросов высокой интенсивности, потому что весь диапазон строк размером `index_granularity` должен быть прочитан для каждого ключа, а сжатый блок должен быть полностью распакован для каждого столбца. Мы сделали индекс разреженным, потому что мы должны иметь возможность поддерживать триллионы строк на один сервер без существенных расходов памяти на индексацию. Кроме того, поскольку первичный ключ является разреженным, он не уникален: он не может проверить наличие ключа в таблице во время INSERT. Вы можете иметь множество строк с одним и тем же ключом в таблице.
При выполнении `INSERT` для группы данных в `MergeTree`, элементы группы сортируются по первичному ключу и образует новую “часть”. Фоновые потоки периодически выбирают некоторые части и объединяют их в одну отсортированную часть, чтобы сохранить относительно небольшое количество частей. Вот почему он называется `MergeTree`. Конечно, объединение приводит к повышению интенсивности записи. Все части иммутабельные: они только создаются и удаляются, но не изменяются. Когда выполняется `SELECT`, он содержит снимок таблицы (набор частей). После объединения старые части также сохраняются в течение некоторого времени, чтобы упростить восстановление после сбоя, поэтому, если мы видим, что какая-то объединенная часть, вероятно, повреждена, мы можем заменить ее исходными частями. При выполнении `INSERT` для группы данных в `MergeTree`, элементы группы сортируются по первичному ключу и образует новую “часть”. Фоновые потоки периодически выбирают некоторые части и объединяют их в одну отсортированную часть, чтобы сохранить относительно небольшое количество частей. Вот почему он называется `MergeTree`. Конечно, объединение приводит к повышению интенсивности записи. Все части иммутабельные: они только создаются и удаляются, но не изменяются. Когда выполняется `SELECT`, он содержит снимок таблицы (набор частей). После объединения старые части также сохраняются в течение некоторого времени, чтобы упростить восстановление после сбоя, поэтому, если мы видим, что какая-то объединенная часть, вероятно, повреждена, мы можем заменить ее исходными частями.
`MergeTree` не является LSM (Log-structured merge-tree — журнально-структурированным деревом со слиянием), потому что оно не содержит «memtable» и «log»: вставленные данные записываются непосредственно в файловую систему. Это делает его пригодным только для вставки данных в пакетах, а не по отдельным строкам и не очень часто — примерно раз в секунду это нормально, а тысячу раз в секунду - нет. Мы сделали это для простоты и потому, что мы уже вставляем данные в пакеты в наших приложениях. `MergeTree` не является LSM (Log-structured merge-tree — журнально-структурированным деревом со слиянием), потому что оно не содержит «memtable» и «log»: вставленные данные записываются непосредственно в файловую систему. Это делает его пригодным только для вставки данных в пакетах, а не по отдельным строкам и не очень часто — примерно раз в секунду это нормально, а тысячу раз в секунду нет. Мы сделали это для простоты и потому, что мы уже вставляем данные в пакеты в наших приложениях.
> Таблицы `MergeTree` могут иметь только один (первичный) индекс: вторичных индексов нет. Было бы неплохо разрешить несколько физических представлениям в одной логической таблице, например, хранить данные в более чем одном физическом порядке или даже разрешить представления с предварительно агрегированными данными вместе с исходными данными. > Таблицы `MergeTree` могут иметь только один (первичный) индекс: вторичных индексов нет. Было бы неплохо разрешить несколько физических представлениям в одной логической таблице, например, хранить данные в более чем одном физическом порядке или даже разрешить представления с предварительно агрегированными данными вместе с исходными данными.
@ -191,7 +191,7 @@ ClickHouse имеет сильную типизацию, поэтому нет
Репликация использует асинхронную multi-master-схему. Вы можете вставить данные в любую реплику, которая имеет открытую сессию в `ZooKeeper`, и данные реплицируются на все другие реплики асинхронно. Поскольку ClickHouse не поддерживает UPDATE, репликация исключает конфликты (conflict-free replication). Поскольку подтверждение вставок кворумом не реализовано, только что вставленные данные могут быть потеряны в случае сбоя одного узла. Репликация использует асинхронную multi-master-схему. Вы можете вставить данные в любую реплику, которая имеет открытую сессию в `ZooKeeper`, и данные реплицируются на все другие реплики асинхронно. Поскольку ClickHouse не поддерживает UPDATE, репликация исключает конфликты (conflict-free replication). Поскольку подтверждение вставок кворумом не реализовано, только что вставленные данные могут быть потеряны в случае сбоя одного узла.
Метаданные для репликации хранятся в `ZooKeeper`. Существует журнал репликации, в котором перечислены действия, которые необходимо выполнить. Среди этих действий: получить часть (get the part); объединить части (merge parts); удалить партицию (drop a partition) и так далее. Каждая реплика копирует журнал репликации в свою очередь, а затем выполняет действия из очереди. Например, при вставке в журнале создается действие «получить часть» (get the part), и каждая реплика загружает эту часть. Слияния координируются между репликами, чтобы получить идентичные до байта результаты. Все части объединяются одинаково на всех репликах. Одна из реплик-лидеров инициирует новое слияние кусков первой и записывает действия «слияния частей» в журнал. Несколько реплик (или все) могут быть лидерами одновременно. Реплике можно запретить быть лидером с помощью `merge_tree` настройки `replicated_can_become_leader`. Метаданные для репликации хранятся в `ZooKeeper`. Существует журнал репликации, в котором перечислены действия, которые необходимо выполнить. Среди этих действий: получить часть (get the part); объединить части (merge parts); удалить партицию (drop a partition) и так далее. Каждая реплика копирует журнал репликации в свою очередь, а затем выполняет действия из очереди. Например, при вставке в журнале создается действие “получить часть” (get the part), и каждая реплика загружает эту часть. Слияния координируются между репликами, чтобы получить идентичные до байта результаты. Все части объединяются одинаково на всех репликах. Одна из реплик-лидеров инициирует новое слияние кусков первой и записывает действия “слияния частей” в журнал. Несколько реплик (или все) могут быть лидерами одновременно. Реплике можно запретить быть лидером с помощью `merge_tree` настройки `replicated_can_become_leader`.
Репликация является физической: между узлами передаются только сжатые части, а не запросы. Слияния обрабатываются на каждой реплике независимо, в большинстве случаев, чтобы снизить затраты на сеть, во избежание усиления роли сети. Крупные объединенные части отправляются по сети только в случае значительной задержки репликации. Репликация является физической: между узлами передаются только сжатые части, а не запросы. Слияния обрабатываются на каждой реплике независимо, в большинстве случаев, чтобы снизить затраты на сеть, во избежание усиления роли сети. Крупные объединенные части отправляются по сети только в случае значительной задержки репликации.

View File

@ -7,16 +7,16 @@ sidebar_label: "История ClickHouse"
# История ClickHouse {#istoriia-clickhouse} # История ClickHouse {#istoriia-clickhouse}
ClickHouse изначально разрабатывался для обеспечения работы [Яндекс.Метрики](https://metrika.yandex.ru/), [второй крупнейшей в мире](http://w3techs.com/technologies/overview/traffic_analysis/all) платформы для веб аналитики, и продолжает быть её ключевым компонентом. При более 13 триллионах записей в базе данных и более 20 миллиардах событий в сутки, ClickHouse позволяет генерировать индивидуально настроенные отчёты на лету напрямую из неагрегированных данных. Данная статья вкратце демонстрирует какие цели исторически стояли перед ClickHouse на ранних этапах его развития. ClickHouse изначально разрабатывался для обеспечения работы [Яндекс.Метрики](https://metrika.yandex.ru/) [второй крупнейшей в мире](http://w3techs.com/technologies/overview/traffic_analysis/all) платформы для веб-аналитики — и продолжает быть её ключевым компонентом. При более 13 триллионах записей в базе данных и более 20 миллиардах событий в сутки, ClickHouse позволяет генерировать индивидуально настроенные отчёты на лету напрямую из неагрегированных данных. Данная статья вкратце демонстрирует какие цели исторически стояли перед ClickHouse на ранних этапах его развития.
Яндекс.Метрика на лету строит индивидуальные отчёты на основе хитов и визитов, с периодом и произвольными сегментами, задаваемыми конечным пользователем. Часто требуется построение сложных агрегатов, например числа уникальных пользователей. Новые данные для построения отчета поступают в реальном времени. Яндекс.Метрика на лету строит индивидуальные отчёты на основе хитов и визитов, с периодом и произвольными сегментами, задаваемыми конечным пользователем. Часто требуется построение сложных агрегатов, например числа уникальных пользователей. Новые данные для построения отчета поступают в реальном времени.
На апрель 2014, в Яндекс.Метрику поступало около 12 миллиардов событий (показов страниц и кликов мыши) ежедневно. Все эти события должны быть сохранены для возможности строить произвольные отчёты. Один запрос может потребовать просканировать миллионы строк за время не более нескольких сотен миллисекунд, или сотни миллионов строк за время не более нескольких секунд. На апрель 2014 года в Яндекс.Метрику поступало около 12 миллиардов событий (показов страниц и кликов мыши) ежедневно. Все эти события должны быть сохранены для возможности строить произвольные отчёты. Один запрос может потребовать просканировать миллионы строк за время не более нескольких сотен миллисекунд, или сотни миллионов строк за время не более нескольких секунд.
## Использование в Яндекс.Метрике и других отделах Яндекса {#ispolzovanie-v-iandeks-metrike-i-drugikh-otdelakh-iandeksa} ## Использование в Яндекс.Метрике и других отделах Яндекса {#ispolzovanie-v-iandeks-metrike-i-drugikh-otdelakh-iandeksa}
В Яндекс.Метрике ClickHouse используется для нескольких задач. В Яндекс.Метрике ClickHouse используется для нескольких задач.
Основная задача - построение отчётов в режиме онлайн по неагрегированным данным. Для решения этой задачи используется кластер из 374 серверов, хранящий более 20,3 триллионов строк в базе данных. Объём сжатых данных, без учёта дублирования и репликации, составляет около 2 ПБ. Объём несжатых данных (в формате tsv) составил бы, приблизительно, 17 ПБ. Основная задача построение отчётов в режиме онлайн по неагрегированным данным. Для решения этой задачи используется кластер из 374 серверов, хранящий более 20,3 триллионов строк в базе данных. Объём сжатых данных, без учёта дублирования и репликации, составляет около 2 ПБ. Объём несжатых данных (в формате tsv) составил бы, приблизительно, 17 ПБ.
Также ClickHouse используется: Также ClickHouse используется:
@ -35,20 +35,20 @@ ClickHouse имеет более десятка инсталляций в дру
Но агрегированные данные являются очень ограниченным решением, по следующим причинам: Но агрегированные данные являются очень ограниченным решением, по следующим причинам:
- вы должны заранее знать перечень отчётов, необходимых пользователю; - вы должны заранее знать перечень отчётов, необходимых пользователю;
- то есть, пользователь не может построить произвольный отчёт; - то есть пользователь не может построить произвольный отчёт;
- при агрегации по большому количеству ключей, объём данных не уменьшается и агрегация бесполезна; - при агрегации по большому количеству ключей объём данных не уменьшается и агрегация бесполезна;
- при большом количестве отчётов, получается слишком много вариантов агрегации (комбинаторный взрыв); - при большом количестве отчётов получается слишком много вариантов агрегации (комбинаторный взрыв);
- при агрегации по ключам высокой кардинальности (например, URL) объём данных уменьшается не сильно (менее чем в 2 раза); - при агрегации по ключам высокой кардинальности (например, URL) объём данных уменьшается не сильно (менее чем в 2 раза);
- из-за этого, объём данных при агрегации может не уменьшиться, а вырасти; - из-за этого объём данных при агрегации может не уменьшиться, а вырасти;
- пользователи будут смотреть не все отчёты, которые мы для них посчитаем - то есть, большая часть вычислений бесполезна; - пользователи будут смотреть не все отчёты, которые мы для них посчитаем — то есть большая часть вычислений бесполезна;
- возможно нарушение логической целостности данных для разных агрегаций; - возможно нарушение логической целостности данных для разных агрегаций.
Как видно, если ничего не агрегировать, и работать с неагрегированными данными, то это даже может уменьшить объём вычислений. Как видно, если ничего не агрегировать и работать с неагрегированными данными, то это даже может уменьшить объём вычислений.
Впрочем, при агрегации, существенная часть работы выносится в оффлайне, и её можно делать сравнительно спокойно. Для сравнения, при онлайн вычислениях, вычисления надо делать так быстро, как это возможно, так как именно в момент вычислений пользователь ждёт результата. Впрочем, при агрегации существенная часть работы ведётся в фоновом режиме и её можно делать сравнительно спокойно. А онлайн-вычисления надо делать так быстро, как это возможно, так как именно в момент вычислений пользователь ждёт результата.
В Яндекс.Метрике есть специализированная система для агрегированных данных - Metrage, на основе которой работает большинство отчётов. В Яндекс.Метрике есть специализированная система для агрегированных данных Metrage, на основе которой работает большинство отчётов.
Также в Яндекс.Метрике с 2009 года использовалась специализированная OLAP БД для неагрегированных данных - OLAPServer, на основе которой раньше работал конструктор отчётов. Также в Яндекс.Метрике с 2009 года использовалась специализированная OLAP БД для неагрегированных данных OLAPServer, на основе которой раньше работал конструктор отчётов.
OLAPServer хорошо подходил для неагрегированных данных, но содержал много ограничений, не позволяющих использовать его для всех отчётов так, как хочется: отсутствие поддержки типов данных (только числа), невозможность инкрементального обновления данных в реальном времени (только перезаписью данных за сутки). OLAPServer не является СУБД, а является специализированной БД. OLAPServer хорошо подходил для неагрегированных данных, но содержал много ограничений, не позволяющих использовать его для всех отчётов так, как хочется: отсутствие поддержки типов данных (только числа), невозможность инкрементального обновления данных в реальном времени (только перезаписью данных за сутки). OLAPServer не является СУБД, а является специализированной БД.
Чтобы снять ограничения OLAPServer-а и решить задачу работы с неагрегированными данными для всех отчётов, разработана СУБД ClickHouse. Чтобы снять ограничения OLAPServer и решить задачу работы с неагрегированными данными для всех отчётов была разработана СУБД ClickHouse.

View File

@ -3838,6 +3838,18 @@ SELECT * FROM positional_arguments ORDER BY 2,3;
Значение по умолчанию: `0`. Значение по умолчанию: `0`.
## date_time_overflow_behavior {#date_time_overflow_behavior}
Задаёт поведение при преобразовании [Date](../../sql-reference/data-types/date.md), [Date32](../../sql-reference/data-types/date32.md), [DateTime](../../sql-reference/data-types/datetime.md), [DateTime64](../../sql-reference/data-types/datetime64.md), а также численных типов данных к Date, Date32, DateTime, DateTime64 в случае, если результат выходит за пределы диапазона значений необходимого типа.
Возможные значения:
- `ignore` — Молча игнорирует переполнение. В таком случае, результатом будет случайное значение.
- `throw` — Выкинуть исключение при переполнении.
- `saturate` — Молча округлить до ближайшего (то есть наибольшего или наименьшего) значения из диапазона значений результата.
Значение по умолчанию: `ignore`.
## optimize_move_to_prewhere {#optimize_move_to_prewhere} ## optimize_move_to_prewhere {#optimize_move_to_prewhere}
Включает или отключает автоматическую оптимизацию [PREWHERE](../../sql-reference/statements/select/prewhere.md) в запросах [SELECT](../../sql-reference/statements/select/index.md). Включает или отключает автоматическую оптимизацию [PREWHERE](../../sql-reference/statements/select/prewhere.md) в запросах [SELECT](../../sql-reference/statements/select/index.md).

View File

@ -46,6 +46,7 @@ CLICKHOUSE_Format=(
ArrowStream ArrowStream
Avro Avro
AvroConfluent AvroConfluent
BSONEachRow
CSV CSV
CSVWithNames CSVWithNames
CSVWithNamesAndTypes CSVWithNamesAndTypes
@ -56,6 +57,7 @@ CLICKHOUSE_Format=(
CustomSeparatedIgnoreSpacesWithNamesAndTypes CustomSeparatedIgnoreSpacesWithNamesAndTypes
CustomSeparatedWithNames CustomSeparatedWithNames
CustomSeparatedWithNamesAndTypes CustomSeparatedWithNamesAndTypes
DWARF
HiveText HiveText
JSON JSON
JSONAsObject JSONAsObject
@ -74,7 +76,7 @@ CLICKHOUSE_Format=(
JSONEachRow JSONEachRow
JSONEachRowWithProgress JSONEachRowWithProgress
JSONLines JSONLines
JSONStringEachRow JSONObjectEachRow
JSONStrings JSONStrings
JSONStringsEachRow JSONStringsEachRow
JSONStringsEachRowWithProgress JSONStringsEachRowWithProgress
@ -90,14 +92,19 @@ CLICKHOUSE_Format=(
Null Null
ODBCDriver2 ODBCDriver2
ORC ORC
One
Parquet Parquet
ParquetMetadata
PostgreSQLWire PostgreSQLWire
Pretty Pretty
PrettyCompact PrettyCompact
PrettyCompactMonoBlock PrettyCompactMonoBlock
PrettyCompactNoEscapes PrettyCompactNoEscapes
PrettyCompactNoEscapesMonoBlock PrettyCompactNoEscapesMonoBlock
PrettyJSONEachRow
PrettyJSONLines
PrettyMonoBlock PrettyMonoBlock
PrettyNDJSON
PrettyNoEscapes PrettyNoEscapes
PrettyNoEscapesMonoBlock PrettyNoEscapesMonoBlock
PrettySpace PrettySpace
@ -111,6 +118,7 @@ CLICKHOUSE_Format=(
RawBLOB RawBLOB
Regexp Regexp
RowBinary RowBinary
RowBinaryWithDefaults
RowBinaryWithNames RowBinaryWithNames
RowBinaryWithNamesAndTypes RowBinaryWithNamesAndTypes
SQLInsert SQLInsert
@ -146,7 +154,7 @@ function _clickhouse_quote()
# Extract every option (everything that starts with "-") from the --help dialog. # Extract every option (everything that starts with "-") from the --help dialog.
function _clickhouse_get_options() function _clickhouse_get_options()
{ {
"$@" --help 2>&1 | awk -F '[ ,=<>]' '{ for (i=1; i <= NF; ++i) { if (substr($i, 0, 1) == "-" && length($i) > 1) print $i; } }' | sort -u "$@" --help 2>&1 | awk -F '[ ,=<>.]' '{ for (i=1; i <= NF; ++i) { if (substr($i, 1, 1) == "-" && length($i) > 1) print $i; } }' | sort -u
} }
function _complete_for_clickhouse_generic_bin_impl() function _complete_for_clickhouse_generic_bin_impl()

View File

@ -50,6 +50,9 @@
#include <Disks/registerDisks.h> #include <Disks/registerDisks.h>
#include <incbin.h>
/// A minimal file used when the keeper is run without installation
INCBIN(keeper_resource_embedded_xml, SOURCE_DIR "/programs/keeper/keeper_embedded.xml");
int mainEntryClickHouseKeeper(int argc, char ** argv) int mainEntryClickHouseKeeper(int argc, char ** argv)
{ {
@ -158,6 +161,8 @@ int Keeper::run()
void Keeper::initialize(Poco::Util::Application & self) void Keeper::initialize(Poco::Util::Application & self)
{ {
ConfigProcessor::registerEmbeddedConfig("keeper_config.xml", std::string_view(reinterpret_cast<const char *>(gkeeper_resource_embedded_xmlData), gkeeper_resource_embedded_xmlSize));
BaseDaemon::initialize(self); BaseDaemon::initialize(self);
logger().information("starting up"); logger().information("starting up");

View File

@ -413,6 +413,9 @@
--> -->
<mark_cache_size>5368709120</mark_cache_size> <mark_cache_size>5368709120</mark_cache_size>
<!-- For marks of secondary indices.
-->
<index_mark_cache_size>5368709120</index_mark_cache_size>
<!-- If you enable the `min_bytes_to_use_mmap_io` setting, <!-- If you enable the `min_bytes_to_use_mmap_io` setting,
the data in MergeTree tables can be read with mmap to avoid copying from kernel to userspace. the data in MergeTree tables can be read with mmap to avoid copying from kernel to userspace.

View File

@ -970,6 +970,10 @@ private:
if (!node->hasAlias()) if (!node->hasAlias())
return; return;
// We should not resolve expressions to WindowNode
if (node->getNodeType() == QueryTreeNodeType::WINDOW)
return;
const auto & alias = node->getAlias(); const auto & alias = node->getAlias();
if (is_lambda_node) if (is_lambda_node)

View File

@ -606,7 +606,7 @@ namespace ErrorCodes
APPLY_FOR_ERROR_CODES(M) APPLY_FOR_ERROR_CODES(M)
#undef M #undef M
constexpr ErrorCode END = 3000; constexpr ErrorCode END = 1002;
ErrorPairHolder values[END + 1]{}; ErrorPairHolder values[END + 1]{};
struct ErrorCodesNames struct ErrorCodesNames

View File

@ -2496,25 +2496,15 @@ void KeeperStorage::dumpSessionsAndEphemerals(WriteBufferFromOwnString & buf) co
uint64_t KeeperStorage::getTotalWatchesCount() const uint64_t KeeperStorage::getTotalWatchesCount() const
{ {
uint64_t ret = 0; uint64_t ret = 0;
for (const auto & [path, subscribed_sessions] : watches) for (const auto & [session, paths] : sessions_and_watchers)
ret += subscribed_sessions.size(); ret += paths.size();
for (const auto & [path, subscribed_sessions] : list_watches)
ret += subscribed_sessions.size();
return ret; return ret;
} }
uint64_t KeeperStorage::getSessionsWithWatchesCount() const uint64_t KeeperStorage::getSessionsWithWatchesCount() const
{ {
std::unordered_set<int64_t> counter; return sessions_and_watchers.size();
for (const auto & [path, subscribed_sessions] : watches)
counter.insert(subscribed_sessions.begin(), subscribed_sessions.end());
for (const auto & [path, subscribed_sessions] : list_watches)
counter.insert(subscribed_sessions.begin(), subscribed_sessions.end());
return counter.size();
} }
uint64_t KeeperStorage::getTotalEphemeralNodesCount() const uint64_t KeeperStorage::getTotalEphemeralNodesCount() const

View File

@ -73,11 +73,11 @@ static constexpr auto DEFAULT_MARK_CACHE_POLICY = "SLRU";
static constexpr auto DEFAULT_MARK_CACHE_MAX_SIZE = 5368_MiB; static constexpr auto DEFAULT_MARK_CACHE_MAX_SIZE = 5368_MiB;
static constexpr auto DEFAULT_MARK_CACHE_SIZE_RATIO = 0.5l; static constexpr auto DEFAULT_MARK_CACHE_SIZE_RATIO = 0.5l;
static constexpr auto DEFAULT_INDEX_UNCOMPRESSED_CACHE_POLICY = "SLRU"; static constexpr auto DEFAULT_INDEX_UNCOMPRESSED_CACHE_POLICY = "SLRU";
static constexpr auto DEFAULT_INDEX_UNCOMPRESSED_CACHE_MAX_SIZE = 0_MiB; static constexpr auto DEFAULT_INDEX_UNCOMPRESSED_CACHE_MAX_SIZE = 0;
static constexpr auto DEFAULT_INDEX_UNCOMPRESSED_CACHE_SIZE_RATIO = 0.5l; static constexpr auto DEFAULT_INDEX_UNCOMPRESSED_CACHE_SIZE_RATIO = 0.5;
static constexpr auto DEFAULT_INDEX_MARK_CACHE_POLICY = "SLRU"; static constexpr auto DEFAULT_INDEX_MARK_CACHE_POLICY = "SLRU";
static constexpr auto DEFAULT_INDEX_MARK_CACHE_MAX_SIZE = 0_MiB; static constexpr auto DEFAULT_INDEX_MARK_CACHE_MAX_SIZE = 5368_MiB;
static constexpr auto DEFAULT_INDEX_MARK_CACHE_SIZE_RATIO = 0.5l; static constexpr auto DEFAULT_INDEX_MARK_CACHE_SIZE_RATIO = 0.3;
static constexpr auto DEFAULT_MMAP_CACHE_MAX_SIZE = 1_KiB; /// chosen by rolling dice static constexpr auto DEFAULT_MMAP_CACHE_MAX_SIZE = 1_KiB; /// chosen by rolling dice
static constexpr auto DEFAULT_COMPILED_EXPRESSION_CACHE_MAX_SIZE = 128_MiB; static constexpr auto DEFAULT_COMPILED_EXPRESSION_CACHE_MAX_SIZE = 128_MiB;
static constexpr auto DEFAULT_COMPILED_EXPRESSION_CACHE_MAX_ENTRIES = 10'000; static constexpr auto DEFAULT_COMPILED_EXPRESSION_CACHE_MAX_ENTRIES = 10'000;

View File

@ -681,6 +681,7 @@ class IColumn;
M(Bool, query_plan_aggregation_in_order, true, "Use query plan for aggregation-in-order optimisation", 0) \ M(Bool, query_plan_aggregation_in_order, true, "Use query plan for aggregation-in-order optimisation", 0) \
M(Bool, query_plan_remove_redundant_sorting, true, "Remove redundant sorting in query plan. For example, sorting steps related to ORDER BY clauses in subqueries", 0) \ M(Bool, query_plan_remove_redundant_sorting, true, "Remove redundant sorting in query plan. For example, sorting steps related to ORDER BY clauses in subqueries", 0) \
M(Bool, query_plan_remove_redundant_distinct, true, "Remove redundant Distinct step in query plan", 0) \ M(Bool, query_plan_remove_redundant_distinct, true, "Remove redundant Distinct step in query plan", 0) \
M(Bool, query_plan_enable_multithreading_after_window_functions, true, "Enable multithreading after evaluating window functions to allow parallel stream processing", 0) \
M(UInt64, regexp_max_matches_per_row, 1000, "Max matches of any single regexp per row, used to safeguard 'extractAllGroupsHorizontal' against consuming too much memory with greedy RE.", 0) \ M(UInt64, regexp_max_matches_per_row, 1000, "Max matches of any single regexp per row, used to safeguard 'extractAllGroupsHorizontal' against consuming too much memory with greedy RE.", 0) \
\ \
M(UInt64, limit, 0, "Limit on read rows from the most 'end' result for select query, default 0 means no limit length", 0) \ M(UInt64, limit, 0, "Limit on read rows from the most 'end' result for select query, default 0 means no limit length", 0) \
@ -813,7 +814,7 @@ class IColumn;
M(Bool, create_table_empty_primary_key_by_default, false, "Allow to create *MergeTree tables with empty primary key when ORDER BY and PRIMARY KEY not specified", 0) \ M(Bool, create_table_empty_primary_key_by_default, false, "Allow to create *MergeTree tables with empty primary key when ORDER BY and PRIMARY KEY not specified", 0) \
// End of COMMON_SETTINGS // End of COMMON_SETTINGS
// Please add settings related to formats into the FORMAT_FACTORY_SETTINGS, move obsolete settings to OBSOLETE_SETTINGS and obsolete format settings to OBSOLETE_FORMAT_SETTINGS. // Please add settings related to formats into the FORMAT_FACTORY_SETTINGS, move obsolete settings to OBSOLETE_SETTINGS and obsolete format settings to OBSOLETE_FORMAT_SETTINGS.
#define MAKE_OBSOLETE(M, TYPE, NAME, DEFAULT) \ #define MAKE_OBSOLETE(M, TYPE, NAME, DEFAULT) \
M(TYPE, NAME, DEFAULT, "Obsolete setting, does nothing.", BaseSettingsHelpers::Flags::OBSOLETE) M(TYPE, NAME, DEFAULT, "Obsolete setting, does nothing.", BaseSettingsHelpers::Flags::OBSOLETE)
@ -1077,6 +1078,8 @@ class IColumn;
\ \
M(Bool, dictionary_use_async_executor, false, "Execute a pipeline for reading dictionary source in several threads. It's supported only by dictionaries with local CLICKHOUSE source.", 0) \ M(Bool, dictionary_use_async_executor, false, "Execute a pipeline for reading dictionary source in several threads. It's supported only by dictionaries with local CLICKHOUSE source.", 0) \
M(Bool, precise_float_parsing, false, "Prefer more precise (but slower) float parsing algorithm", 0) \ M(Bool, precise_float_parsing, false, "Prefer more precise (but slower) float parsing algorithm", 0) \
M(DateTimeOverflowBehavior, date_time_overflow_behavior, "ignore", "Overflow mode for Date, Date32, DateTime, DateTime64 types. Possible values: 'ignore', 'throw', 'saturate'.", 0) \
// End of FORMAT_FACTORY_SETTINGS // End of FORMAT_FACTORY_SETTINGS
// Please add settings non-related to formats into the COMMON_SETTINGS above. // Please add settings non-related to formats into the COMMON_SETTINGS above.

View File

@ -190,4 +190,9 @@ IMPLEMENT_SETTING_ENUM(ExternalCommandStderrReaction, ErrorCodes::BAD_ARGUMENTS,
{"log_last", ExternalCommandStderrReaction::LOG_LAST}, {"log_last", ExternalCommandStderrReaction::LOG_LAST},
{"throw", ExternalCommandStderrReaction::THROW}}) {"throw", ExternalCommandStderrReaction::THROW}})
IMPLEMENT_SETTING_ENUM(DateTimeOverflowBehavior, ErrorCodes::BAD_ARGUMENTS,
{{"throw", FormatSettings::DateTimeOverflowBehavior::Throw},
{"ignore", FormatSettings::DateTimeOverflowBehavior::Ignore},
{"saturate", FormatSettings::DateTimeOverflowBehavior::Saturate}})
} }

View File

@ -242,4 +242,6 @@ DECLARE_SETTING_ENUM(S3QueueAction)
DECLARE_SETTING_ENUM(ExternalCommandStderrReaction) DECLARE_SETTING_ENUM(ExternalCommandStderrReaction)
DECLARE_SETTING_ENUM_WITH_RENAME(DateTimeOverflowBehavior, FormatSettings::DateTimeOverflowBehavior)
} }

View File

@ -227,6 +227,7 @@ FormatSettings getFormatSettings(ContextPtr context, const Settings & settings)
format_settings.native.allow_types_conversion = settings.input_format_native_allow_types_conversion; format_settings.native.allow_types_conversion = settings.input_format_native_allow_types_conversion;
format_settings.max_parser_depth = context->getSettingsRef().max_parser_depth; format_settings.max_parser_depth = context->getSettingsRef().max_parser_depth;
format_settings.client_protocol_version = context->getClientProtocolVersion(); format_settings.client_protocol_version = context->getClientProtocolVersion();
format_settings.date_time_overflow_behavior = settings.date_time_overflow_behavior;
/// Validate avro_schema_registry_url with RemoteHostFilter when non-empty and in Server context /// Validate avro_schema_registry_url with RemoteHostFilter when non-empty and in Server context
if (format_settings.schema.is_server) if (format_settings.schema.is_server)

View File

@ -88,6 +88,15 @@ struct FormatSettings
IntervalOutputFormat output_format = IntervalOutputFormat::Numeric; IntervalOutputFormat output_format = IntervalOutputFormat::Numeric;
} interval; } interval;
enum class DateTimeOverflowBehavior
{
Ignore,
Throw,
Saturate
};
DateTimeOverflowBehavior date_time_overflow_behavior = DateTimeOverflowBehavior::Ignore;
bool input_format_ipv4_default_on_conversion_error = false; bool input_format_ipv4_default_on_conversion_error = false;
bool input_format_ipv6_default_on_conversion_error = false; bool input_format_ipv6_default_on_conversion_error = false;

View File

@ -22,5 +22,4 @@ void throwDate32IsNotSupported(const char * name)
{ {
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal type Date32 of argument for function {}", name); throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal type Date32 of argument for function {}", name);
} }
} }

View File

@ -23,12 +23,15 @@ namespace DB
static constexpr auto microsecond_multiplier = 1000000; static constexpr auto microsecond_multiplier = 1000000;
static constexpr auto millisecond_multiplier = 1000; static constexpr auto millisecond_multiplier = 1000;
static constexpr FormatSettings::DateTimeOverflowBehavior default_date_time_overflow_behavior = FormatSettings::DateTimeOverflowBehavior::Ignore;
namespace ErrorCodes namespace ErrorCodes
{ {
extern const int CANNOT_CONVERT_TYPE; extern const int CANNOT_CONVERT_TYPE;
extern const int DECIMAL_OVERFLOW; extern const int DECIMAL_OVERFLOW;
extern const int ILLEGAL_COLUMN; extern const int ILLEGAL_COLUMN;
extern const int ILLEGAL_TYPE_OF_ARGUMENT; extern const int ILLEGAL_TYPE_OF_ARGUMENT;
extern const int VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE;
} }
/** Transformations. /** Transformations.
@ -44,6 +47,12 @@ namespace ErrorCodes
* factor-transformation F is "round to the nearest month" (2015-02-03 -> 2015-02-01). * factor-transformation F is "round to the nearest month" (2015-02-03 -> 2015-02-01).
*/ */
constexpr time_t MAX_DATETIME64_TIMESTAMP = 10413791999LL; // 1900-01-01 00:00:00 UTC
constexpr time_t MIN_DATETIME64_TIMESTAMP = -2208988800LL; // 2299-12-31 23:59:59 UTC
constexpr time_t MAX_DATETIME_TIMESTAMP = 0xFFFFFFFF;
constexpr time_t MAX_DATE_TIMESTAMP = 5662310399; // 2149-06-06 23:59:59 UTC
constexpr time_t MAX_DATETIME_DAY_NUM = 49710; // 2106-02-07
[[noreturn]] void throwDateIsNotSupported(const char * name); [[noreturn]] void throwDateIsNotSupported(const char * name);
[[noreturn]] void throwDateTimeIsNotSupported(const char * name); [[noreturn]] void throwDateTimeIsNotSupported(const char * name);
[[noreturn]] void throwDate32IsNotSupported(const char * name); [[noreturn]] void throwDate32IsNotSupported(const char * name);
@ -57,25 +66,51 @@ struct ZeroTransform
static UInt16 execute(UInt16, const DateLUTImpl &) { return 0; } static UInt16 execute(UInt16, const DateLUTImpl &) { return 0; }
}; };
template <FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior = default_date_time_overflow_behavior>
struct ToDateImpl struct ToDateImpl
{ {
static constexpr auto name = "toDate"; static constexpr auto name = "toDate";
static UInt16 execute(const DecimalUtils::DecimalComponents<DateTime64> & t, const DateLUTImpl & time_zone) static UInt16 execute(const DecimalUtils::DecimalComponents<DateTime64> & t, const DateLUTImpl & time_zone)
{ {
return static_cast<UInt16>(time_zone.toDayNum(t.whole)); return execute(t.whole, time_zone);
} }
static UInt16 execute(Int64 t, const DateLUTImpl & time_zone) static UInt16 execute(Int64 t, const DateLUTImpl & time_zone)
{ {
return UInt16(time_zone.toDayNum(t)); if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Saturate)
{
if (t < 0)
t = 0;
else if (t > MAX_DATE_TIMESTAMP)
t = MAX_DATE_TIMESTAMP;
}
else if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Throw)
{
if (t < 0 || t > MAX_DATE_TIMESTAMP) [[unlikely]]
throw Exception(ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, "Value {} is out of bounds of type Date", t);
}
return static_cast<UInt16>(time_zone.toDayNum(t));
} }
static UInt16 execute(UInt32 t, const DateLUTImpl & time_zone) static UInt16 execute(UInt32 t, const DateLUTImpl & time_zone)
{ {
return UInt16(time_zone.toDayNum(t)); return UInt16(time_zone.toDayNum(t)); /// never causes overflow by design
} }
static UInt16 execute(Int32, const DateLUTImpl &) static UInt16 execute(Int32 t, const DateLUTImpl &)
{ {
throwDateIsNotSupported(name); if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Saturate)
{
if (t < 0)
return UInt16(0);
else if (t > DATE_LUT_MAX_DAY_NUM)
return UInt16(DATE_LUT_MAX_DAY_NUM);
}
else if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Throw)
{
if (t < 0 || t > DATE_LUT_MAX_DAY_NUM) [[unlikely]]
throw Exception(ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, "Value {} is out of bounds of type Date", t);
}
return static_cast<UInt16>(t);
} }
static UInt16 execute(UInt16 d, const DateLUTImpl &) static UInt16 execute(UInt16 d, const DateLUTImpl &)
{ {
@ -750,7 +785,7 @@ struct ToTimeImpl
} }
static constexpr bool hasPreimage() { return false; } static constexpr bool hasPreimage() { return false; }
using FactorTransform = ToDateImpl; using FactorTransform = ToDateImpl<>;
}; };
struct ToStartOfMinuteImpl struct ToStartOfMinuteImpl
@ -1401,7 +1436,7 @@ struct ToHourImpl
} }
static constexpr bool hasPreimage() { return false; } static constexpr bool hasPreimage() { return false; }
using FactorTransform = ToDateImpl; using FactorTransform = ToDateImpl<>;
}; };
struct TimezoneOffsetImpl struct TimezoneOffsetImpl

View File

@ -1279,8 +1279,15 @@ public:
bool date_and_datetime = (which_left.idx != which_right.idx) && (which_left.isDate() || which_left.isDate32() || which_left.isDateTime() || which_left.isDateTime64()) bool date_and_datetime = (which_left.idx != which_right.idx) && (which_left.isDate() || which_left.isDate32() || which_left.isDateTime() || which_left.isDateTime64())
&& (which_right.isDate() || which_right.isDate32() || which_right.isDateTime() || which_right.isDateTime64()); && (which_right.isDate() || which_right.isDate32() || which_right.isDateTime() || which_right.isDateTime64());
/// Interval data types can be compared only when having equal units.
bool left_is_interval = which_left.isInterval();
bool right_is_interval = which_right.isInterval();
bool types_equal = left_type->equals(*right_type);
ColumnPtr res; ColumnPtr res;
if (left_is_num && right_is_num && !date_and_datetime) if (left_is_num && right_is_num && !date_and_datetime
&& (!left_is_interval || !right_is_interval || types_equal))
{ {
if (!((res = executeNumLeftType<UInt8>(col_left_untyped, col_right_untyped)) if (!((res = executeNumLeftType<UInt8>(col_left_untyped, col_right_untyped))
|| (res = executeNumLeftType<UInt16>(col_left_untyped, col_right_untyped)) || (res = executeNumLeftType<UInt16>(col_left_untyped, col_right_untyped))
@ -1372,7 +1379,7 @@ public:
throw Exception(ErrorCodes::LOGICAL_ERROR, "Date related common types can only be UInt32/UInt64/Int32/Decimal"); throw Exception(ErrorCodes::LOGICAL_ERROR, "Date related common types can only be UInt32/UInt64/Int32/Decimal");
return res; return res;
} }
else if (left_type->equals(*right_type)) else if (types_equal)
{ {
return executeGenericIdenticalTypes(col_left_untyped, col_right_untyped); return executeGenericIdenticalTypes(col_left_untyped, col_right_untyped);
} }

View File

@ -90,9 +90,9 @@ namespace ErrorCodes
extern const int NOT_IMPLEMENTED; extern const int NOT_IMPLEMENTED;
extern const int CANNOT_INSERT_NULL_IN_ORDINARY_COLUMN; extern const int CANNOT_INSERT_NULL_IN_ORDINARY_COLUMN;
extern const int CANNOT_PARSE_BOOL; extern const int CANNOT_PARSE_BOOL;
extern const int VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE;
} }
/** Type conversion functions. /** Type conversion functions.
* toType - conversion in "natural way"; * toType - conversion in "natural way";
*/ */
@ -133,7 +133,9 @@ struct ConvertReturnZeroOnErrorTag {};
/** Conversion of number types to each other, enums to numbers, dates and datetimes to numbers and back: done by straight assignment. /** Conversion of number types to each other, enums to numbers, dates and datetimes to numbers and back: done by straight assignment.
* (Date is represented internally as number of days from some day; DateTime - as unix timestamp) * (Date is represented internally as number of days from some day; DateTime - as unix timestamp)
*/ */
template <typename FromDataType, typename ToDataType, typename Name, typename SpecialTag = ConvertDefaultBehaviorTag> template <typename FromDataType, typename ToDataType, typename Name,
typename SpecialTag = ConvertDefaultBehaviorTag,
FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior = default_date_time_overflow_behavior>
struct ConvertImpl struct ConvertImpl
{ {
using FromFieldType = typename FromDataType::FieldType; using FromFieldType = typename FromDataType::FieldType;
@ -388,28 +390,53 @@ struct ConvertImpl
/** Conversion of DateTime to Date: throw off time component. /** Conversion of DateTime to Date: throw off time component.
*/ */
template <typename Name> struct ConvertImpl<DataTypeDateTime, DataTypeDate, Name, ConvertDefaultBehaviorTag> template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeDateTime, DataTypeDate, ToDateImpl> {}; struct ConvertImpl<DataTypeDateTime, DataTypeDate, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeDateTime, DataTypeDate, ToDateImpl<date_time_overflow_behavior>, false> {};
/** Conversion of DateTime to Date32: throw off time component. /** Conversion of DateTime to Date32: throw off time component.
*/ */
template <typename Name> struct ConvertImpl<DataTypeDateTime, DataTypeDate32, Name, ConvertDefaultBehaviorTag> template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeDateTime, DataTypeDate32, ToDate32Impl> {}; struct ConvertImpl<DataTypeDateTime, DataTypeDate32, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeDateTime, DataTypeDate32, ToDate32Impl, false> {};
/** Conversion of Date to DateTime: adding 00:00:00 time component. /** Conversion of Date to DateTime: adding 00:00:00 time component.
*/ */
template <FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior = default_date_time_overflow_behavior>
struct ToDateTimeImpl struct ToDateTimeImpl
{ {
static constexpr auto name = "toDateTime"; static constexpr auto name = "toDateTime";
static UInt32 execute(UInt16 d, const DateLUTImpl & time_zone) static UInt32 execute(UInt16 d, const DateLUTImpl & time_zone)
{ {
if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Throw)
{
if (d > MAX_DATETIME_DAY_NUM) [[unlikely]]
throw Exception(ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, "Day number {} is out of bounds of type DateTime", d);
}
else if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Saturate)
{
if (d > MAX_DATETIME_DAY_NUM)
d = MAX_DATETIME_DAY_NUM;
}
return static_cast<UInt32>(time_zone.fromDayNum(DayNum(d))); return static_cast<UInt32>(time_zone.fromDayNum(DayNum(d)));
} }
static Int64 execute(Int32 d, const DateLUTImpl & time_zone) static UInt32 execute(Int32 d, const DateLUTImpl & time_zone)
{ {
return time_zone.fromDayNum(ExtendedDayNum(d)); if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Saturate)
{
if (d < 0)
return 0;
else if (d > MAX_DATETIME_DAY_NUM)
d = MAX_DATETIME_DAY_NUM;
}
else if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Throw)
{
if (d < 0 || d > MAX_DATETIME_DAY_NUM) [[unlikely]]
throw Exception(ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, "Value {} is out of bounds of type DateTime", d);
}
return static_cast<UInt32>(time_zone.fromDayNum(ExtendedDayNum(d)));
} }
static UInt32 execute(UInt32 dt, const DateLUTImpl & /*time_zone*/) static UInt32 execute(UInt32 dt, const DateLUTImpl & /*time_zone*/)
@ -417,36 +444,63 @@ struct ToDateTimeImpl
return dt; return dt;
} }
// TODO: return UInt32 ??? static UInt32 execute(Int64 dt64, const DateLUTImpl & /*time_zone*/)
static Int64 execute(Int64 dt64, const DateLUTImpl & /*time_zone*/)
{ {
return dt64; if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Ignore)
return static_cast<UInt32>(dt64);
else
{
if (dt64 < 0 || dt64 >= MAX_DATETIME_TIMESTAMP)
{
if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Saturate)
return dt64 < 0 ? 0 : std::numeric_limits<UInt32>::max();
else
throw Exception(ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, "Value {} is out of bounds of type DateTime", dt64);
}
else
return static_cast<UInt32>(dt64);
}
} }
}; };
template <typename Name> struct ConvertImpl<DataTypeDate, DataTypeDateTime, Name, ConvertDefaultBehaviorTag> template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeDate, DataTypeDateTime, ToDateTimeImpl> {}; struct ConvertImpl<DataTypeDate, DataTypeDateTime, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeDate, DataTypeDateTime, ToDateTimeImpl<date_time_overflow_behavior>, false> {};
template <typename Name> struct ConvertImpl<DataTypeDate32, DataTypeDateTime, Name, ConvertDefaultBehaviorTag> template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeDate32, DataTypeDateTime, ToDateTimeImpl> {}; struct ConvertImpl<DataTypeDate32, DataTypeDateTime, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeDate32, DataTypeDateTime, ToDateTimeImpl<date_time_overflow_behavior>, false> {};
/// Implementation of toDate function. /// Implementation of toDate function.
template <typename FromType, typename ToType> template <typename FromType, typename ToType, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ToDateTransform32Or64 struct ToDateTransform32Or64
{ {
static constexpr auto name = "toDate"; static constexpr auto name = "toDate";
static NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl & time_zone) static NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl & time_zone)
{ {
// since converting to Date, no need in values outside of default LUT range. if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Throw)
return (from <= DATE_LUT_MAX_DAY_NUM) {
? from if (from > MAX_DATETIME_TIMESTAMP) [[unlikely]]
: time_zone.toDayNum(std::min(time_t(from), time_t(0xFFFFFFFF))); throw Exception(ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, "Value {} is out of bounds of type Date", from);
}
/// if value is smaller (or equal) than maximum day value for Date, than treat it as day num,
/// otherwise treat it as unix timestamp. This is a bit weird, but we leave this behavior.
if (from <= DATE_LUT_MAX_DAY_NUM)
return from;
else
return time_zone.toDayNum(std::min(time_t(from), time_t(MAX_DATETIME_TIMESTAMP)));
} }
}; };
template <typename FromType, typename ToType> /** Conversion of Date32 to Date.
*/
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeDate32, DataTypeDate, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeDate32, DataTypeDate, ToDateImpl<date_time_overflow_behavior>, false> {};
template <typename FromType, typename ToType, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ToDateTransform32Or64Signed struct ToDateTransform32Or64Signed
{ {
static constexpr auto name = "toDate"; static constexpr auto name = "toDate";
@ -454,16 +508,23 @@ struct ToDateTransform32Or64Signed
static NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl & time_zone) static NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl & time_zone)
{ {
// TODO: decide narrow or extended range based on FromType // TODO: decide narrow or extended range based on FromType
/// The function should be monotonic (better for query optimizations), so we saturate instead of overflow. if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Throw)
if (from < 0) {
return 0; if (from < 0 || from > MAX_DATE_TIMESTAMP) [[unlikely]]
throw Exception(ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, "Value {} is out of bounds of type Date", from);
}
else
{
if (from < 0)
return 0;
}
return (from <= DATE_LUT_MAX_DAY_NUM) return (from <= DATE_LUT_MAX_DAY_NUM)
? static_cast<ToType>(from) ? static_cast<ToType>(from)
: time_zone.toDayNum(std::min(time_t(from), time_t(0xFFFFFFFF))); : time_zone.toDayNum(std::min(time_t(from), time_t(MAX_DATE_TIMESTAMP)));
} }
}; };
template <typename FromType, typename ToType> template <typename FromType, typename ToType, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ToDateTransform8Or16Signed struct ToDateTransform8Or16Signed
{ {
static constexpr auto name = "toDate"; static constexpr auto name = "toDate";
@ -471,30 +532,44 @@ struct ToDateTransform8Or16Signed
static NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl &) static NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl &)
{ {
if (from < 0) if (from < 0)
return 0; {
if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Throw)
throw Exception(ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, "Value {} is out of bounds of type Date", from);
else
return 0;
}
return from; return from;
} }
}; };
template <typename Name> struct ConvertImpl<DataTypeDateTime64, DataTypeDate32, Name, ConvertDefaultBehaviorTag> template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeDateTime64, DataTypeDate32, TransformDateTime64<ToDate32Impl>> {}; struct ConvertImpl<DataTypeDateTime64, DataTypeDate32, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeDateTime64, DataTypeDate32, TransformDateTime64<ToDate32Impl>, false> {};
/// Implementation of toDate32 function. /// Implementation of toDate32 function.
template <typename FromType, typename ToType> template <typename FromType, typename ToType, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ToDate32Transform32Or64 struct ToDate32Transform32Or64
{ {
static constexpr auto name = "toDate32"; static constexpr auto name = "toDate32";
static NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl & time_zone) static NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl & time_zone)
{ {
return (from < DATE_LUT_MAX_EXTEND_DAY_NUM) if (from < DATE_LUT_MAX_EXTEND_DAY_NUM)
? static_cast<ToType>(from) return static_cast<ToType>(from);
: time_zone.toDayNum(std::min(time_t(from), time_t(0xFFFFFFFF))); else
{
if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Throw)
{
if (from > MAX_DATETIME64_TIMESTAMP) [[unlikely]]
throw Exception(ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, "Timestamp value {} is out of bounds of type Date32", from);
}
return time_zone.toDayNum(std::min(time_t(from), time_t(MAX_DATETIME64_TIMESTAMP)));
}
} }
}; };
template <typename FromType, typename ToType> template <typename FromType, typename ToType, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ToDate32Transform32Or64Signed struct ToDate32Transform32Or64Signed
{ {
static constexpr auto name = "toDate32"; static constexpr auto name = "toDate32";
@ -502,11 +577,19 @@ struct ToDate32Transform32Or64Signed
static NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl & time_zone) static NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl & time_zone)
{ {
static const Int32 daynum_min_offset = -static_cast<Int32>(time_zone.getDayNumOffsetEpoch()); static const Int32 daynum_min_offset = -static_cast<Int32>(time_zone.getDayNumOffsetEpoch());
if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Throw)
{
if (from < daynum_min_offset || from > MAX_DATETIME64_TIMESTAMP) [[unlikely]]
throw Exception(ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, "Timestamp value {} is out of bounds of type Date32", from);
}
if (from < daynum_min_offset) if (from < daynum_min_offset)
return daynum_min_offset; return daynum_min_offset;
return (from < DATE_LUT_MAX_EXTEND_DAY_NUM) return (from < DATE_LUT_MAX_EXTEND_DAY_NUM)
? static_cast<ToType>(from) ? static_cast<ToType>(from)
: time_zone.toDayNum(std::min(time_t(Int64(from)), time_t(0xFFFFFFFF))); : time_zone.toDayNum(std::min(time_t(Int64(from)), time_t(MAX_DATETIME64_TIMESTAMP)));
} }
}; };
@ -522,7 +605,7 @@ struct ToDate32Transform8Or16Signed
}; };
/** Special case of converting Int8, Int16, (U)Int32 or (U)Int64 (and also, for convenience, /** Special case of converting Int8, Int16, (U)Int32 or (U)Int64 (and also, for convenience,
* Float32, Float64) to Date. If the number is negative, saturate it to unix epoch time. If the * Float32, Float64) to Date. If the
* number is less than 65536, then it is treated as DayNum, and if it's greater or equals to 65536, * number is less than 65536, then it is treated as DayNum, and if it's greater or equals to 65536,
* then treated as unix timestamp. If the number exceeds UInt32, saturate to MAX_UINT32 then as DayNum. * then treated as unix timestamp. If the number exceeds UInt32, saturate to MAX_UINT32 then as DayNum.
* It's a bit illogical, as we actually have two functions in one. * It's a bit illogical, as we actually have two functions in one.
@ -530,53 +613,89 @@ struct ToDate32Transform8Or16Signed
* when user write toDate(UInt32), expecting conversion of unix timestamp to Date. * when user write toDate(UInt32), expecting conversion of unix timestamp to Date.
* (otherwise such usage would be frequent mistake). * (otherwise such usage would be frequent mistake).
*/ */
template <typename Name> struct ConvertImpl<DataTypeUInt32, DataTypeDate, Name, ConvertDefaultBehaviorTag> template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeUInt32, DataTypeDate, ToDateTransform32Or64<UInt32, UInt16>> {}; struct ConvertImpl<DataTypeUInt32, DataTypeDate, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
template <typename Name> struct ConvertImpl<DataTypeUInt64, DataTypeDate, Name, ConvertDefaultBehaviorTag> : DateTimeTransformImpl<DataTypeUInt32, DataTypeDate, ToDateTransform32Or64<UInt32, UInt16, default_date_time_overflow_behavior>, false> {};
: DateTimeTransformImpl<DataTypeUInt64, DataTypeDate, ToDateTransform32Or64<UInt64, UInt16>> {};
template <typename Name> struct ConvertImpl<DataTypeInt8, DataTypeDate, Name, ConvertDefaultBehaviorTag>
: DateTimeTransformImpl<DataTypeInt8, DataTypeDate, ToDateTransform8Or16Signed<Int8, UInt16>> {};
template <typename Name> struct ConvertImpl<DataTypeInt16, DataTypeDate, Name, ConvertDefaultBehaviorTag>
: DateTimeTransformImpl<DataTypeInt16, DataTypeDate, ToDateTransform8Or16Signed<Int16, UInt16>> {};
template <typename Name> struct ConvertImpl<DataTypeInt32, DataTypeDate, Name, ConvertDefaultBehaviorTag>
: DateTimeTransformImpl<DataTypeInt32, DataTypeDate, ToDateTransform32Or64Signed<Int32, UInt16>> {};
template <typename Name> struct ConvertImpl<DataTypeInt64, DataTypeDate, Name, ConvertDefaultBehaviorTag>
: DateTimeTransformImpl<DataTypeInt64, DataTypeDate, ToDateTransform32Or64Signed<Int64, UInt16>> {};
template <typename Name> struct ConvertImpl<DataTypeFloat32, DataTypeDate, Name, ConvertDefaultBehaviorTag>
: DateTimeTransformImpl<DataTypeFloat32, DataTypeDate, ToDateTransform32Or64Signed<Float32, UInt16>> {};
template <typename Name> struct ConvertImpl<DataTypeFloat64, DataTypeDate, Name, ConvertDefaultBehaviorTag>
: DateTimeTransformImpl<DataTypeFloat64, DataTypeDate, ToDateTransform32Or64Signed<Float64, UInt16>> {};
template <typename Name> struct ConvertImpl<DataTypeUInt32, DataTypeDate32, Name, ConvertDefaultBehaviorTag> template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeUInt32, DataTypeDate32, ToDate32Transform32Or64<UInt32, Int32>> {}; struct ConvertImpl<DataTypeUInt64, DataTypeDate, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
template <typename Name> struct ConvertImpl<DataTypeUInt64, DataTypeDate32, Name, ConvertDefaultBehaviorTag> : DateTimeTransformImpl<DataTypeUInt64, DataTypeDate, ToDateTransform32Or64<UInt64, UInt16, default_date_time_overflow_behavior>, false> {};
: DateTimeTransformImpl<DataTypeUInt64, DataTypeDate32, ToDate32Transform32Or64<UInt64, Int32>> {};
template <typename Name> struct ConvertImpl<DataTypeInt8, DataTypeDate32, Name, ConvertDefaultBehaviorTag> template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeInt8, DataTypeDate32, ToDate32Transform8Or16Signed<Int8, Int32>> {}; struct ConvertImpl<DataTypeInt8, DataTypeDate, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
template <typename Name> struct ConvertImpl<DataTypeInt16, DataTypeDate32, Name, ConvertDefaultBehaviorTag> : DateTimeTransformImpl<DataTypeInt8, DataTypeDate, ToDateTransform8Or16Signed<Int8, UInt16, default_date_time_overflow_behavior>, false> {};
: DateTimeTransformImpl<DataTypeInt16, DataTypeDate32, ToDate32Transform8Or16Signed<Int16, Int32>> {};
template <typename Name> struct ConvertImpl<DataTypeInt32, DataTypeDate32, Name, ConvertDefaultBehaviorTag> template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeInt32, DataTypeDate32, ToDate32Transform32Or64Signed<Int32, Int32>> {}; struct ConvertImpl<DataTypeInt16, DataTypeDate, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
template <typename Name> struct ConvertImpl<DataTypeInt64, DataTypeDate32, Name, ConvertDefaultBehaviorTag> : DateTimeTransformImpl<DataTypeInt16, DataTypeDate, ToDateTransform8Or16Signed<Int16, UInt16, default_date_time_overflow_behavior>, false> {};
: DateTimeTransformImpl<DataTypeInt64, DataTypeDate32, ToDate32Transform32Or64Signed<Int64, Int32>> {};
template <typename Name> struct ConvertImpl<DataTypeFloat32, DataTypeDate32, Name, ConvertDefaultBehaviorTag> template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeFloat32, DataTypeDate32, ToDate32Transform32Or64Signed<Float32, Int32>> {}; struct ConvertImpl<DataTypeInt32, DataTypeDate, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
template <typename Name> struct ConvertImpl<DataTypeFloat64, DataTypeDate32, Name, ConvertDefaultBehaviorTag> : DateTimeTransformImpl<DataTypeInt32, DataTypeDate, ToDateTransform32Or64Signed<Int32, UInt16, default_date_time_overflow_behavior>, false> {};
: DateTimeTransformImpl<DataTypeFloat64, DataTypeDate32, ToDate32Transform32Or64Signed<Float64, Int32>> {};
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeInt64, DataTypeDate, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeInt64, DataTypeDate, ToDateTransform32Or64Signed<Int64, UInt16, default_date_time_overflow_behavior>, false> {};
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeFloat32, DataTypeDate, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeFloat32, DataTypeDate, ToDateTransform32Or64Signed<Float32, UInt16, default_date_time_overflow_behavior>, false> {};
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeFloat64, DataTypeDate, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeFloat64, DataTypeDate, ToDateTransform32Or64Signed<Float64, UInt16, default_date_time_overflow_behavior>, false> {};
template <typename FromType, typename ToType> template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeUInt32, DataTypeDate32, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeUInt32, DataTypeDate32, ToDate32Transform32Or64<UInt32, Int32, default_date_time_overflow_behavior>, false> {};
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeUInt64, DataTypeDate32, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeUInt64, DataTypeDate32, ToDate32Transform32Or64<UInt64, Int32, default_date_time_overflow_behavior>, false> {};
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeInt8, DataTypeDate32, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeInt8, DataTypeDate32, ToDate32Transform8Or16Signed<Int8, Int32>, false> {};
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeInt16, DataTypeDate32, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeInt16, DataTypeDate32, ToDate32Transform8Or16Signed<Int16, Int32>, false> {};
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeInt32, DataTypeDate32, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeInt32, DataTypeDate32, ToDate32Transform32Or64Signed<Int32, Int32, default_date_time_overflow_behavior>, false> {};
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeInt64, DataTypeDate32, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeInt64, DataTypeDate32, ToDate32Transform32Or64Signed<Int64, Int32, default_date_time_overflow_behavior>, false> {};
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeFloat32, DataTypeDate32, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeFloat32, DataTypeDate32, ToDate32Transform32Or64Signed<Float32, Int32, default_date_time_overflow_behavior>, false> {};
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeFloat64, DataTypeDate32, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeFloat64, DataTypeDate32, ToDate32Transform32Or64Signed<Float64, Int32, default_date_time_overflow_behavior>, false> {};
template <typename FromType, typename ToType, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ToDateTimeTransform64 struct ToDateTimeTransform64
{ {
static constexpr auto name = "toDateTime"; static constexpr auto name = "toDateTime";
static NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl &) static NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl &)
{ {
return static_cast<ToType>(std::min(time_t(from), time_t(0xFFFFFFFF))); if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Throw)
{
if (from > MAX_DATETIME_TIMESTAMP) [[unlikely]]
throw Exception(ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, "Timestamp value {} is out of bounds of type DateTime", from);
}
return static_cast<ToType>(std::min(time_t(from), time_t(MAX_DATETIME_TIMESTAMP)));
} }
}; };
template <typename FromType, typename ToType> template <typename FromType, typename ToType, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ToDateTimeTransformSigned struct ToDateTimeTransformSigned
{ {
static constexpr auto name = "toDateTime"; static constexpr auto name = "toDateTime";
@ -584,51 +703,68 @@ struct ToDateTimeTransformSigned
static NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl &) static NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl &)
{ {
if (from < 0) if (from < 0)
return 0; {
if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Throw)
throw Exception(ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, "Timestamp value {} is out of bounds of type DateTime", from);
else
return 0;
}
return from; return from;
} }
}; };
template <typename FromType, typename ToType> template <typename FromType, typename ToType, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ToDateTimeTransform64Signed struct ToDateTimeTransform64Signed
{ {
static constexpr auto name = "toDateTime"; static constexpr auto name = "toDateTime";
static NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl &) static NO_SANITIZE_UNDEFINED ToType execute(const FromType & from, const DateLUTImpl &)
{ {
if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Throw)
{
if (from < 0 || from > MAX_DATETIME_TIMESTAMP) [[unlikely]]
throw Exception(ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, "Timestamp value {} is out of bounds of type DateTime", from);
}
if (from < 0) if (from < 0)
return 0; return 0;
return static_cast<ToType>(std::min(time_t(from), time_t(0xFFFFFFFF))); return static_cast<ToType>(std::min(time_t(from), time_t(MAX_DATETIME_TIMESTAMP)));
} }
}; };
/** Special case of converting Int8, Int16, Int32 or (U)Int64 (and also, for convenience, Float32, /// Special case of converting Int8, Int16, Int32 or (U)Int64 (and also, for convenience, Float32, Float64) to DateTime.
* Float64) to DateTime. If the number is negative, saturate it to unix epoch time. If the number template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
* exceeds UInt32, saturate to MAX_UINT32. struct ConvertImpl<DataTypeInt8, DataTypeDateTime, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
*/ : DateTimeTransformImpl<DataTypeInt8, DataTypeDateTime, ToDateTimeTransformSigned<Int8, UInt32, default_date_time_overflow_behavior>, false> {};
template <typename Name> struct ConvertImpl<DataTypeInt8, DataTypeDateTime, Name>
: DateTimeTransformImpl<DataTypeInt8, DataTypeDateTime, ToDateTimeTransformSigned<Int8, UInt32>> {};
template <typename Name> struct ConvertImpl<DataTypeInt16, DataTypeDateTime, Name>
: DateTimeTransformImpl<DataTypeInt16, DataTypeDateTime, ToDateTimeTransformSigned<Int16, UInt32>> {};
template <typename Name> struct ConvertImpl<DataTypeInt32, DataTypeDateTime, Name>
: DateTimeTransformImpl<DataTypeInt32, DataTypeDateTime, ToDateTimeTransformSigned<Int32, UInt32>> {};
template <typename Name> struct ConvertImpl<DataTypeInt64, DataTypeDateTime, Name>
: DateTimeTransformImpl<DataTypeInt64, DataTypeDateTime, ToDateTimeTransform64Signed<Int64, UInt32>> {};
template <typename Name> struct ConvertImpl<DataTypeUInt64, DataTypeDateTime, Name>
: DateTimeTransformImpl<DataTypeUInt64, DataTypeDateTime, ToDateTimeTransform64<UInt64, UInt32>> {};
template <typename Name> struct ConvertImpl<DataTypeFloat32, DataTypeDateTime, Name>
: DateTimeTransformImpl<DataTypeFloat32, DataTypeDateTime, ToDateTimeTransform64Signed<Float32, UInt32>> {};
template <typename Name> struct ConvertImpl<DataTypeFloat64, DataTypeDateTime, Name>
: DateTimeTransformImpl<DataTypeFloat64, DataTypeDateTime, ToDateTimeTransform64Signed<Float64, UInt32>> {};
constexpr time_t LUT_MIN_TIME = -2208988800l; // 1900-01-01 UTC template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeInt16, DataTypeDateTime, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeInt16, DataTypeDateTime, ToDateTimeTransformSigned<Int16, UInt32, default_date_time_overflow_behavior>, false> {};
constexpr time_t LUT_MAX_TIME = 10413791999l; // 2299-12-31 UTC template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeInt32, DataTypeDateTime, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeInt32, DataTypeDateTime, ToDateTimeTransformSigned<Int32, UInt32, default_date_time_overflow_behavior>, false> {};
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeInt64, DataTypeDateTime, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeInt64, DataTypeDateTime, ToDateTimeTransform64Signed<Int64, UInt32, default_date_time_overflow_behavior>, false> {};
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeUInt64, DataTypeDateTime, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeUInt64, DataTypeDateTime, ToDateTimeTransform64<UInt64, UInt32, default_date_time_overflow_behavior>, false> {};
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeFloat32, DataTypeDateTime, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeFloat32, DataTypeDateTime, ToDateTimeTransform64Signed<Float32, UInt32, default_date_time_overflow_behavior>, false> {};
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeFloat64, DataTypeDateTime, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeFloat64, DataTypeDateTime, ToDateTimeTransform64Signed<Float64, UInt32, default_date_time_overflow_behavior>, false> {};
/** Conversion of numeric to DateTime64 /** Conversion of numeric to DateTime64
*/ */
template <typename FromType> template <typename FromType, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ToDateTime64TransformUnsigned struct ToDateTime64TransformUnsigned
{ {
static constexpr auto name = "toDateTime64"; static constexpr auto name = "toDateTime64";
@ -641,11 +777,18 @@ struct ToDateTime64TransformUnsigned
NO_SANITIZE_UNDEFINED DateTime64::NativeType execute(FromType from, const DateLUTImpl &) const NO_SANITIZE_UNDEFINED DateTime64::NativeType execute(FromType from, const DateLUTImpl &) const
{ {
from = std::min<time_t>(from, LUT_MAX_TIME); if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Throw)
return DecimalUtils::decimalFromComponentsWithMultiplier<DateTime64>(from, 0, scale_multiplier); {
if (from > MAX_DATETIME64_TIMESTAMP) [[unlikely]]
throw Exception(ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, "Timestamp value {} is out of bounds of type DateTime64", from);
else
return DecimalUtils::decimalFromComponentsWithMultiplier<DateTime64>(from, 0, scale_multiplier);
}
else
return DecimalUtils::decimalFromComponentsWithMultiplier<DateTime64>(std::min<time_t>(from, MAX_DATETIME64_TIMESTAMP), 0, scale_multiplier);
} }
}; };
template <typename FromType> template <typename FromType, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ToDateTime64TransformSigned struct ToDateTime64TransformSigned
{ {
static constexpr auto name = "toDateTime64"; static constexpr auto name = "toDateTime64";
@ -658,12 +801,18 @@ struct ToDateTime64TransformSigned
NO_SANITIZE_UNDEFINED DateTime64::NativeType execute(FromType from, const DateLUTImpl &) const NO_SANITIZE_UNDEFINED DateTime64::NativeType execute(FromType from, const DateLUTImpl &) const
{ {
from = static_cast<FromType>(std::max<time_t>(from, LUT_MIN_TIME)); if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Throw)
from = static_cast<FromType>(std::min<time_t>(from, LUT_MAX_TIME)); {
if (from < MIN_DATETIME64_TIMESTAMP || from > MAX_DATETIME64_TIMESTAMP) [[unlikely]]
throw Exception(ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, "Timestamp value {} is out of bounds of type DateTime64", from);
}
from = static_cast<FromType>(std::max<time_t>(from, MIN_DATETIME64_TIMESTAMP));
from = static_cast<FromType>(std::min<time_t>(from, MAX_DATETIME64_TIMESTAMP));
return DecimalUtils::decimalFromComponentsWithMultiplier<DateTime64>(from, 0, scale_multiplier); return DecimalUtils::decimalFromComponentsWithMultiplier<DateTime64>(from, 0, scale_multiplier);
} }
}; };
template <typename FromDataType, typename FromType> template <typename FromDataType, typename FromType, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ToDateTime64TransformFloat struct ToDateTime64TransformFloat
{ {
static constexpr auto name = "toDateTime64"; static constexpr auto name = "toDateTime64";
@ -676,26 +825,45 @@ struct ToDateTime64TransformFloat
NO_SANITIZE_UNDEFINED DateTime64::NativeType execute(FromType from, const DateLUTImpl &) const NO_SANITIZE_UNDEFINED DateTime64::NativeType execute(FromType from, const DateLUTImpl &) const
{ {
from = std::max(from, static_cast<FromType>(LUT_MIN_TIME)); if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Throw)
from = std::min(from, static_cast<FromType>(LUT_MAX_TIME)); {
if (from < MIN_DATETIME64_TIMESTAMP || from > MAX_DATETIME64_TIMESTAMP) [[unlikely]]
throw Exception(ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, "Timestamp value {} is out of bounds of type DateTime64", from);
}
from = std::max(from, static_cast<FromType>(MIN_DATETIME64_TIMESTAMP));
from = std::min(from, static_cast<FromType>(MAX_DATETIME64_TIMESTAMP));
return convertToDecimal<FromDataType, DataTypeDateTime64>(from, scale); return convertToDecimal<FromDataType, DataTypeDateTime64>(from, scale);
} }
}; };
template <typename Name> struct ConvertImpl<DataTypeInt8, DataTypeDateTime64, Name> template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeInt8, DataTypeDateTime64, ToDateTime64TransformSigned<Int8>> {}; struct ConvertImpl<DataTypeInt8, DataTypeDateTime64, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
template <typename Name> struct ConvertImpl<DataTypeInt16, DataTypeDateTime64, Name> : DateTimeTransformImpl<DataTypeInt8, DataTypeDateTime64, ToDateTime64TransformSigned<Int8, date_time_overflow_behavior>, false> {};
: DateTimeTransformImpl<DataTypeInt16, DataTypeDateTime64, ToDateTime64TransformSigned<Int16>> {};
template <typename Name> struct ConvertImpl<DataTypeInt32, DataTypeDateTime64, Name> template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeInt32, DataTypeDateTime64, ToDateTime64TransformSigned<Int32>> {}; struct ConvertImpl<DataTypeInt16, DataTypeDateTime64, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
template <typename Name> struct ConvertImpl<DataTypeInt64, DataTypeDateTime64, Name> : DateTimeTransformImpl<DataTypeInt16, DataTypeDateTime64, ToDateTime64TransformSigned<Int16, date_time_overflow_behavior>, false> {};
: DateTimeTransformImpl<DataTypeInt64, DataTypeDateTime64, ToDateTime64TransformSigned<Int64>> {};
template <typename Name> struct ConvertImpl<DataTypeUInt64, DataTypeDateTime64, Name> template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeUInt64, DataTypeDateTime64, ToDateTime64TransformUnsigned<UInt64>> {}; struct ConvertImpl<DataTypeInt32, DataTypeDateTime64, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
template <typename Name> struct ConvertImpl<DataTypeFloat32, DataTypeDateTime64, Name> : DateTimeTransformImpl<DataTypeInt32, DataTypeDateTime64, ToDateTime64TransformSigned<Int32, date_time_overflow_behavior>, false> {};
: DateTimeTransformImpl<DataTypeFloat32, DataTypeDateTime64, ToDateTime64TransformFloat<DataTypeFloat32, Float32>> {};
template <typename Name> struct ConvertImpl<DataTypeFloat64, DataTypeDateTime64, Name> template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeFloat64, DataTypeDateTime64, ToDateTime64TransformFloat<DataTypeFloat64, Float64>> {}; struct ConvertImpl<DataTypeInt64, DataTypeDateTime64, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeInt64, DataTypeDateTime64, ToDateTime64TransformSigned<Int64, date_time_overflow_behavior>, false> {};
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeUInt64, DataTypeDateTime64, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeUInt64, DataTypeDateTime64, ToDateTime64TransformUnsigned<UInt64, date_time_overflow_behavior>, false> {};
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeFloat32, DataTypeDateTime64, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeFloat32, DataTypeDateTime64, ToDateTime64TransformFloat<DataTypeFloat32, Float32, date_time_overflow_behavior>, false> {};
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeFloat64, DataTypeDateTime64, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeFloat64, DataTypeDateTime64, ToDateTime64TransformFloat<DataTypeFloat64, Float64, date_time_overflow_behavior>, false> {};
/** Conversion of DateTime64 to Date or DateTime: discards fractional part. /** Conversion of DateTime64 to Date or DateTime: discards fractional part.
@ -720,10 +888,13 @@ struct FromDateTime64Transform
/** Conversion of DateTime64 to Date or DateTime: discards fractional part. /** Conversion of DateTime64 to Date or DateTime: discards fractional part.
*/ */
template <typename Name> struct ConvertImpl<DataTypeDateTime64, DataTypeDate, Name, ConvertDefaultBehaviorTag> template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeDateTime64, DataTypeDate, TransformDateTime64<ToDateImpl>> {}; struct ConvertImpl<DataTypeDateTime64, DataTypeDate, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
template <typename Name> struct ConvertImpl<DataTypeDateTime64, DataTypeDateTime, Name, ConvertDefaultBehaviorTag> : DateTimeTransformImpl<DataTypeDateTime64, DataTypeDate, TransformDateTime64<ToDateImpl<date_time_overflow_behavior>>, false> {};
: DateTimeTransformImpl<DataTypeDateTime64, DataTypeDateTime, TransformDateTime64<ToDateTimeImpl>> {};
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeDateTime64, DataTypeDateTime, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeDateTime64, DataTypeDateTime, TransformDateTime64<ToDateTimeImpl<date_time_overflow_behavior>>, false> {};
struct ToDateTime64Transform struct ToDateTime64Transform
{ {
@ -737,13 +908,13 @@ struct ToDateTime64Transform
DateTime64::NativeType execute(UInt16 d, const DateLUTImpl & time_zone) const DateTime64::NativeType execute(UInt16 d, const DateLUTImpl & time_zone) const
{ {
const auto dt = ToDateTimeImpl::execute(d, time_zone); const auto dt = ToDateTimeImpl<>::execute(d, time_zone);
return execute(dt, time_zone); return execute(dt, time_zone);
} }
DateTime64::NativeType execute(Int32 d, const DateLUTImpl & time_zone) const DateTime64::NativeType execute(Int32 d, const DateLUTImpl & time_zone) const
{ {
const auto dt = ToDateTimeImpl::execute(d, time_zone); Int64 dt = static_cast<Int64>(time_zone.fromDayNum(ExtendedDayNum(d)));
return DecimalUtils::decimalFromComponentsWithMultiplier<DateTime64>(dt, 0, scale_multiplier); return DecimalUtils::decimalFromComponentsWithMultiplier<DateTime64>(dt, 0, scale_multiplier);
} }
@ -755,11 +926,16 @@ struct ToDateTime64Transform
/** Conversion of Date or DateTime to DateTime64: add zero sub-second part. /** Conversion of Date or DateTime to DateTime64: add zero sub-second part.
*/ */
template <typename Name> struct ConvertImpl<DataTypeDate, DataTypeDateTime64, Name, ConvertDefaultBehaviorTag> template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeDate, DataTypeDateTime64, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeDate, DataTypeDateTime64, ToDateTime64Transform> {}; : DateTimeTransformImpl<DataTypeDate, DataTypeDateTime64, ToDateTime64Transform> {};
template <typename Name> struct ConvertImpl<DataTypeDate32, DataTypeDateTime64, Name, ConvertDefaultBehaviorTag>
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeDate32, DataTypeDateTime64, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeDate32, DataTypeDateTime64, ToDateTime64Transform> {}; : DateTimeTransformImpl<DataTypeDate32, DataTypeDateTime64, ToDateTime64Transform> {};
template <typename Name> struct ConvertImpl<DataTypeDateTime, DataTypeDateTime64, Name, ConvertDefaultBehaviorTag>
template <typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct ConvertImpl<DataTypeDateTime, DataTypeDateTime64, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: DateTimeTransformImpl<DataTypeDateTime, DataTypeDateTime64, ToDateTime64Transform> {}; : DateTimeTransformImpl<DataTypeDateTime, DataTypeDateTime64, ToDateTime64Transform> {};
@ -1115,8 +1291,8 @@ inline void convertFromTime<DataTypeDateTime>(DataTypeDateTime::FieldType & x, t
{ {
if (unlikely(time < 0)) if (unlikely(time < 0))
x = 0; x = 0;
else if (unlikely(time > 0xFFFFFFFF)) else if (unlikely(time > MAX_DATETIME_TIMESTAMP))
x = 0xFFFFFFFF; x = MAX_DATETIME_TIMESTAMP;
else else
x = static_cast<UInt32>(time); x = static_cast<UInt32>(time);
} }
@ -1614,29 +1790,29 @@ struct ConvertThroughParsing
}; };
template <typename ToDataType, typename Name> template <typename ToDataType, typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
requires (!std::is_same_v<ToDataType, DataTypeString>) requires (!std::is_same_v<ToDataType, DataTypeString>)
struct ConvertImpl<DataTypeString, ToDataType, Name, ConvertDefaultBehaviorTag> struct ConvertImpl<DataTypeString, ToDataType, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: ConvertThroughParsing<DataTypeString, ToDataType, Name, ConvertFromStringExceptionMode::Throw, ConvertFromStringParsingMode::Normal> {}; : ConvertThroughParsing<DataTypeString, ToDataType, Name, ConvertFromStringExceptionMode::Throw, ConvertFromStringParsingMode::Normal> {};
template <typename ToDataType, typename Name> template <typename ToDataType, typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
requires (!std::is_same_v<ToDataType, DataTypeFixedString>) requires (!std::is_same_v<ToDataType, DataTypeFixedString>)
struct ConvertImpl<DataTypeFixedString, ToDataType, Name, ConvertDefaultBehaviorTag> struct ConvertImpl<DataTypeFixedString, ToDataType, Name, ConvertDefaultBehaviorTag, date_time_overflow_behavior>
: ConvertThroughParsing<DataTypeFixedString, ToDataType, Name, ConvertFromStringExceptionMode::Throw, ConvertFromStringParsingMode::Normal> {}; : ConvertThroughParsing<DataTypeFixedString, ToDataType, Name, ConvertFromStringExceptionMode::Throw, ConvertFromStringParsingMode::Normal> {};
template <typename ToDataType, typename Name> template <typename ToDataType, typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
requires (!std::is_same_v<ToDataType, DataTypeString>) requires (!std::is_same_v<ToDataType, DataTypeString>)
struct ConvertImpl<DataTypeString, ToDataType, Name, ConvertReturnNullOnErrorTag> struct ConvertImpl<DataTypeString, ToDataType, Name, ConvertReturnNullOnErrorTag, date_time_overflow_behavior>
: ConvertThroughParsing<DataTypeString, ToDataType, Name, ConvertFromStringExceptionMode::Null, ConvertFromStringParsingMode::Normal> {}; : ConvertThroughParsing<DataTypeString, ToDataType, Name, ConvertFromStringExceptionMode::Null, ConvertFromStringParsingMode::Normal> {};
template <typename ToDataType, typename Name> template <typename ToDataType, typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
requires (!std::is_same_v<ToDataType, DataTypeFixedString>) requires (!std::is_same_v<ToDataType, DataTypeFixedString>)
struct ConvertImpl<DataTypeFixedString, ToDataType, Name, ConvertReturnNullOnErrorTag> struct ConvertImpl<DataTypeFixedString, ToDataType, Name, ConvertReturnNullOnErrorTag, date_time_overflow_behavior>
: ConvertThroughParsing<DataTypeFixedString, ToDataType, Name, ConvertFromStringExceptionMode::Null, ConvertFromStringParsingMode::Normal> {}; : ConvertThroughParsing<DataTypeFixedString, ToDataType, Name, ConvertFromStringExceptionMode::Null, ConvertFromStringParsingMode::Normal> {};
template <typename FromDataType, typename ToDataType, typename Name> template <typename FromDataType, typename ToDataType, typename Name, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
requires (is_any_of<FromDataType, DataTypeString, DataTypeFixedString> && is_any_of<ToDataType, DataTypeIPv4, DataTypeIPv6>) requires (is_any_of<FromDataType, DataTypeString, DataTypeFixedString> && is_any_of<ToDataType, DataTypeIPv4, DataTypeIPv6>)
struct ConvertImpl<FromDataType, ToDataType, Name, ConvertReturnZeroOnErrorTag> struct ConvertImpl<FromDataType, ToDataType, Name, ConvertReturnZeroOnErrorTag, date_time_overflow_behavior>
: ConvertThroughParsing<FromDataType, ToDataType, Name, ConvertFromStringExceptionMode::Zero, ConvertFromStringParsingMode::Normal> {}; : ConvertThroughParsing<FromDataType, ToDataType, Name, ConvertFromStringExceptionMode::Zero, ConvertFromStringParsingMode::Normal> {};
/// Generic conversion of any type from String. Used for complex types: Array and Tuple or types with custom serialization. /// Generic conversion of any type from String. Used for complex types: Array and Tuple or types with custom serialization.
@ -2093,6 +2269,11 @@ private:
const DataTypePtr from_type = removeNullable(arguments[0].type); const DataTypePtr from_type = removeNullable(arguments[0].type);
ColumnPtr result_column; ColumnPtr result_column;
[[maybe_unused]] FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior = default_date_time_overflow_behavior;
if (context)
date_time_overflow_behavior = context->getSettingsRef().date_time_overflow_behavior.value;
auto call = [&](const auto & types, const auto & tag) -> bool auto call = [&](const auto & types, const auto & tag) -> bool
{ {
using Types = std::decay_t<decltype(types)>; using Types = std::decay_t<decltype(types)>;
@ -2116,13 +2297,42 @@ private:
const ColumnWithTypeAndName & scale_column = arguments[1]; const ColumnWithTypeAndName & scale_column = arguments[1];
UInt32 scale = extractToDecimalScale(scale_column); UInt32 scale = extractToDecimalScale(scale_column);
result_column = ConvertImpl<LeftDataType, RightDataType, Name, SpecialTag>::execute(arguments, result_type, input_rows_count, scale); switch (date_time_overflow_behavior)
{
case FormatSettings::DateTimeOverflowBehavior::Throw:
result_column = ConvertImpl<LeftDataType, RightDataType, Name, SpecialTag, FormatSettings::DateTimeOverflowBehavior::Throw>::execute(arguments, result_type, input_rows_count, scale);
break;
case FormatSettings::DateTimeOverflowBehavior::Ignore:
result_column = ConvertImpl<LeftDataType, RightDataType, Name, SpecialTag, FormatSettings::DateTimeOverflowBehavior::Ignore>::execute(arguments, result_type, input_rows_count, scale);
break;
case FormatSettings::DateTimeOverflowBehavior::Saturate:
result_column = ConvertImpl<LeftDataType, RightDataType, Name, SpecialTag, FormatSettings::DateTimeOverflowBehavior::Saturate>::execute(arguments, result_type, input_rows_count, scale);
break;
}
} }
else if constexpr (IsDataTypeDateOrDateTime<RightDataType> && std::is_same_v<LeftDataType, DataTypeDateTime64>) else if constexpr (IsDataTypeDateOrDateTime<RightDataType> && std::is_same_v<LeftDataType, DataTypeDateTime64>)
{ {
const auto * dt64 = assert_cast<const DataTypeDateTime64 *>(arguments[0].type.get()); const auto * dt64 = assert_cast<const DataTypeDateTime64 *>(arguments[0].type.get());
result_column = ConvertImpl<LeftDataType, RightDataType, Name, SpecialTag>::execute(arguments, result_type, input_rows_count, dt64->getScale()); switch (date_time_overflow_behavior)
{
case FormatSettings::DateTimeOverflowBehavior::Throw:
result_column = ConvertImpl<LeftDataType, RightDataType, Name, SpecialTag, FormatSettings::DateTimeOverflowBehavior::Throw>::execute(arguments, result_type, input_rows_count, dt64->getScale());
break;
case FormatSettings::DateTimeOverflowBehavior::Ignore:
result_column = ConvertImpl<LeftDataType, RightDataType, Name, SpecialTag, FormatSettings::DateTimeOverflowBehavior::Ignore>::execute(arguments, result_type, input_rows_count, dt64->getScale());
break;
case FormatSettings::DateTimeOverflowBehavior::Saturate:
result_column = ConvertImpl<LeftDataType, RightDataType, Name, SpecialTag, FormatSettings::DateTimeOverflowBehavior::Saturate>::execute(arguments, result_type, input_rows_count, dt64->getScale());
break;
}
} }
#define GENERATE_OVERFLOW_MODE_CASE(OVERFLOW_MODE) \
case FormatSettings::DateTimeOverflowBehavior::OVERFLOW_MODE: \
result_column = ConvertImpl<LeftDataType, RightDataType, Name, SpecialTag, FormatSettings::DateTimeOverflowBehavior::OVERFLOW_MODE>::execute( \
arguments, result_type, input_rows_count); \
break;
else if constexpr (IsDataTypeDecimalOrNumber<LeftDataType> && IsDataTypeDecimalOrNumber<RightDataType>) else if constexpr (IsDataTypeDecimalOrNumber<LeftDataType> && IsDataTypeDecimalOrNumber<RightDataType>)
{ {
using LeftT = typename LeftDataType::FieldType; using LeftT = typename LeftDataType::FieldType;
@ -2141,14 +2351,27 @@ private:
} }
else else
{ {
result_column switch (date_time_overflow_behavior)
= ConvertImpl<LeftDataType, RightDataType, Name, SpecialTag>::execute(arguments, result_type, input_rows_count); {
GENERATE_OVERFLOW_MODE_CASE(Throw)
GENERATE_OVERFLOW_MODE_CASE(Ignore)
GENERATE_OVERFLOW_MODE_CASE(Saturate)
}
} }
} }
else else if constexpr ((IsDataTypeNumber<LeftDataType> || IsDataTypeDateOrDateTime<LeftDataType>)
&& IsDataTypeDateOrDateTime<RightDataType>)
{ {
result_column = ConvertImpl<LeftDataType, RightDataType, Name, SpecialTag>::execute(arguments, result_type, input_rows_count); switch (date_time_overflow_behavior)
{
GENERATE_OVERFLOW_MODE_CASE(Throw)
GENERATE_OVERFLOW_MODE_CASE(Ignore)
GENERATE_OVERFLOW_MODE_CASE(Saturate)
}
} }
#undef GENERATE_OVERFLOW_MODE_CASE
else
result_column = ConvertImpl<LeftDataType, RightDataType, Name, SpecialTag>::execute(arguments, result_type, input_rows_count);
return true; return true;
}; };
@ -2559,16 +2782,19 @@ struct ToDateMonotonicity
static IFunction::Monotonicity get(const IDataType & type, const Field & left, const Field & right) static IFunction::Monotonicity get(const IDataType & type, const Field & left, const Field & right)
{ {
auto which = WhichDataType(type); auto which = WhichDataType(type);
if (which.isDateOrDate32() || which.isDateTime() || which.isDateTime64() || which.isInt8() || which.isInt16() || which.isUInt8() || which.isUInt16()) if (which.isDateOrDate32() || which.isDateTime() || which.isDateTime64() || which.isInt8() || which.isInt16() || which.isUInt8()
|| which.isUInt16())
{ {
return { .is_monotonic = true, .is_always_monotonic = true }; return {.is_monotonic = true, .is_always_monotonic = true};
} }
else if ( else if (
((left.getType() == Field::Types::UInt64 || left.isNull()) && (right.getType() == Field::Types::UInt64 || right.isNull()) ((left.getType() == Field::Types::UInt64 || left.isNull()) && (right.getType() == Field::Types::UInt64 || right.isNull())
&& ((left.isNull() || left.get<UInt64>() < 0xFFFF) && (right.isNull() || right.get<UInt64>() >= 0xFFFF))) && ((left.isNull() || left.get<UInt64>() < 0xFFFF) && (right.isNull() || right.get<UInt64>() >= 0xFFFF)))
|| ((left.getType() == Field::Types::Int64 || left.isNull()) && (right.getType() == Field::Types::Int64 || right.isNull()) || ((left.getType() == Field::Types::Int64 || left.isNull()) && (right.getType() == Field::Types::Int64 || right.isNull())
&& ((left.isNull() || left.get<Int64>() < 0xFFFF) && (right.isNull() || right.get<Int64>() >= 0xFFFF))) && ((left.isNull() || left.get<Int64>() < 0xFFFF) && (right.isNull() || right.get<Int64>() >= 0xFFFF)))
|| (((left.getType() == Field::Types::Float64 || left.isNull()) && (right.getType() == Field::Types::Float64 || right.isNull()) || ((
(left.getType() == Field::Types::Float64 || left.isNull())
&& (right.getType() == Field::Types::Float64 || right.isNull())
&& ((left.isNull() || left.get<Float64>() < 0xFFFF) && (right.isNull() || right.get<Float64>() >= 0xFFFF)))) && ((left.isNull() || left.get<Float64>() < 0xFFFF) && (right.isNull() || right.get<Float64>() >= 0xFFFF))))
|| !isNativeNumber(type)) || !isNativeNumber(type))
{ {
@ -2576,7 +2802,7 @@ struct ToDateMonotonicity
} }
else else
{ {
return { .is_monotonic = true, .is_always_monotonic = true }; return {.is_monotonic = true, .is_always_monotonic = true};
} }
} }
}; };
@ -2588,7 +2814,7 @@ struct ToDateTimeMonotonicity
static IFunction::Monotonicity get(const IDataType & type, const Field &, const Field &) static IFunction::Monotonicity get(const IDataType & type, const Field &, const Field &)
{ {
if (type.isValueRepresentedByNumber()) if (type.isValueRepresentedByNumber())
return { .is_monotonic = true, .is_always_monotonic = true }; return {.is_monotonic = true, .is_always_monotonic = true};
else else
return {}; return {};
} }
@ -2674,11 +2900,17 @@ using FunctionToInt128 = FunctionConvert<DataTypeInt128, NameToInt128, ToNumberM
using FunctionToInt256 = FunctionConvert<DataTypeInt256, NameToInt256, ToNumberMonotonicity<Int256>>; using FunctionToInt256 = FunctionConvert<DataTypeInt256, NameToInt256, ToNumberMonotonicity<Int256>>;
using FunctionToFloat32 = FunctionConvert<DataTypeFloat32, NameToFloat32, ToNumberMonotonicity<Float32>>; using FunctionToFloat32 = FunctionConvert<DataTypeFloat32, NameToFloat32, ToNumberMonotonicity<Float32>>;
using FunctionToFloat64 = FunctionConvert<DataTypeFloat64, NameToFloat64, ToNumberMonotonicity<Float64>>; using FunctionToFloat64 = FunctionConvert<DataTypeFloat64, NameToFloat64, ToNumberMonotonicity<Float64>>;
using FunctionToDate = FunctionConvert<DataTypeDate, NameToDate, ToDateMonotonicity>; using FunctionToDate = FunctionConvert<DataTypeDate, NameToDate, ToDateMonotonicity>;
using FunctionToDate32 = FunctionConvert<DataTypeDate32, NameToDate32, ToDateMonotonicity>; using FunctionToDate32 = FunctionConvert<DataTypeDate32, NameToDate32, ToDateMonotonicity>;
using FunctionToDateTime = FunctionConvert<DataTypeDateTime, NameToDateTime, ToDateTimeMonotonicity>; using FunctionToDateTime = FunctionConvert<DataTypeDateTime, NameToDateTime, ToDateTimeMonotonicity>;
using FunctionToDateTime32 = FunctionConvert<DataTypeDateTime, NameToDateTime32, ToDateTimeMonotonicity>; using FunctionToDateTime32 = FunctionConvert<DataTypeDateTime, NameToDateTime32, ToDateTimeMonotonicity>;
using FunctionToDateTime64 = FunctionConvert<DataTypeDateTime64, NameToDateTime64, ToDateTimeMonotonicity>; using FunctionToDateTime64 = FunctionConvert<DataTypeDateTime64, NameToDateTime64, ToDateTimeMonotonicity>;
using FunctionToUUID = FunctionConvert<DataTypeUUID, NameToUUID, ToNumberMonotonicity<UInt128>>; using FunctionToUUID = FunctionConvert<DataTypeUUID, NameToUUID, ToNumberMonotonicity<UInt128>>;
using FunctionToIPv4 = FunctionConvert<DataTypeIPv4, NameToIPv4, ToNumberMonotonicity<UInt32>>; using FunctionToIPv4 = FunctionConvert<DataTypeIPv4, NameToIPv4, ToNumberMonotonicity<UInt32>>;
using FunctionToIPv6 = FunctionConvert<DataTypeIPv6, NameToIPv6, ToNumberMonotonicity<UInt128>>; using FunctionToIPv6 = FunctionConvert<DataTypeIPv6, NameToIPv6, ToNumberMonotonicity<UInt128>>;
@ -2689,8 +2921,7 @@ using FunctionToDecimal64 = FunctionConvert<DataTypeDecimal<Decimal64>, NameToDe
using FunctionToDecimal128 = FunctionConvert<DataTypeDecimal<Decimal128>, NameToDecimal128, UnknownMonotonicity>; using FunctionToDecimal128 = FunctionConvert<DataTypeDecimal<Decimal128>, NameToDecimal128, UnknownMonotonicity>;
using FunctionToDecimal256 = FunctionConvert<DataTypeDecimal<Decimal256>, NameToDecimal256, UnknownMonotonicity>; using FunctionToDecimal256 = FunctionConvert<DataTypeDecimal<Decimal256>, NameToDecimal256, UnknownMonotonicity>;
template <typename DataType, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior = default_date_time_overflow_behavior> struct FunctionTo;
template <typename DataType> struct FunctionTo;
template <> struct FunctionTo<DataTypeUInt8> { using Type = FunctionToUInt8; }; template <> struct FunctionTo<DataTypeUInt8> { using Type = FunctionToUInt8; };
template <> struct FunctionTo<DataTypeUInt16> { using Type = FunctionToUInt16; }; template <> struct FunctionTo<DataTypeUInt16> { using Type = FunctionToUInt16; };
@ -2706,10 +2937,19 @@ template <> struct FunctionTo<DataTypeInt128> { using Type = FunctionToInt128; }
template <> struct FunctionTo<DataTypeInt256> { using Type = FunctionToInt256; }; template <> struct FunctionTo<DataTypeInt256> { using Type = FunctionToInt256; };
template <> struct FunctionTo<DataTypeFloat32> { using Type = FunctionToFloat32; }; template <> struct FunctionTo<DataTypeFloat32> { using Type = FunctionToFloat32; };
template <> struct FunctionTo<DataTypeFloat64> { using Type = FunctionToFloat64; }; template <> struct FunctionTo<DataTypeFloat64> { using Type = FunctionToFloat64; };
template <> struct FunctionTo<DataTypeDate> { using Type = FunctionToDate; };
template <> struct FunctionTo<DataTypeDate32> { using Type = FunctionToDate32; }; template <FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
template <> struct FunctionTo<DataTypeDateTime> { using Type = FunctionToDateTime; }; struct FunctionTo<DataTypeDate, date_time_overflow_behavior> { using Type = FunctionToDate; };
template <> struct FunctionTo<DataTypeDateTime64> { using Type = FunctionToDateTime64; };
template <FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct FunctionTo<DataTypeDate32, date_time_overflow_behavior> { using Type = FunctionToDate32; };
template <FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct FunctionTo<DataTypeDateTime, date_time_overflow_behavior> { using Type = FunctionToDateTime; };
template <FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior>
struct FunctionTo<DataTypeDateTime64, date_time_overflow_behavior> { using Type = FunctionToDateTime64; };
template <> struct FunctionTo<DataTypeUUID> { using Type = FunctionToUUID; }; template <> struct FunctionTo<DataTypeUUID> { using Type = FunctionToUUID; };
template <> struct FunctionTo<DataTypeIPv4> { using Type = FunctionToIPv4; }; template <> struct FunctionTo<DataTypeIPv4> { using Type = FunctionToIPv4; };
template <> struct FunctionTo<DataTypeIPv6> { using Type = FunctionToIPv6; }; template <> struct FunctionTo<DataTypeIPv6> { using Type = FunctionToIPv6; };
@ -3044,6 +3284,10 @@ private:
bool can_apply_accurate_cast = (cast_type == CastType::accurate || cast_type == CastType::accurateOrNull) bool can_apply_accurate_cast = (cast_type == CastType::accurate || cast_type == CastType::accurateOrNull)
&& (which.isInt() || which.isUInt() || which.isFloat()); && (which.isInt() || which.isUInt() || which.isFloat());
FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior = default_date_time_overflow_behavior;
if (context)
date_time_overflow_behavior = context->getSettingsRef().date_time_overflow_behavior;
if (requested_result_is_nullable && checkAndGetDataType<DataTypeString>(from_type.get())) if (requested_result_is_nullable && checkAndGetDataType<DataTypeString>(from_type.get()))
{ {
/// In case when converting to Nullable type, we apply different parsing rule, /// In case when converting to Nullable type, we apply different parsing rule,
@ -3060,7 +3304,7 @@ private:
auto wrapper_cast_type = cast_type; auto wrapper_cast_type = cast_type;
return [wrapper_cast_type, from_type_index, to_type] return [wrapper_cast_type, from_type_index, to_type, date_time_overflow_behavior]
(ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, const ColumnNullable *column_nullable, size_t input_rows_count) (ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, const ColumnNullable *column_nullable, size_t input_rows_count)
{ {
ColumnPtr result_column; ColumnPtr result_column;
@ -3073,31 +3317,60 @@ private:
{ {
if constexpr (IsDataTypeNumber<RightDataType>) if constexpr (IsDataTypeNumber<RightDataType>)
{ {
#define GENERATE_OVERFLOW_MODE_CASE(OVERFLOW_MODE, ADDITIONS) \
case FormatSettings::DateTimeOverflowBehavior::OVERFLOW_MODE: \
result_column = ConvertImpl<LeftDataType, RightDataType, FunctionName, ConvertDefaultBehaviorTag, FormatSettings::DateTimeOverflowBehavior::OVERFLOW_MODE>::execute( \
arguments, result_type, input_rows_count, ADDITIONS()); \
break;
if (wrapper_cast_type == CastType::accurate) if (wrapper_cast_type == CastType::accurate)
{ {
result_column = ConvertImpl<LeftDataType, RightDataType, FunctionName>::execute( switch (date_time_overflow_behavior)
arguments, result_type, input_rows_count, AccurateConvertStrategyAdditions()); {
GENERATE_OVERFLOW_MODE_CASE(Throw, AccurateConvertStrategyAdditions)
GENERATE_OVERFLOW_MODE_CASE(Ignore, AccurateConvertStrategyAdditions)
GENERATE_OVERFLOW_MODE_CASE(Saturate, AccurateConvertStrategyAdditions)
}
} }
else else
{ {
result_column = ConvertImpl<LeftDataType, RightDataType, FunctionName>::execute( switch (date_time_overflow_behavior)
arguments, result_type, input_rows_count, AccurateOrNullConvertStrategyAdditions()); {
GENERATE_OVERFLOW_MODE_CASE(Throw, AccurateOrNullConvertStrategyAdditions)
GENERATE_OVERFLOW_MODE_CASE(Ignore, AccurateOrNullConvertStrategyAdditions)
GENERATE_OVERFLOW_MODE_CASE(Saturate, AccurateOrNullConvertStrategyAdditions)
}
} }
#undef GENERATE_OVERFLOW_MODE_CASE
return true; return true;
} }
if constexpr (std::is_same_v<RightDataType, DataTypeDate> || std::is_same_v<RightDataType, DataTypeDateTime>) if constexpr (std::is_same_v<RightDataType, DataTypeDate> || std::is_same_v<RightDataType, DataTypeDateTime>)
{ {
#define GENERATE_OVERFLOW_MODE_CASE(OVERFLOW_MODE, ADDITIONS) \
case FormatSettings::DateTimeOverflowBehavior::OVERFLOW_MODE: \
result_column = ConvertImpl<LeftDataType, RightDataType, FunctionName, ConvertDefaultBehaviorTag, FormatSettings::DateTimeOverflowBehavior::OVERFLOW_MODE>::template execute<ADDITIONS>( \
arguments, result_type, input_rows_count); \
break;
if (wrapper_cast_type == CastType::accurate) if (wrapper_cast_type == CastType::accurate)
{ {
result_column = ConvertImpl<LeftDataType, RightDataType, FunctionName>::template execute<DateTimeAccurateConvertStrategyAdditions>( switch (date_time_overflow_behavior)
arguments, result_type, input_rows_count); {
GENERATE_OVERFLOW_MODE_CASE(Throw, DateTimeAccurateConvertStrategyAdditions)
GENERATE_OVERFLOW_MODE_CASE(Ignore, DateTimeAccurateConvertStrategyAdditions)
GENERATE_OVERFLOW_MODE_CASE(Saturate, DateTimeAccurateConvertStrategyAdditions)
}
} }
else else
{ {
result_column = ConvertImpl<LeftDataType, RightDataType, FunctionName>::template execute<DateTimeAccurateOrNullConvertStrategyAdditions>( switch (date_time_overflow_behavior)
arguments, result_type, input_rows_count); {
GENERATE_OVERFLOW_MODE_CASE(Throw, DateTimeAccurateOrNullConvertStrategyAdditions)
GENERATE_OVERFLOW_MODE_CASE(Ignore, DateTimeAccurateOrNullConvertStrategyAdditions)
GENERATE_OVERFLOW_MODE_CASE(Saturate, DateTimeAccurateOrNullConvertStrategyAdditions)
}
} }
#undef GENERATE_OVERFLOW_MODE_CASE
return true; return true;
} }
} }

View File

@ -148,11 +148,11 @@ ReturnType readFloatTextPreciseImpl(T & x, ReadBuffer & buf)
static_assert('a' > '.' && 'A' > '.' && '\n' < '.' && '\t' < '.' && '\'' < '.' && '"' < '.', "Layout of char is not like ASCII"); static_assert('a' > '.' && 'A' > '.' && '\n' < '.' && '\t' < '.' && '\'' < '.' && '"' < '.', "Layout of char is not like ASCII");
static constexpr bool throw_exception = std::is_same_v<ReturnType, void>; static constexpr bool throw_exception = std::is_same_v<ReturnType, void>;
/// Fast path (avoid copying) if the buffer have at least MAX_LENGTH bytes.
static constexpr int MAX_LENGTH = 316; static constexpr int MAX_LENGTH = 316;
if (likely(!buf.eof() && buf.position() + MAX_LENGTH <= buf.buffer().end())) ReadBufferFromMemory * buf_from_memory = dynamic_cast<ReadBufferFromMemory *>(&buf);
/// Fast path (avoid copying) if the buffer have at least MAX_LENGTH bytes or buf is ReadBufferFromMemory
if (likely(!buf.eof() && (buf_from_memory || buf.position() + MAX_LENGTH <= buf.buffer().end())))
{ {
auto * initial_position = buf.position(); auto * initial_position = buf.position();
auto res = fast_float::from_chars(initial_position, buf.buffer().end(), x); auto res = fast_float::from_chars(initial_position, buf.buffer().end(), x);
@ -160,7 +160,10 @@ ReturnType readFloatTextPreciseImpl(T & x, ReadBuffer & buf)
if (unlikely(res.ec != std::errc())) if (unlikely(res.ec != std::errc()))
{ {
if constexpr (throw_exception) if constexpr (throw_exception)
throw ParsingException(ErrorCodes::CANNOT_PARSE_NUMBER, "Cannot read floating point value"); throw ParsingException(
ErrorCodes::CANNOT_PARSE_NUMBER,
"Cannot read floating point value here: {}",
String(initial_position, buf.buffer().end() - initial_position));
else else
return ReturnType(false); return ReturnType(false);
} }
@ -247,10 +250,11 @@ ReturnType readFloatTextPreciseImpl(T & x, ReadBuffer & buf)
res = fast_float::from_chars(tmp_buf, tmp_buf + num_copied_chars, x64); res = fast_float::from_chars(tmp_buf, tmp_buf + num_copied_chars, x64);
x = static_cast<T>(x64); x = static_cast<T>(x64);
} }
if (unlikely(res.ec != std::errc())) if (unlikely(res.ec != std::errc() || res.ptr - tmp_buf != num_copied_chars))
{ {
if constexpr (throw_exception) if constexpr (throw_exception)
throw ParsingException(ErrorCodes::CANNOT_PARSE_NUMBER, "Cannot read floating point value"); throw ParsingException(
ErrorCodes::CANNOT_PARSE_NUMBER, "Cannot read floating point value here: {}", String(tmp_buf, num_copied_chars));
else else
return ReturnType(false); return ReturnType(false);
} }

View File

@ -475,13 +475,12 @@ InterpreterSelectQuery::InterpreterSelectQuery(
/// Check support for FINAL for parallel replicas /// Check support for FINAL for parallel replicas
bool is_query_with_final = isQueryWithFinal(query_info); bool is_query_with_final = isQueryWithFinal(query_info);
if (is_query_with_final && (!settings.parallel_replicas_custom_key.value.empty() || settings.allow_experimental_parallel_reading_from_replicas > 0)) if (is_query_with_final && settings.allow_experimental_parallel_reading_from_replicas > 0)
{ {
if (settings.allow_experimental_parallel_reading_from_replicas == 1) if (settings.allow_experimental_parallel_reading_from_replicas == 1)
{ {
LOG_DEBUG(log, "FINAL modifier is not supported with parallel replicas. Query will be executed without using them."); LOG_DEBUG(log, "FINAL modifier is not supported with parallel replicas. Query will be executed without using them.");
context->setSetting("allow_experimental_parallel_reading_from_replicas", Field(0)); context->setSetting("allow_experimental_parallel_reading_from_replicas", Field(0));
context->setSetting("parallel_replicas_custom_key", String{""});
} }
else if (settings.allow_experimental_parallel_reading_from_replicas == 2) else if (settings.allow_experimental_parallel_reading_from_replicas == 2)
{ {
@ -2936,7 +2935,11 @@ void InterpreterSelectQuery::executeWindow(QueryPlan & query_plan)
query_plan.addStep(std::move(sorting_step)); query_plan.addStep(std::move(sorting_step));
} }
auto window_step = std::make_unique<WindowStep>(query_plan.getCurrentDataStream(), window, window.window_functions); // Fan out streams only for the last window to preserve the ordering between windows,
// and WindowTransform works on single stream anyway.
const bool streams_fan_out = settings.query_plan_enable_multithreading_after_window_functions && ((i + 1) == windows_sorted.size());
auto window_step = std::make_unique<WindowStep>(query_plan.getCurrentDataStream(), window, window.window_functions, streams_fan_out);
window_step->setStepDescription("Window step for window '" + window.window_name + "'"); window_step->setStepDescription("Window step for window '" + window.window_name + "'");
query_plan.addStep(std::move(window_step)); query_plan.addStep(std::move(window_step));

View File

@ -89,7 +89,7 @@ ORDER BY index_type, expression, column_name, seq_in_index;)", database, table,
/// can be functional indexes. /// can be functional indexes.
/// Above SELECT tries to emulate that. Caveats: /// Above SELECT tries to emulate that. Caveats:
/// 1. The primary key index sub-SELECT assumes the primary key expression is non-functional. Non-functional primary key indexes in /// 1. The primary key index sub-SELECT assumes the primary key expression is non-functional. Non-functional primary key indexes in
/// ClickHouse are possible but quiete obscure. In MySQL they are not possible at all. /// ClickHouse are possible but quite obscure. In MySQL they are not possible at all.
/// 2. Related to 1.: Poor man's tuple parsing with splitByString() in the PK sub-SELECT messes up for functional primary key index /// 2. Related to 1.: Poor man's tuple parsing with splitByString() in the PK sub-SELECT messes up for functional primary key index
/// expressions where the comma is not only used as separator between tuple components, e.g. in 'col1 + 1, concat(col2, col3)'. /// expressions where the comma is not only used as separator between tuple components, e.g. in 'col1 + 1, concat(col2, col3)'.
/// 3. The data skipping index sub-SELECT assumes the index expression is functional. 3rd party tools that expect MySQL semantics from /// 3. The data skipping index sub-SELECT assumes the index expression is functional. 3rd party tools that expect MySQL semantics from
@ -106,4 +106,3 @@ BlockIO InterpreterShowIndexesQuery::execute()
} }

View File

@ -905,8 +905,12 @@ void addWindowSteps(QueryPlan & query_plan,
query_plan.addStep(std::move(sorting_step)); query_plan.addStep(std::move(sorting_step));
} }
// Fan out streams only for the last window to preserve the ordering between windows,
// and WindowTransform works on single stream anyway.
const bool streams_fan_out = settings.query_plan_enable_multithreading_after_window_functions && ((i + 1) == window_descriptions_size);
auto window_step auto window_step
= std::make_unique<WindowStep>(query_plan.getCurrentDataStream(), window_description, window_description.window_functions); = std::make_unique<WindowStep>(query_plan.getCurrentDataStream(), window_description, window_description.window_functions, streams_fan_out);
window_step->setStepDescription("Window step for window '" + window_description.window_name + "'"); window_step->setStepDescription("Window step for window '" + window_description.window_name + "'");
query_plan.addStep(std::move(window_step)); query_plan.addStep(std::move(window_step));
} }

View File

@ -157,6 +157,7 @@ void ArrowBlockInputFormat::prepareReader()
"Arrow", "Arrow",
format_settings.arrow.allow_missing_columns, format_settings.arrow.allow_missing_columns,
format_settings.null_as_default, format_settings.null_as_default,
format_settings.date_time_overflow_behavior,
format_settings.arrow.case_insensitive_column_matching); format_settings.arrow.case_insensitive_column_matching);
if (stream) if (stream)

View File

@ -247,11 +247,13 @@ static ColumnWithTypeAndName readColumnWithBooleanData(std::shared_ptr<arrow::Ch
return {std::move(internal_column), internal_type, column_name}; return {std::move(internal_column), internal_type, column_name};
} }
static ColumnWithTypeAndName readColumnWithDate32Data(std::shared_ptr<arrow::ChunkedArray> & arrow_column, const String & column_name, const DataTypePtr & type_hint) static ColumnWithTypeAndName readColumnWithDate32Data(std::shared_ptr<arrow::ChunkedArray> & arrow_column, const String & column_name,
const DataTypePtr & type_hint, FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior)
{ {
DataTypePtr internal_type; DataTypePtr internal_type;
bool check_date_range = false; bool check_date_range = false;
/// Make result type Date32 when requested type is actually Date32 or when we use schema inference /// Make result type Date32 when requested type is actually Date32 or when we use schema inference
if (!type_hint || (type_hint && isDate32(*type_hint))) if (!type_hint || (type_hint && isDate32(*type_hint)))
{ {
internal_type = std::make_shared<DataTypeDate32>(); internal_type = std::make_shared<DataTypeDate32>();
@ -277,8 +279,21 @@ static ColumnWithTypeAndName readColumnWithDate32Data(std::shared_ptr<arrow::Chu
{ {
Int32 days_num = static_cast<Int32>(chunk.Value(value_i)); Int32 days_num = static_cast<Int32>(chunk.Value(value_i));
if (days_num > DATE_LUT_MAX_EXTEND_DAY_NUM || days_num < -DAYNUM_OFFSET_EPOCH) if (days_num > DATE_LUT_MAX_EXTEND_DAY_NUM || days_num < -DAYNUM_OFFSET_EPOCH)
throw Exception{ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, {
"Input value {} of a column \"{}\" is out of allowed Date32 range, which is [{}, {}]", days_num, column_name, DAYNUM_OFFSET_EPOCH, DATE_LUT_MAX_EXTEND_DAY_NUM}; switch (date_time_overflow_behavior)
{
case FormatSettings::DateTimeOverflowBehavior::Saturate:
days_num = (days_num < -DAYNUM_OFFSET_EPOCH) ? -DAYNUM_OFFSET_EPOCH : DATE_LUT_MAX_EXTEND_DAY_NUM;
break;
default:
/// Prior to introducing `date_time_overflow_behavior`, this function threw an error in case value was out of range.
/// In order to leave this behavior as default, we also throw when `date_time_overflow_mode == ignore`, as it is the setting's default value
/// (As we want to make this backwards compatible, not break any workflows.)
throw Exception{ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE,
"Input value {} of a column \"{}\" is out of allowed Date32 range, which is [{}, {}]",
days_num,column_name, -DAYNUM_OFFSET_EPOCH, DATE_LUT_MAX_EXTEND_DAY_NUM};
}
}
column_data.emplace_back(days_num); column_data.emplace_back(days_num);
} }
@ -681,6 +696,7 @@ static ColumnWithTypeAndName readColumnFromArrowColumn(
bool allow_null_type, bool allow_null_type,
bool skip_columns_with_unsupported_types, bool skip_columns_with_unsupported_types,
bool & skipped, bool & skipped,
FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior = FormatSettings::DateTimeOverflowBehavior::Ignore,
DataTypePtr type_hint = nullptr, DataTypePtr type_hint = nullptr,
bool is_map_nested = false) bool is_map_nested = false)
{ {
@ -691,7 +707,7 @@ static ColumnWithTypeAndName readColumnFromArrowColumn(
DataTypePtr nested_type_hint; DataTypePtr nested_type_hint;
if (type_hint) if (type_hint)
nested_type_hint = removeNullable(type_hint); nested_type_hint = removeNullable(type_hint);
auto nested_column = readColumnFromArrowColumn(arrow_column, column_name, format_name, true, dictionary_infos, allow_null_type, skip_columns_with_unsupported_types, skipped, nested_type_hint); auto nested_column = readColumnFromArrowColumn(arrow_column, column_name, format_name, true, dictionary_infos, allow_null_type, skip_columns_with_unsupported_types, skipped, date_time_overflow_behavior, nested_type_hint);
if (skipped) if (skipped)
return {}; return {};
auto nullmap_column = readByteMapFromArrowColumn(arrow_column); auto nullmap_column = readByteMapFromArrowColumn(arrow_column);
@ -756,7 +772,7 @@ static ColumnWithTypeAndName readColumnFromArrowColumn(
case arrow::Type::BOOL: case arrow::Type::BOOL:
return readColumnWithBooleanData(arrow_column, column_name); return readColumnWithBooleanData(arrow_column, column_name);
case arrow::Type::DATE32: case arrow::Type::DATE32:
return readColumnWithDate32Data(arrow_column, column_name, type_hint); return readColumnWithDate32Data(arrow_column, column_name, type_hint, date_time_overflow_behavior);
case arrow::Type::DATE64: case arrow::Type::DATE64:
return readColumnWithDate64Data(arrow_column, column_name); return readColumnWithDate64Data(arrow_column, column_name);
// ClickHouse writes Date as arrow UINT16 and DateTime as arrow UINT32, // ClickHouse writes Date as arrow UINT16 and DateTime as arrow UINT32,
@ -804,7 +820,7 @@ static ColumnWithTypeAndName readColumnFromArrowColumn(
} }
} }
auto arrow_nested_column = getNestedArrowColumn(arrow_column); auto arrow_nested_column = getNestedArrowColumn(arrow_column);
auto nested_column = readColumnFromArrowColumn(arrow_nested_column, column_name, format_name, false, dictionary_infos, allow_null_type, skip_columns_with_unsupported_types, skipped, nested_type_hint, true); auto nested_column = readColumnFromArrowColumn(arrow_nested_column, column_name, format_name, false, dictionary_infos, allow_null_type, skip_columns_with_unsupported_types, skipped, date_time_overflow_behavior, nested_type_hint, true);
if (skipped) if (skipped)
return {}; return {};
@ -839,7 +855,7 @@ static ColumnWithTypeAndName readColumnFromArrowColumn(
nested_type_hint = array_type_hint->getNestedType(); nested_type_hint = array_type_hint->getNestedType();
} }
auto arrow_nested_column = getNestedArrowColumn(arrow_column); auto arrow_nested_column = getNestedArrowColumn(arrow_column);
auto nested_column = readColumnFromArrowColumn(arrow_nested_column, column_name, format_name, false, dictionary_infos, allow_null_type, skip_columns_with_unsupported_types, skipped, nested_type_hint); auto nested_column = readColumnFromArrowColumn(arrow_nested_column, column_name, format_name, false, dictionary_infos, allow_null_type, skip_columns_with_unsupported_types, skipped, date_time_overflow_behavior, nested_type_hint);
if (skipped) if (skipped)
return {}; return {};
auto offsets_column = readOffsetsFromArrowListColumn(arrow_column); auto offsets_column = readOffsetsFromArrowListColumn(arrow_column);
@ -880,7 +896,7 @@ static ColumnWithTypeAndName readColumnFromArrowColumn(
nested_type_hint = tuple_type_hint->getElement(i); nested_type_hint = tuple_type_hint->getElement(i);
} }
auto nested_arrow_column = std::make_shared<arrow::ChunkedArray>(nested_arrow_columns[i]); auto nested_arrow_column = std::make_shared<arrow::ChunkedArray>(nested_arrow_columns[i]);
auto element = readColumnFromArrowColumn(nested_arrow_column, field_name, format_name, false, dictionary_infos, allow_null_type, skip_columns_with_unsupported_types, skipped, nested_type_hint); auto element = readColumnFromArrowColumn(nested_arrow_column, field_name, format_name, false, dictionary_infos, allow_null_type, skip_columns_with_unsupported_types, skipped, date_time_overflow_behavior, nested_type_hint);
if (skipped) if (skipped)
return {}; return {};
tuple_elements.emplace_back(std::move(element.column)); tuple_elements.emplace_back(std::move(element.column));
@ -907,7 +923,7 @@ static ColumnWithTypeAndName readColumnFromArrowColumn(
dict_array.emplace_back(dict_chunk.dictionary()); dict_array.emplace_back(dict_chunk.dictionary());
} }
auto arrow_dict_column = std::make_shared<arrow::ChunkedArray>(dict_array); auto arrow_dict_column = std::make_shared<arrow::ChunkedArray>(dict_array);
auto dict_column = readColumnFromArrowColumn(arrow_dict_column, column_name, format_name, false, dictionary_infos, allow_null_type, skip_columns_with_unsupported_types, skipped); auto dict_column = readColumnFromArrowColumn(arrow_dict_column, column_name, format_name, false, dictionary_infos, allow_null_type, skip_columns_with_unsupported_types, skipped, date_time_overflow_behavior);
for (size_t i = 0; i != dict_column.column->size(); ++i) for (size_t i = 0; i != dict_column.column->size(); ++i)
{ {
if (dict_column.column->isDefaultAt(i)) if (dict_column.column->isDefaultAt(i))
@ -997,7 +1013,8 @@ static void checkStatus(const arrow::Status & status, const String & column_name
Block ArrowColumnToCHColumn::arrowSchemaToCHHeader( Block ArrowColumnToCHColumn::arrowSchemaToCHHeader(
const arrow::Schema & schema, const std::string & format_name, bool skip_columns_with_unsupported_types, const Block * hint_header, bool ignore_case) const arrow::Schema & schema, const std::string & format_name,
bool skip_columns_with_unsupported_types, const Block * hint_header, bool ignore_case)
{ {
ColumnsWithTypeAndName sample_columns; ColumnsWithTypeAndName sample_columns;
std::unordered_set<String> nested_table_names; std::unordered_set<String> nested_table_names;
@ -1040,12 +1057,14 @@ ArrowColumnToCHColumn::ArrowColumnToCHColumn(
const std::string & format_name_, const std::string & format_name_,
bool allow_missing_columns_, bool allow_missing_columns_,
bool null_as_default_, bool null_as_default_,
FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior_,
bool case_insensitive_matching_) bool case_insensitive_matching_)
: header(header_) : header(header_)
, format_name(format_name_) , format_name(format_name_)
, allow_missing_columns(allow_missing_columns_) , allow_missing_columns(allow_missing_columns_)
, null_as_default(null_as_default_) , null_as_default(null_as_default_)
, case_insensitive_matching(case_insensitive_matching_) , case_insensitive_matching(case_insensitive_matching_)
, date_time_overflow_behavior(date_time_overflow_behavior_)
{ {
} }
@ -1102,8 +1121,9 @@ void ArrowColumnToCHColumn::arrowColumnsToCHChunk(Chunk & res, NameToColumnPtr &
auto nested_table_type = Nested::collect(nested_columns).front().type; auto nested_table_type = Nested::collect(nested_columns).front().type;
std::shared_ptr<arrow::ChunkedArray> arrow_column = name_to_column_ptr[search_nested_table_name]; std::shared_ptr<arrow::ChunkedArray> arrow_column = name_to_column_ptr[search_nested_table_name];
ColumnsWithTypeAndName cols = {readColumnFromArrowColumn( ColumnsWithTypeAndName cols = {
arrow_column, nested_table_name, format_name, false, dictionary_infos, true, false, skipped, nested_table_type)}; readColumnFromArrowColumn(arrow_column, nested_table_name, format_name, false, dictionary_infos, true, false,
skipped, date_time_overflow_behavior, nested_table_type)};
BlockPtr block_ptr = std::make_shared<Block>(cols); BlockPtr block_ptr = std::make_shared<Block>(cols);
auto column_extractor = std::make_shared<NestedColumnExtractHelper>(*block_ptr, case_insensitive_matching); auto column_extractor = std::make_shared<NestedColumnExtractHelper>(*block_ptr, case_insensitive_matching);
nested_tables[search_nested_table_name] = {block_ptr, column_extractor}; nested_tables[search_nested_table_name] = {block_ptr, column_extractor};
@ -1138,7 +1158,7 @@ void ArrowColumnToCHColumn::arrowColumnsToCHChunk(Chunk & res, NameToColumnPtr &
{ {
auto arrow_column = name_to_column_ptr[search_column_name]; auto arrow_column = name_to_column_ptr[search_column_name];
column = readColumnFromArrowColumn( column = readColumnFromArrowColumn(
arrow_column, header_column.name, format_name, false, dictionary_infos, true, false, skipped, header_column.type); arrow_column, header_column.name, format_name, false, dictionary_infos, true, false, skipped, date_time_overflow_behavior, header_column.type);
} }
if (null_as_default) if (null_as_default)

View File

@ -8,7 +8,7 @@
#include <Core/ColumnWithTypeAndName.h> #include <Core/ColumnWithTypeAndName.h>
#include <Core/Block.h> #include <Core/Block.h>
#include <arrow/table.h> #include <arrow/table.h>
#include <Formats/FormatSettings.h>
namespace DB namespace DB
{ {
@ -26,6 +26,7 @@ public:
const std::string & format_name_, const std::string & format_name_,
bool allow_missing_columns_, bool allow_missing_columns_,
bool null_as_default_, bool null_as_default_,
FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior_,
bool case_insensitive_matching_ = false); bool case_insensitive_matching_ = false);
void arrowTableToCHChunk(Chunk & res, std::shared_ptr<arrow::Table> & table, size_t num_rows, BlockMissingValues * block_missing_values = nullptr); void arrowTableToCHChunk(Chunk & res, std::shared_ptr<arrow::Table> & table, size_t num_rows, BlockMissingValues * block_missing_values = nullptr);
@ -56,6 +57,7 @@ private:
bool allow_missing_columns; bool allow_missing_columns;
bool null_as_default; bool null_as_default;
bool case_insensitive_matching; bool case_insensitive_matching;
FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior;
/// Map {column name : dictionary column}. /// Map {column name : dictionary column}.
/// To avoid converting dictionary from Arrow Dictionary /// To avoid converting dictionary from Arrow Dictionary

View File

@ -131,6 +131,7 @@ void ORCBlockInputFormat::prepareReader()
"ORC", "ORC",
format_settings.orc.allow_missing_columns, format_settings.orc.allow_missing_columns,
format_settings.null_as_default, format_settings.null_as_default,
format_settings.date_time_overflow_behavior,
format_settings.orc.case_insensitive_column_matching); format_settings.orc.case_insensitive_column_matching);
const bool ignore_case = format_settings.orc.case_insensitive_column_matching; const bool ignore_case = format_settings.orc.case_insensitive_column_matching;

View File

@ -493,6 +493,7 @@ void ParquetBlockInputFormat::initializeRowGroupBatchReader(size_t row_group_bat
"Parquet", "Parquet",
format_settings.parquet.allow_missing_columns, format_settings.parquet.allow_missing_columns,
format_settings.null_as_default, format_settings.null_as_default,
format_settings.date_time_overflow_behavior,
format_settings.parquet.case_insensitive_column_matching); format_settings.parquet.case_insensitive_column_matching);
} }

View File

@ -10,14 +10,14 @@
namespace DB namespace DB
{ {
static ITransformingStep::Traits getTraits() static ITransformingStep::Traits getTraits(bool preserves_sorting)
{ {
return ITransformingStep::Traits return ITransformingStep::Traits
{ {
{ {
.returns_single_stream = false, .returns_single_stream = false,
.preserves_number_of_streams = true, .preserves_number_of_streams = true,
.preserves_sorting = true, .preserves_sorting = preserves_sorting,
}, },
{ {
.preserves_number_of_rows = true .preserves_number_of_rows = true
@ -46,10 +46,12 @@ static Block addWindowFunctionResultColumns(const Block & block,
WindowStep::WindowStep( WindowStep::WindowStep(
const DataStream & input_stream_, const DataStream & input_stream_,
const WindowDescription & window_description_, const WindowDescription & window_description_,
const std::vector<WindowFunctionDescription> & window_functions_) const std::vector<WindowFunctionDescription> & window_functions_,
: ITransformingStep(input_stream_, addWindowFunctionResultColumns(input_stream_.header, window_functions_), getTraits()) bool streams_fan_out_)
: ITransformingStep(input_stream_, addWindowFunctionResultColumns(input_stream_.header, window_functions_), getTraits(!streams_fan_out_))
, window_description(window_description_) , window_description(window_description_)
, window_functions(window_functions_) , window_functions(window_functions_)
, streams_fan_out(streams_fan_out_)
{ {
// We don't remove any columns, only add, so probably we don't have to update // We don't remove any columns, only add, so probably we don't have to update
// the output DataStream::distinct_columns. // the output DataStream::distinct_columns.
@ -60,6 +62,8 @@ WindowStep::WindowStep(
void WindowStep::transformPipeline(QueryPipelineBuilder & pipeline, const BuildQueryPipelineSettings &) void WindowStep::transformPipeline(QueryPipelineBuilder & pipeline, const BuildQueryPipelineSettings &)
{ {
auto num_threads = pipeline.getNumThreads();
// This resize is needed for cases such as `over ()` when we don't have a // This resize is needed for cases such as `over ()` when we don't have a
// sort node, and the input might have multiple streams. The sort node would // sort node, and the input might have multiple streams. The sort node would
// have resized it. // have resized it.
@ -72,6 +76,11 @@ void WindowStep::transformPipeline(QueryPipelineBuilder & pipeline, const BuildQ
input_streams.front().header, output_stream->header, window_description, window_functions); input_streams.front().header, output_stream->header, window_description, window_functions);
}); });
if (streams_fan_out)
{
pipeline.resize(num_threads);
}
assertBlocksHaveEqualStructure(pipeline.getHeader(), output_stream->header, assertBlocksHaveEqualStructure(pipeline.getHeader(), output_stream->header,
"WindowStep transform for '" + window_description.window_name + "'"); "WindowStep transform for '" + window_description.window_name + "'");
} }

View File

@ -16,7 +16,8 @@ class WindowStep : public ITransformingStep
public: public:
explicit WindowStep(const DataStream & input_stream_, explicit WindowStep(const DataStream & input_stream_,
const WindowDescription & window_description_, const WindowDescription & window_description_,
const std::vector<WindowFunctionDescription> & window_functions_); const std::vector<WindowFunctionDescription> & window_functions_,
bool streams_fan_out_);
String getName() const override { return "Window"; } String getName() const override { return "Window"; }
@ -32,6 +33,7 @@ private:
WindowDescription window_description; WindowDescription window_description;
std::vector<WindowFunctionDescription> window_functions; std::vector<WindowFunctionDescription> window_functions;
bool streams_fan_out;
}; };
} }

View File

@ -283,6 +283,7 @@ struct DeltaLakeMetadataParser<Configuration, MetadataReadHelper>::Impl
header, "Parquet", header, "Parquet",
format_settings.parquet.allow_missing_columns, format_settings.parquet.allow_missing_columns,
/* null_as_default */true, /* null_as_default */true,
format_settings.date_time_overflow_behavior,
/* case_insensitive_column_matching */false); /* case_insensitive_column_matching */false);
Chunk res; Chunk res;

View File

@ -165,7 +165,8 @@ public:
: ISource(storage_snapshot->getSampleBlockForColumns(column_names_)) : ISource(storage_snapshot->getSampleBlockForColumns(column_names_))
, column_names_and_types(storage_snapshot->getColumnsByNames( , column_names_and_types(storage_snapshot->getColumnsByNames(
GetColumnsOptions(GetColumnsOptions::All).withSubcolumns(), column_names_)) GetColumnsOptions(GetColumnsOptions::All).withSubcolumns(), column_names_))
, buffer(buffer_) {} , buffer(buffer_)
, metadata_version(storage_snapshot->metadata->metadata_version) {}
String getName() const override { return "Buffer"; } String getName() const override { return "Buffer"; }
@ -180,7 +181,7 @@ protected:
std::unique_lock lock(buffer.lockForReading()); std::unique_lock lock(buffer.lockForReading());
if (!buffer.data.rows()) if (!buffer.data.rows() || buffer.metadata_version != metadata_version)
return res; return res;
Columns columns; Columns columns;
@ -198,6 +199,7 @@ protected:
private: private:
NamesAndTypesList column_names_and_types; NamesAndTypesList column_names_and_types;
StorageBuffer::Buffer & buffer; StorageBuffer::Buffer & buffer;
int32_t metadata_version;
bool has_been_read = false; bool has_been_read = false;
}; };
@ -615,7 +617,7 @@ public:
least_busy_buffer = &storage.buffers[start_shard_num]; least_busy_buffer = &storage.buffers[start_shard_num];
least_busy_lock = least_busy_buffer->lockForWriting(); least_busy_lock = least_busy_buffer->lockForWriting();
} }
insertIntoBuffer(block, *least_busy_buffer); insertIntoBuffer(block, *least_busy_buffer, metadata_snapshot->metadata_version);
least_busy_lock.unlock(); least_busy_lock.unlock();
storage.reschedule(); storage.reschedule();
@ -624,14 +626,15 @@ private:
StorageBuffer & storage; StorageBuffer & storage;
StorageMetadataPtr metadata_snapshot; StorageMetadataPtr metadata_snapshot;
void insertIntoBuffer(const Block & block, StorageBuffer::Buffer & buffer) void insertIntoBuffer(const Block & block, StorageBuffer::Buffer & buffer, int32_t metadata_version)
{ {
time_t current_time = time(nullptr); time_t current_time = time(nullptr);
/// Sort the columns in the block. This is necessary to make it easier to concatenate the blocks later. /// Sort the columns in the block. This is necessary to make it easier to concatenate the blocks later.
Block sorted_block = block.sortColumns(); Block sorted_block = block.sortColumns();
if (storage.checkThresholds(buffer, /* direct= */true, current_time, sorted_block.rows(), sorted_block.bytes())) if (storage.checkThresholds(buffer, /* direct= */true, current_time, sorted_block.rows(), sorted_block.bytes()) ||
buffer.metadata_version != metadata_version)
{ {
/** If, after inserting the buffer, the constraints are exceeded, then we will reset the buffer. /** If, after inserting the buffer, the constraints are exceeded, then we will reset the buffer.
* This also protects against unlimited consumption of RAM, since if it is impossible to write to the table, * This also protects against unlimited consumption of RAM, since if it is impossible to write to the table,
@ -639,6 +642,7 @@ private:
*/ */
storage.flushBuffer(buffer, false /* check_thresholds */, true /* locked */); storage.flushBuffer(buffer, false /* check_thresholds */, true /* locked */);
buffer.metadata_version = metadata_version;
} }
if (!buffer.first_write_time) if (!buffer.first_write_time)
@ -1062,13 +1066,12 @@ void StorageBuffer::alter(const AlterCommands & params, ContextPtr local_context
checkAlterIsPossible(params, local_context); checkAlterIsPossible(params, local_context);
auto metadata_snapshot = getInMemoryMetadataPtr(); auto metadata_snapshot = getInMemoryMetadataPtr();
/// Flush all buffers to storages, so that no non-empty blocks of the old /// Flush buffers to the storage because BufferSource skips buffers with old metadata_version.
/// structure remain. Structure of empty blocks will be updated during first
/// insert.
optimize({} /*query*/, metadata_snapshot, {} /*partition_id*/, false /*final*/, false /*deduplicate*/, {}, false /*cleanup*/, local_context); optimize({} /*query*/, metadata_snapshot, {} /*partition_id*/, false /*final*/, false /*deduplicate*/, {}, false /*cleanup*/, local_context);
StorageInMemoryMetadata new_metadata = *metadata_snapshot; StorageInMemoryMetadata new_metadata = *metadata_snapshot;
params.apply(new_metadata, local_context); params.apply(new_metadata, local_context);
new_metadata.metadata_version += 1;
DatabaseCatalog::instance().getDatabase(table_id.database_name)->alterTable(local_context, table_id, new_metadata); DatabaseCatalog::instance().getDatabase(table_id.database_name)->alterTable(local_context, table_id, new_metadata);
setInMemoryMetadata(new_metadata); setInMemoryMetadata(new_metadata);
} }

View File

@ -128,6 +128,18 @@ private:
time_t first_write_time = 0; time_t first_write_time = 0;
Block data; Block data;
/// Schema version, checked to avoid mixing blocks with different sets of columns, from
/// before and after an ALTER. There are some remaining mild problems if an ALTER happens
/// in the middle of a long-running INSERT:
/// * The data produced by the INSERT after the ALTER is not visible to SELECTs until flushed.
/// That's because BufferSource skips buffers with old metadata_version instead of converting
/// them to the latest schema, for simplicity.
/// * If there are concurrent INSERTs, some of which started before the ALTER and some started
/// after, then the buffer's metadata_version will oscillate back and forth between the two
/// schemas, flushing the buffer each time. This is probably fine because long-running INSERTs
/// usually don't produce lots of small blocks.
int32_t metadata_version = 0;
std::unique_lock<std::mutex> lockForReading() const; std::unique_lock<std::mutex> lockForReading() const;
std::unique_lock<std::mutex> lockForWriting() const; std::unique_lock<std::mutex> lockForWriting() const;
std::unique_lock<std::mutex> tryLock() const; std::unique_lock<std::mutex> tryLock() const;

View File

@ -67,13 +67,13 @@ void StorageSystemZooKeeperConnection::fillData(MutableColumns & res_columns, Co
UInt16 port = static_cast<UInt16>(Poco::NumberParser::parseUnsigned(host_port.substr(offset + 1))); UInt16 port = static_cast<UInt16>(Poco::NumberParser::parseUnsigned(host_port.substr(offset + 1)));
UInt32 uptime = zookeeper->getSessionUptime(); UInt32 uptime = zookeeper->getSessionUptime();
time_t time = timeInSeconds(std::chrono::system_clock::now()) - uptime; time_t connected_time = time(nullptr) - uptime;
columns[0]->insert(name); columns[0]->insert(name);
columns[1]->insert(host); columns[1]->insert(host);
columns[2]->insert(port); columns[2]->insert(port);
columns[3]->insert(index); columns[3]->insert(index);
columns[4]->insert(time); columns[4]->insert(connected_time);
columns[5]->insert(uptime); columns[5]->insert(uptime);
columns[6]->insert(zookeeper->expired()); columns[6]->insert(zookeeper->expired());
columns[7]->insert(0); columns[7]->insert(0);

View File

@ -515,6 +515,7 @@ class ClickHouseCluster:
self.spark_session = None self.spark_session = None
self.with_azurite = False self.with_azurite = False
self._azurite_port = 0
# available when with_hdfs == True # available when with_hdfs == True
self.hdfs_host = "hdfs1" self.hdfs_host = "hdfs1"
@ -734,6 +735,13 @@ class ClickHouseCluster:
self._kerberized_kafka_port = get_free_port() self._kerberized_kafka_port = get_free_port()
return self._kerberized_kafka_port return self._kerberized_kafka_port
@property
def azurite_port(self):
if self._azurite_port:
return self._azurite_port
self._azurite_port = get_free_port()
return self._azurite_port
@property @property
def mongo_port(self): def mongo_port(self):
if self._mongo_port: if self._mongo_port:
@ -1436,6 +1444,16 @@ class ClickHouseCluster:
def setup_azurite_cmd(self, instance, env_variables, docker_compose_yml_dir): def setup_azurite_cmd(self, instance, env_variables, docker_compose_yml_dir):
self.with_azurite = True self.with_azurite = True
env_variables["AZURITE_PORT"] = str(self.azurite_port)
env_variables[
"AZURITE_STORAGE_ACCOUNT_URL"
] = f"http://azurite1:{env_variables['AZURITE_PORT']}/devstoreaccount1"
env_variables["AZURITE_CONNECTION_STRING"] = (
f"DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;"
f"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;"
f"BlobEndpoint={env_variables['AZURITE_STORAGE_ACCOUNT_URL']};"
)
self.base_cmd.extend( self.base_cmd.extend(
["--file", p.join(docker_compose_yml_dir, "docker_compose_azurite.yml")] ["--file", p.join(docker_compose_yml_dir, "docker_compose_azurite.yml")]
) )
@ -2524,7 +2542,11 @@ class ClickHouseCluster:
def wait_azurite_to_start(self, timeout=180): def wait_azurite_to_start(self, timeout=180):
from azure.storage.blob import BlobServiceClient from azure.storage.blob import BlobServiceClient
connection_string = "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;" connection_string = (
f"DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;"
f"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;"
f"BlobEndpoint=http://127.0.0.1:{self.env_variables['AZURITE_PORT']}/devstoreaccount1;"
)
time.sleep(1) time.sleep(1)
start = time.time() start = time.time()
while time.time() - start < timeout: while time.time() - start < timeout:

View File

@ -0,0 +1,27 @@
<clickhouse>
<remote_servers>
<test_cluster>
<shard>
<replica>
<host>node1</host>
<port>9000</port>
</replica>
</shard>
<shard>
<replica>
<host>node2</host>
<port>9000</port>
</replica>
</shard>
</test_cluster>
</remote_servers>
<macros>
<cluster>test_cluster</cluster>
</macros>
<merge_tree>
<allow_remote_fs_zero_copy_replication>true</allow_remote_fs_zero_copy_replication>
<ratio_of_defaults_for_sparse_serialization>1.0</ratio_of_defaults_for_sparse_serialization>
</merge_tree>
</clickhouse>

View File

@ -1,50 +0,0 @@
<clickhouse>
<storage_configuration>
<disks>
<blob_storage_disk>
<type>azure_blob_storage</type>
<storage_account_url>http://azurite1:10000/devstoreaccount1</storage_account_url>
<container_name>cont</container_name>
<skip_access_check>false</skip_access_check>
<!-- default credentials for Azurite storage account -->
<account_name>devstoreaccount1</account_name>
<account_key>Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==</account_key>
</blob_storage_disk>
</disks>
<policies>
<blob_storage_policy>
<volumes>
<main>
<disk>blob_storage_disk</disk>
</main>
</volumes>
</blob_storage_policy>
</policies>
</storage_configuration>
<remote_servers>
<test_cluster>
<shard>
<replica>
<host>node1</host>
<port>9000</port>
</replica>
</shard>
<shard>
<replica>
<host>node2</host>
<port>9000</port>
</replica>
</shard>
</test_cluster>
</remote_servers>
<macros>
<cluster>test_cluster</cluster>
</macros>
<merge_tree>
<allow_remote_fs_zero_copy_replication>true</allow_remote_fs_zero_copy_replication>
<ratio_of_defaults_for_sparse_serialization>1.0</ratio_of_defaults_for_sparse_serialization>
</merge_tree>
</clickhouse>

View File

@ -1,6 +1,8 @@
import logging import logging
import pytest import pytest
from helpers.cluster import ClickHouseCluster from helpers.cluster import ClickHouseCluster
from test_storage_azure_blob_storage.test import azure_query
import os
logging.getLogger().setLevel(logging.INFO) logging.getLogger().setLevel(logging.INFO)
@ -15,20 +17,65 @@ CLUSTER_NAME = "test_cluster"
drop_table_statement = f"DROP TABLE {TABLE_NAME} ON CLUSTER {CLUSTER_NAME} SYNC" drop_table_statement = f"DROP TABLE {TABLE_NAME} ON CLUSTER {CLUSTER_NAME} SYNC"
def generate_cluster_def(port):
path = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"./_gen/storage_conf.xml",
)
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w") as f:
f.write(
f"""<clickhouse>
<storage_configuration>
<disks>
<blob_storage_disk>
<type>azure_blob_storage</type>
<storage_account_url>http://azurite1:{port}/devstoreaccount1</storage_account_url>
<container_name>cont</container_name>
<skip_access_check>false</skip_access_check>
<!-- default credentials for Azurite storage account -->
<account_name>devstoreaccount1</account_name>
<account_key>Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==</account_key>
</blob_storage_disk>
</disks>
<policies>
<blob_storage_policy>
<volumes>
<main>
<disk>blob_storage_disk</disk>
</main>
</volumes>
</blob_storage_policy>
</policies>
</storage_configuration>
</clickhouse>
"""
)
return path
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def cluster(): def cluster():
try: try:
cluster = ClickHouseCluster(__file__) cluster = ClickHouseCluster(__file__)
port = cluster.azurite_port
path = generate_cluster_def(port)
cluster.add_instance( cluster.add_instance(
NODE1, NODE1,
main_configs=["configs/config.d/storage_conf.xml"], main_configs=[
"configs/config.d/config.xml",
path,
],
macros={"replica": "1"}, macros={"replica": "1"},
with_azurite=True, with_azurite=True,
with_zookeeper=True, with_zookeeper=True,
) )
cluster.add_instance( cluster.add_instance(
NODE2, NODE2,
main_configs=["configs/config.d/storage_conf.xml"], main_configs=[
"configs/config.d/config.xml",
path,
],
macros={"replica": "2"}, macros={"replica": "2"},
with_azurite=True, with_azurite=True,
with_zookeeper=True, with_zookeeper=True,
@ -57,7 +104,7 @@ def create_table(node, table_name, replica, **additional_settings):
ORDER BY id ORDER BY id
SETTINGS {",".join((k+"="+repr(v) for k, v in settings.items()))}""" SETTINGS {",".join((k+"="+repr(v) for k, v in settings.items()))}"""
node.query(create_table_statement) azure_query(node, create_table_statement)
assert node.query(f"SELECT COUNT(*) FROM {table_name} FORMAT Values") == "(0)" assert node.query(f"SELECT COUNT(*) FROM {table_name} FORMAT Values") == "(0)"
@ -80,27 +127,29 @@ def test_zero_copy_replication(cluster):
values1 = "(0,'data'),(1,'data')" values1 = "(0,'data'),(1,'data')"
values2 = "(2,'data'),(3,'data')" values2 = "(2,'data'),(3,'data')"
node1.query(f"INSERT INTO {TABLE_NAME} VALUES {values1}") azure_query(node1, f"INSERT INTO {TABLE_NAME} VALUES {values1}")
node2.query(f"SYSTEM SYNC REPLICA {TABLE_NAME}") node2.query(f"SYSTEM SYNC REPLICA {TABLE_NAME}")
assert ( assert (
node1.query(f"SELECT * FROM {TABLE_NAME} order by id FORMAT Values") == values1 azure_query(node1, f"SELECT * FROM {TABLE_NAME} order by id FORMAT Values")
== values1
) )
assert ( assert (
node2.query(f"SELECT * FROM {TABLE_NAME} order by id FORMAT Values") == values1 azure_query(node2, f"SELECT * FROM {TABLE_NAME} order by id FORMAT Values")
== values1
) )
# Based on version 21.x - should be only one file with size 100+ (checksums.txt), used by both nodes # Based on version 21.x - should be only one file with size 100+ (checksums.txt), used by both nodes
assert get_large_objects_count(blob_container_client) == 1 assert get_large_objects_count(blob_container_client) == 1
node2.query(f"INSERT INTO {TABLE_NAME} VALUES {values2}") azure_query(node2, f"INSERT INTO {TABLE_NAME} VALUES {values2}")
node1.query(f"SYSTEM SYNC REPLICA {TABLE_NAME}") node1.query(f"SYSTEM SYNC REPLICA {TABLE_NAME}")
assert ( assert (
node2.query(f"SELECT * FROM {TABLE_NAME} order by id FORMAT Values") azure_query(node2, f"SELECT * FROM {TABLE_NAME} order by id FORMAT Values")
== values1 + "," + values2 == values1 + "," + values2
) )
assert ( assert (
node1.query(f"SELECT * FROM {TABLE_NAME} order by id FORMAT Values") azure_query(node1, f"SELECT * FROM {TABLE_NAME} order by id FORMAT Values")
== values1 + "," + values2 == values1 + "," + values2
) )

View File

@ -1,38 +0,0 @@
<clickhouse>
<storage_configuration>
<disks>
<blob_storage_disk>
<type>azure_blob_storage</type>
<storage_account_url>http://azurite1:10000/devstoreaccount1</storage_account_url>
<container_name>cont</container_name>
<skip_access_check>false</skip_access_check>
<!-- default credentials for Azurite storage account -->
<account_name>devstoreaccount1</account_name>
<account_key>Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==</account_key>
<max_single_part_upload_size>100000</max_single_part_upload_size>
<max_single_download_retries>10</max_single_download_retries>
<max_single_read_retries>10</max_single_read_retries>
<!-- NOTE: container_already_exists is omitted to:
a) create it
b) ignore if it already exists, since there are two instances, that conflicts with each other
-->
</blob_storage_disk>
<hdd>
<type>local</type>
<path>/</path>
</hdd>
</disks>
<policies>
<blob_storage_policy>
<volumes>
<main>
<disk>blob_storage_disk</disk>
</main>
<external>
<disk>hdd</disk>
</external>
</volumes>
</blob_storage_policy>
</policies>
</storage_configuration>
</clickhouse>

View File

@ -18,15 +18,63 @@ LOCAL_DISK = "hdd"
CONTAINER_NAME = "cont" CONTAINER_NAME = "cont"
def generate_cluster_def(port):
path = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"./_gen/disk_storage_conf.xml",
)
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w") as f:
f.write(
f"""<clickhouse>
<storage_configuration>
<disks>
<blob_storage_disk>
<type>azure_blob_storage</type>
<storage_account_url>http://azurite1:{port}/devstoreaccount1</storage_account_url>
<container_name>cont</container_name>
<skip_access_check>false</skip_access_check>
<account_name>devstoreaccount1</account_name>
<account_key>Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==</account_key>
<max_single_part_upload_size>100000</max_single_part_upload_size>
<max_single_download_retries>10</max_single_download_retries>
<max_single_read_retries>10</max_single_read_retries>
</blob_storage_disk>
<hdd>
<type>local</type>
<path>/</path>
</hdd>
</disks>
<policies>
<blob_storage_policy>
<volumes>
<main>
<disk>blob_storage_disk</disk>
</main>
<external>
<disk>hdd</disk>
</external>
</volumes>
</blob_storage_policy>
</policies>
</storage_configuration>
</clickhouse>
"""
)
return path
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def cluster(): def cluster():
try: try:
cluster = ClickHouseCluster(__file__) cluster = ClickHouseCluster(__file__)
port = cluster.azurite_port
path = generate_cluster_def(port)
cluster.add_instance( cluster.add_instance(
NODE_NAME, NODE_NAME,
main_configs=[ main_configs=[
"configs/config.d/storage_conf.xml",
"configs/config.d/bg_processing_pool_conf.xml", "configs/config.d/bg_processing_pool_conf.xml",
path,
], ],
with_azurite=True, with_azurite=True,
) )
@ -490,9 +538,7 @@ def test_apply_new_settings(cluster):
create_table(node, TABLE_NAME) create_table(node, TABLE_NAME)
config_path = os.path.join( config_path = os.path.join(
SCRIPT_DIR, SCRIPT_DIR,
"./{}/node/configs/config.d/storage_conf.xml".format( "./_gen/disk_storage_conf.xml".format(cluster.instances_dir_name),
cluster.instances_dir_name
),
) )
azure_query( azure_query(

View File

@ -1,14 +1,12 @@
<clickhouse> <clickhouse>
<named_collections> <named_collections>
<azure_conf1> <azure_conf1>
<connection_string>DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite1:10000/devstoreaccount1;</connection_string>
<container>cont</container> <container>cont</container>
<blob_path>test_simple_write_named.csv</blob_path> <blob_path>test_simple_write_named.csv</blob_path>
<structure>key UInt64, data String</structure> <structure>key UInt64, data String</structure>
<format>CSV</format> <format>CSV</format>
</azure_conf1> </azure_conf1>
<azure_conf2> <azure_conf2>
<storage_account_url>http://azurite1:10000/devstoreaccount1</storage_account_url>
<account_name>devstoreaccount1</account_name> <account_name>devstoreaccount1</account_name>
<account_key>Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==</account_key> <account_key>Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==</account_key>
</azure_conf2> </azure_conf2>

View File

@ -29,7 +29,6 @@ def cluster():
with_azurite=True, with_azurite=True,
) )
cluster.start() cluster.start()
yield cluster yield cluster
finally: finally:
cluster.shutdown() cluster.shutdown()
@ -69,19 +68,29 @@ def azure_query(
continue continue
def get_azure_file_content(filename): def get_azure_file_content(filename, port):
container_name = "cont" container_name = "cont"
connection_string = "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;" connection_string = (
blob_service_client = BlobServiceClient.from_connection_string(connection_string) f"DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;"
f"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;"
f"BlobEndpoint=http://127.0.0.1:{port}/devstoreaccount1;"
)
blob_service_client = BlobServiceClient.from_connection_string(
str(connection_string)
)
container_client = blob_service_client.get_container_client(container_name) container_client = blob_service_client.get_container_client(container_name)
blob_client = container_client.get_blob_client(filename) blob_client = container_client.get_blob_client(filename)
download_stream = blob_client.download_blob() download_stream = blob_client.download_blob()
return download_stream.readall().decode("utf-8") return download_stream.readall().decode("utf-8")
def put_azure_file_content(filename, data): def put_azure_file_content(filename, port, data):
container_name = "cont" container_name = "cont"
connection_string = "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;" connection_string = (
f"DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;"
f"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;"
f"BlobEndpoint=http://127.0.0.1:{port}/devstoreaccount1;"
)
blob_service_client = BlobServiceClient.from_connection_string(connection_string) blob_service_client = BlobServiceClient.from_connection_string(connection_string)
try: try:
container_client = blob_service_client.create_container(container_name) container_client = blob_service_client.create_container(container_name)
@ -94,8 +103,13 @@ def put_azure_file_content(filename, data):
@pytest.fixture(autouse=True, scope="function") @pytest.fixture(autouse=True, scope="function")
def delete_all_files(): def delete_all_files(cluster):
connection_string = "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;" port = cluster.env_variables["AZURITE_PORT"]
connection_string = (
f"DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;"
f"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;"
f"BlobEndpoint=http://127.0.0.1:{port}/devstoreaccount1;"
)
blob_service_client = BlobServiceClient.from_connection_string(connection_string) blob_service_client = BlobServiceClient.from_connection_string(connection_string)
containers = blob_service_client.list_containers() containers = blob_service_client.list_containers()
for container in containers: for container in containers:
@ -115,7 +129,8 @@ def test_create_table_connection_string(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
azure_query( azure_query(
node, node,
"CREATE TABLE test_create_table_conn_string (key UInt64, data String) Engine = AzureBlobStorage('DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite1:10000/devstoreaccount1/;', 'cont', 'test_create_connection_string', 'CSV')", f"CREATE TABLE test_create_table_conn_string (key UInt64, data String) Engine = AzureBlobStorage('{cluster.env_variables['AZURITE_CONNECTION_STRING']}',"
f"'cont', 'test_create_connection_string', 'CSV')",
) )
@ -123,57 +138,67 @@ def test_create_table_account_string(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
azure_query( azure_query(
node, node,
"CREATE TABLE test_create_table_account_url (key UInt64, data String) Engine = AzureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', 'test_create_connection_string', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV')", f"CREATE TABLE test_create_table_account_url (key UInt64, data String) Engine = AzureBlobStorage('{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}',"
f"'cont', 'test_create_connection_string', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV')",
) )
def test_simple_write_account_string(cluster): def test_simple_write_account_string(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
port = cluster.env_variables["AZURITE_PORT"]
azure_query( azure_query(
node, node,
"CREATE TABLE test_simple_write (key UInt64, data String) Engine = AzureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', 'test_simple_write.csv', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV')", f"CREATE TABLE test_simple_write (key UInt64, data String) Engine = AzureBlobStorage('{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}',"
f" 'cont', 'test_simple_write.csv', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV')",
) )
azure_query(node, "INSERT INTO test_simple_write VALUES (1, 'a')") azure_query(node, "INSERT INTO test_simple_write VALUES (1, 'a')")
print(get_azure_file_content("test_simple_write.csv")) print(get_azure_file_content("test_simple_write.csv", port))
assert get_azure_file_content("test_simple_write.csv") == '1,"a"\n' assert get_azure_file_content("test_simple_write.csv", port) == '1,"a"\n'
def test_simple_write_connection_string(cluster): def test_simple_write_connection_string(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
port = cluster.env_variables["AZURITE_PORT"]
azure_query( azure_query(
node, node,
"CREATE TABLE test_simple_write_connection_string (key UInt64, data String) Engine = AzureBlobStorage('DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite1:10000/devstoreaccount1;', 'cont', 'test_simple_write_c.csv', 'CSV')", f"CREATE TABLE test_simple_write_connection_string (key UInt64, data String) Engine = AzureBlobStorage('{cluster.env_variables['AZURITE_CONNECTION_STRING']}', "
f"'cont', 'test_simple_write_c.csv', 'CSV')",
) )
azure_query(node, "INSERT INTO test_simple_write_connection_string VALUES (1, 'a')") azure_query(node, "INSERT INTO test_simple_write_connection_string VALUES (1, 'a')")
print(get_azure_file_content("test_simple_write_c.csv")) print(get_azure_file_content("test_simple_write_c.csv", port))
assert get_azure_file_content("test_simple_write_c.csv") == '1,"a"\n' assert get_azure_file_content("test_simple_write_c.csv", port) == '1,"a"\n'
def test_simple_write_named_collection_1(cluster): def test_simple_write_named_collection_1(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
port = cluster.env_variables["AZURITE_PORT"]
azure_query( azure_query(
node, node,
"CREATE TABLE test_simple_write_named_collection_1 (key UInt64, data String) Engine = AzureBlobStorage(azure_conf1)", f"CREATE TABLE test_simple_write_named_collection_1 (key UInt64, data String) Engine = AzureBlobStorage(azure_conf1, "
f"connection_string = '{cluster.env_variables['AZURITE_CONNECTION_STRING']}')",
) )
azure_query( azure_query(
node, "INSERT INTO test_simple_write_named_collection_1 VALUES (1, 'a')" node, "INSERT INTO test_simple_write_named_collection_1 VALUES (1, 'a')"
) )
print(get_azure_file_content("test_simple_write_named.csv")) print(get_azure_file_content("test_simple_write_named.csv", port))
assert get_azure_file_content("test_simple_write_named.csv") == '1,"a"\n' assert get_azure_file_content("test_simple_write_named.csv", port) == '1,"a"\n'
azure_query(node, "TRUNCATE TABLE test_simple_write_named_collection_1") azure_query(node, "TRUNCATE TABLE test_simple_write_named_collection_1")
def test_simple_write_named_collection_2(cluster): def test_simple_write_named_collection_2(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
port = cluster.env_variables["AZURITE_PORT"]
azure_query( azure_query(
node, node,
"CREATE TABLE test_simple_write_named_collection_2 (key UInt64, data String) Engine = AzureBlobStorage(azure_conf2, container='cont', blob_path='test_simple_write_named_2.csv', format='CSV')", f"CREATE TABLE test_simple_write_named_collection_2 (key UInt64, data String) Engine = AzureBlobStorage(azure_conf2, "
f"storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', "
f"container='cont', blob_path='test_simple_write_named_2.csv', format='CSV')",
) )
azure_query( azure_query(
node, "INSERT INTO test_simple_write_named_collection_2 VALUES (1, 'a')" node, "INSERT INTO test_simple_write_named_collection_2 VALUES (1, 'a')"
) )
print(get_azure_file_content("test_simple_write_named_2.csv")) print(get_azure_file_content("test_simple_write_named_2.csv", port))
assert get_azure_file_content("test_simple_write_named_2.csv") == '1,"a"\n' assert get_azure_file_content("test_simple_write_named_2.csv", port) == '1,"a"\n'
def test_partition_by(cluster): def test_partition_by(cluster):
@ -182,16 +207,19 @@ def test_partition_by(cluster):
partition_by = "column3" partition_by = "column3"
values = "(1, 2, 3), (3, 2, 1), (78, 43, 45)" values = "(1, 2, 3), (3, 2, 1), (78, 43, 45)"
filename = "test_{_partition_id}.csv" filename = "test_{_partition_id}.csv"
port = cluster.env_variables["AZURITE_PORT"]
azure_query( azure_query(
node, node,
f"CREATE TABLE test_partitioned_write ({table_format}) Engine = AzureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', '{filename}', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV') PARTITION BY {partition_by}", f"CREATE TABLE test_partitioned_write ({table_format}) Engine = AzureBlobStorage('{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}',"
f" 'cont', '{filename}', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV') "
f"PARTITION BY {partition_by}",
) )
azure_query(node, f"INSERT INTO test_partitioned_write VALUES {values}") azure_query(node, f"INSERT INTO test_partitioned_write VALUES {values}")
assert "1,2,3\n" == get_azure_file_content("test_3.csv") assert "1,2,3\n" == get_azure_file_content("test_3.csv", port)
assert "3,2,1\n" == get_azure_file_content("test_1.csv") assert "3,2,1\n" == get_azure_file_content("test_1.csv", port)
assert "78,43,45\n" == get_azure_file_content("test_45.csv") assert "78,43,45\n" == get_azure_file_content("test_45.csv", port)
def test_partition_by_string_column(cluster): def test_partition_by_string_column(cluster):
@ -200,15 +228,18 @@ def test_partition_by_string_column(cluster):
partition_by = "col_str" partition_by = "col_str"
values = "(1, 'foo/bar'), (3, 'йцук'), (78, '你好')" values = "(1, 'foo/bar'), (3, 'йцук'), (78, '你好')"
filename = "test_{_partition_id}.csv" filename = "test_{_partition_id}.csv"
port = cluster.env_variables["AZURITE_PORT"]
azure_query( azure_query(
node, node,
f"CREATE TABLE test_partitioned_string_write ({table_format}) Engine = AzureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', '{filename}', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV') PARTITION BY {partition_by}", f"CREATE TABLE test_partitioned_string_write ({table_format}) Engine = AzureBlobStorage('{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}',"
f" 'cont', '{filename}', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV') "
f"PARTITION BY {partition_by}",
) )
azure_query(node, f"INSERT INTO test_partitioned_string_write VALUES {values}") azure_query(node, f"INSERT INTO test_partitioned_string_write VALUES {values}")
assert '1,"foo/bar"\n' == get_azure_file_content("test_foo/bar.csv") assert '1,"foo/bar"\n' == get_azure_file_content("test_foo/bar.csv", port)
assert '3,"йцук"\n' == get_azure_file_content("test_йцук.csv") assert '3,"йцук"\n' == get_azure_file_content("test_йцук.csv", port)
assert '78,"你好"\n' == get_azure_file_content("test_你好.csv") assert '78,"你好"\n' == get_azure_file_content("test_你好.csv", port)
def test_partition_by_const_column(cluster): def test_partition_by_const_column(cluster):
@ -218,46 +249,54 @@ def test_partition_by_const_column(cluster):
partition_by = "'88'" partition_by = "'88'"
values_csv = "1,2,3\n3,2,1\n78,43,45\n" values_csv = "1,2,3\n3,2,1\n78,43,45\n"
filename = "test_{_partition_id}.csv" filename = "test_{_partition_id}.csv"
port = cluster.env_variables["AZURITE_PORT"]
azure_query( azure_query(
node, node,
f"CREATE TABLE test_partitioned_const_write ({table_format}) Engine = AzureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', '{filename}', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV') PARTITION BY {partition_by}", f"CREATE TABLE test_partitioned_const_write ({table_format}) Engine = AzureBlobStorage('{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}',"
f" 'cont', '{filename}', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV')"
f" PARTITION BY {partition_by}",
) )
azure_query(node, f"INSERT INTO test_partitioned_const_write VALUES {values}") azure_query(node, f"INSERT INTO test_partitioned_const_write VALUES {values}")
assert values_csv == get_azure_file_content("test_88.csv") assert values_csv == get_azure_file_content("test_88.csv", port)
def test_truncate(cluster): def test_truncate(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
port = cluster.env_variables["AZURITE_PORT"]
azure_query( azure_query(
node, node,
"CREATE TABLE test_truncate (key UInt64, data String) Engine = AzureBlobStorage(azure_conf2, container='cont', blob_path='test_truncate.csv', format='CSV')", f"CREATE TABLE test_truncate (key UInt64, data String) Engine = AzureBlobStorage(azure_conf2, "
f"storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', container='cont', blob_path='test_truncate.csv', format='CSV')",
) )
azure_query(node, "INSERT INTO test_truncate VALUES (1, 'a')") azure_query(node, "INSERT INTO test_truncate VALUES (1, 'a')")
assert get_azure_file_content("test_truncate.csv") == '1,"a"\n' assert get_azure_file_content("test_truncate.csv", port) == '1,"a"\n'
azure_query(node, "TRUNCATE TABLE test_truncate") azure_query(node, "TRUNCATE TABLE test_truncate")
with pytest.raises(Exception): with pytest.raises(Exception):
print(get_azure_file_content("test_truncate.csv")) print(get_azure_file_content("test_truncate.csv", port))
def test_simple_read_write(cluster): def test_simple_read_write(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
port = cluster.env_variables["AZURITE_PORT"]
azure_query( azure_query(
node, node,
"CREATE TABLE test_simple_read_write (key UInt64, data String) Engine = AzureBlobStorage(azure_conf2, container='cont', blob_path='test_simple_read_write.csv', format='CSV')", f"CREATE TABLE test_simple_read_write (key UInt64, data String) Engine = AzureBlobStorage(azure_conf2, "
f"storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', container='cont', blob_path='test_simple_read_write.csv', "
f"format='CSV')",
) )
azure_query(node, "INSERT INTO test_simple_read_write VALUES (1, 'a')") azure_query(node, "INSERT INTO test_simple_read_write VALUES (1, 'a')")
assert get_azure_file_content("test_simple_read_write.csv") == '1,"a"\n' assert get_azure_file_content("test_simple_read_write.csv", port) == '1,"a"\n'
print(azure_query(node, "SELECT * FROM test_simple_read_write")) print(azure_query(node, "SELECT * FROM test_simple_read_write"))
assert azure_query(node, "SELECT * FROM test_simple_read_write") == "1\ta\n" assert azure_query(node, "SELECT * FROM test_simple_read_write") == "1\ta\n"
def test_create_new_files_on_insert(cluster): def test_create_new_files_on_insert(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
azure_query( azure_query(
node, node,
f"create table test_multiple_inserts(a Int32, b String) ENGINE = AzureBlobStorage(azure_conf2, container='cont', blob_path='test_parquet', format='Parquet')", f"create table test_multiple_inserts(a Int32, b String) ENGINE = AzureBlobStorage(azure_conf2, "
f"storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', container='cont', blob_path='test_parquet', format='Parquet')",
) )
azure_query(node, "truncate table test_multiple_inserts") azure_query(node, "truncate table test_multiple_inserts")
azure_query( azure_query(
@ -281,10 +320,10 @@ def test_create_new_files_on_insert(cluster):
def test_overwrite(cluster): def test_overwrite(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
azure_query( azure_query(
node, node,
f"create table test_overwrite(a Int32, b String) ENGINE = AzureBlobStorage(azure_conf2, container='cont', blob_path='test_parquet_overwrite', format='Parquet')", f"create table test_overwrite(a Int32, b String) ENGINE = AzureBlobStorage(azure_conf2, "
f"storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', container='cont', blob_path='test_parquet_overwrite', format='Parquet')",
) )
azure_query(node, "truncate table test_overwrite") azure_query(node, "truncate table test_overwrite")
@ -308,7 +347,8 @@ def test_insert_with_path_with_globs(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
azure_query( azure_query(
node, node,
f"create table test_insert_globs(a Int32, b String) ENGINE = AzureBlobStorage(azure_conf2, container='cont', blob_path='test_insert_with_globs*', format='Parquet')", f"create table test_insert_globs(a Int32, b String) ENGINE = AzureBlobStorage(azure_conf2, "
f"storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', container='cont', blob_path='test_insert_with_globs*', format='Parquet')",
) )
node.query_and_get_error( node.query_and_get_error(
f"insert into table function test_insert_globs SELECT number, randomString(100) FROM numbers(500)" f"insert into table function test_insert_globs SELECT number, randomString(100) FROM numbers(500)"
@ -331,7 +371,8 @@ def test_put_get_with_globs(cluster):
azure_query( azure_query(
node, node,
f"CREATE TABLE test_put_{i}_{j} ({table_format}) Engine = AzureBlobStorage(azure_conf2, container='cont', blob_path='{path}', format='CSV')", f"CREATE TABLE test_put_{i}_{j} ({table_format}) Engine = AzureBlobStorage(azure_conf2, "
f"storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', container='cont', blob_path='{path}', format='CSV')",
) )
query = f"insert into test_put_{i}_{j} VALUES {values}" query = f"insert into test_put_{i}_{j} VALUES {values}"
@ -339,7 +380,8 @@ def test_put_get_with_globs(cluster):
azure_query( azure_query(
node, node,
f"CREATE TABLE test_glob_select ({table_format}) Engine = AzureBlobStorage(azure_conf2, container='cont', blob_path='{unique_prefix}/*_{{a,b,c,d}}/?.csv', format='CSV')", f"CREATE TABLE test_glob_select ({table_format}) Engine = AzureBlobStorage(azure_conf2, "
f"storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', container='cont', blob_path='{unique_prefix}/*_{{a,b,c,d}}/?.csv', format='CSV')",
) )
query = "select sum(column1), sum(column2), sum(column3), min(_file), max(_path) from test_glob_select" query = "select sum(column1), sum(column2), sum(column3), min(_file), max(_path) from test_glob_select"
assert azure_query(node, query).splitlines() == [ assert azure_query(node, query).splitlines() == [
@ -363,7 +405,8 @@ def test_azure_glob_scheherazade(cluster):
unique_num = random.randint(1, 10000) unique_num = random.randint(1, 10000)
azure_query( azure_query(
node, node,
f"CREATE TABLE test_scheherazade_{i}_{unique_num} ({table_format}) Engine = AzureBlobStorage(azure_conf2, container='cont', blob_path='{path}', format='CSV')", f"CREATE TABLE test_scheherazade_{i}_{unique_num} ({table_format}) Engine = AzureBlobStorage(azure_conf2, "
f"storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', container='cont', blob_path='{path}', format='CSV')",
) )
query = ( query = (
f"insert into test_scheherazade_{i}_{unique_num} VALUES {values}" f"insert into test_scheherazade_{i}_{unique_num} VALUES {values}"
@ -382,7 +425,8 @@ def test_azure_glob_scheherazade(cluster):
azure_query( azure_query(
node, node,
f"CREATE TABLE test_glob_select_scheherazade ({table_format}) Engine = AzureBlobStorage(azure_conf2, container='cont', blob_path='night_*/tale.csv', format='CSV')", f"CREATE TABLE test_glob_select_scheherazade ({table_format}) Engine = AzureBlobStorage(azure_conf2, "
f"storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', container='cont', blob_path='night_*/tale.csv', format='CSV')",
) )
query = "select count(), sum(column1), sum(column2), sum(column3) from test_glob_select_scheherazade" query = "select count(), sum(column1), sum(column2), sum(column3) from test_glob_select_scheherazade"
assert azure_query(node, query).splitlines() == ["1001\t1001\t1001\t1001"] assert azure_query(node, query).splitlines() == ["1001\t1001\t1001\t1001"]
@ -394,6 +438,7 @@ def test_azure_glob_scheherazade(cluster):
) )
def test_storage_azure_get_gzip(cluster, extension, method): def test_storage_azure_get_gzip(cluster, extension, method):
node = cluster.instances["node"] node = cluster.instances["node"]
port = cluster.env_variables["AZURITE_PORT"]
filename = f"test_get_gzip.{extension}" filename = f"test_get_gzip.{extension}"
name = f"test_get_gzip_{extension}" name = f"test_get_gzip_{extension}"
data = [ data = [
@ -420,14 +465,13 @@ def test_storage_azure_get_gzip(cluster, extension, method):
compressed = gzip.GzipFile(fileobj=buf, mode="wb") compressed = gzip.GzipFile(fileobj=buf, mode="wb")
compressed.write(("\n".join(data)).encode()) compressed.write(("\n".join(data)).encode())
compressed.close() compressed.close()
put_azure_file_content(filename, buf.getvalue()) put_azure_file_content(filename, port, buf.getvalue())
azure_query( azure_query(
node, node,
f"""CREATE TABLE {name} (name String, id UInt32) ENGINE = AzureBlobStorage( f"CREATE TABLE {name} (name String, id UInt32) ENGINE = AzureBlobStorage( azure_conf2,"
azure_conf2, container='cont', blob_path ='{filename}', f" storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', container='cont', blob_path ='{filename}',"
format='CSV', f"format='CSV', compression='{method}')",
compression='{method}')""",
) )
assert azure_query(node, f"SELECT sum(id) FROM {name}").splitlines() == ["565"] assert azure_query(node, f"SELECT sum(id) FROM {name}").splitlines() == ["565"]
@ -439,7 +483,9 @@ def test_schema_inference_no_globs(cluster):
table_format = "column1 UInt32, column2 String, column3 UInt32" table_format = "column1 UInt32, column2 String, column3 UInt32"
azure_query( azure_query(
node, node,
f"CREATE TABLE test_schema_inference_src ({table_format}) Engine = AzureBlobStorage(azure_conf2, container='cont', blob_path='test_schema_inference_no_globs.csv', format='CSVWithNames')", f"CREATE TABLE test_schema_inference_src ({table_format}) Engine = AzureBlobStorage(azure_conf2, "
f"storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', container='cont', "
f"blob_path='test_schema_inference_no_globs.csv', format='CSVWithNames')",
) )
query = f"insert into test_schema_inference_src SELECT number, toString(number), number * number FROM numbers(1000)" query = f"insert into test_schema_inference_src SELECT number, toString(number), number * number FROM numbers(1000)"
@ -447,7 +493,8 @@ def test_schema_inference_no_globs(cluster):
azure_query( azure_query(
node, node,
f"CREATE TABLE test_select_inference Engine = AzureBlobStorage(azure_conf2, container='cont', blob_path='test_schema_inference_no_globs.csv')", f"CREATE TABLE test_select_inference Engine = AzureBlobStorage(azure_conf2, "
f"storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', container='cont', blob_path='test_schema_inference_no_globs.csv')",
) )
print(node.query("SHOW CREATE TABLE test_select_inference")) print(node.query("SHOW CREATE TABLE test_select_inference"))
@ -474,7 +521,9 @@ def test_schema_inference_from_globs(cluster):
azure_query( azure_query(
node, node,
f"CREATE TABLE test_schema_{i}_{j} ({table_format}) Engine = AzureBlobStorage(azure_conf2, container='cont', blob_path='{path}', format='CSVWithNames')", f"CREATE TABLE test_schema_{i}_{j} ({table_format}) Engine = AzureBlobStorage(azure_conf2, "
f"storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', container='cont', "
f"blob_path='{path}', format='CSVWithNames')",
) )
query = f"insert into test_schema_{i}_{j} VALUES {values}" query = f"insert into test_schema_{i}_{j} VALUES {values}"
@ -482,7 +531,8 @@ def test_schema_inference_from_globs(cluster):
azure_query( azure_query(
node, node,
f"CREATE TABLE test_glob_select_inference Engine = AzureBlobStorage(azure_conf2, container='cont', blob_path='{unique_prefix}/*_{{a,b,c,d}}/?.csv')", f"CREATE TABLE test_glob_select_inference Engine = AzureBlobStorage(azure_conf2, "
f"storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', container='cont', blob_path='{unique_prefix}/*_{{a,b,c,d}}/?.csv')",
) )
print(node.query("SHOW CREATE TABLE test_glob_select_inference")) print(node.query("SHOW CREATE TABLE test_glob_select_inference"))
@ -497,36 +547,47 @@ def test_schema_inference_from_globs(cluster):
def test_simple_write_account_string_table_function(cluster): def test_simple_write_account_string_table_function(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
port = cluster.env_variables["AZURITE_PORT"]
azure_query( azure_query(
node, node,
"INSERT INTO TABLE FUNCTION azureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', 'test_simple_write_tf.csv', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV', 'auto', 'key UInt64, data String') VALUES (1, 'a')", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', "
f"'cont', 'test_simple_write_tf.csv', 'devstoreaccount1', "
f"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV', 'auto', 'key UInt64, data String')"
f" VALUES (1, 'a')",
) )
print(get_azure_file_content("test_simple_write_tf.csv")) print(get_azure_file_content("test_simple_write_tf.csv", port))
assert get_azure_file_content("test_simple_write_tf.csv") == '1,"a"\n' assert get_azure_file_content("test_simple_write_tf.csv", port) == '1,"a"\n'
def test_simple_write_connection_string_table_function(cluster): def test_simple_write_connection_string_table_function(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
port = cluster.env_variables["AZURITE_PORT"]
azure_query( azure_query(
node, node,
"INSERT INTO TABLE FUNCTION azureBlobStorage('DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite1:10000/devstoreaccount1;', 'cont', 'test_simple_write_connection_tf.csv', 'CSV', 'auto', 'key UInt64, data String') VALUES (1, 'a')", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{cluster.env_variables['AZURITE_CONNECTION_STRING']}', "
f"'cont', 'test_simple_write_connection_tf.csv', 'CSV', 'auto', 'key UInt64, data String') VALUES (1, 'a')",
)
print(get_azure_file_content("test_simple_write_connection_tf.csv", port))
assert (
get_azure_file_content("test_simple_write_connection_tf.csv", port) == '1,"a"\n'
) )
print(get_azure_file_content("test_simple_write_connection_tf.csv"))
assert get_azure_file_content("test_simple_write_connection_tf.csv") == '1,"a"\n'
def test_simple_write_named_collection_1_table_function(cluster): def test_simple_write_named_collection_1_table_function(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
port = cluster.env_variables["AZURITE_PORT"]
azure_query( azure_query(
node, node,
"INSERT INTO TABLE FUNCTION azureBlobStorage(azure_conf1) VALUES (1, 'a')", f"INSERT INTO TABLE FUNCTION azureBlobStorage(azure_conf1, "
f"connection_string = '{cluster.env_variables['AZURITE_CONNECTION_STRING']}') VALUES (1, 'a')",
) )
print(get_azure_file_content("test_simple_write_named.csv")) print(get_azure_file_content("test_simple_write_named.csv", port))
assert get_azure_file_content("test_simple_write_named.csv") == '1,"a"\n' assert get_azure_file_content("test_simple_write_named.csv", port) == '1,"a"\n'
azure_query( azure_query(
node, node,
"CREATE TABLE drop_table (key UInt64, data String) Engine = AzureBlobStorage(azure_conf1)", f"CREATE TABLE drop_table (key UInt64, data String) Engine = AzureBlobStorage(azure_conf1, "
f"connection_string = '{cluster.env_variables['AZURITE_CONNECTION_STRING']};')",
) )
azure_query( azure_query(
@ -537,13 +598,14 @@ def test_simple_write_named_collection_1_table_function(cluster):
def test_simple_write_named_collection_2_table_function(cluster): def test_simple_write_named_collection_2_table_function(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
port = cluster.env_variables["AZURITE_PORT"]
azure_query( azure_query(
node, node,
"INSERT INTO TABLE FUNCTION azureBlobStorage(azure_conf2, container='cont', blob_path='test_simple_write_named_2_tf.csv', format='CSV', structure='key UInt64, data String') VALUES (1, 'a')", f"INSERT INTO TABLE FUNCTION azureBlobStorage(azure_conf2, storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}',"
f" container='cont', blob_path='test_simple_write_named_2_tf.csv', format='CSV', structure='key UInt64, data String') VALUES (1, 'a')",
) )
print(get_azure_file_content("test_simple_write_named_2_tf.csv")) print(get_azure_file_content("test_simple_write_named_2_tf.csv", port))
assert get_azure_file_content("test_simple_write_named_2_tf.csv") == '1,"a"\n' assert get_azure_file_content("test_simple_write_named_2_tf.csv", port) == '1,"a"\n'
def test_put_get_with_globs_tf(cluster): def test_put_get_with_globs_tf(cluster):
@ -562,9 +624,14 @@ def test_put_get_with_globs_tf(cluster):
azure_query( azure_query(
node, node,
f"INSERT INTO TABLE FUNCTION azureBlobStorage(azure_conf2, container='cont', blob_path='{path}', format='CSV', compression='auto', structure='{table_format}') VALUES {values}", f"INSERT INTO TABLE FUNCTION azureBlobStorage(azure_conf2, storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}',"
f" container='cont', blob_path='{path}', format='CSV', compression='auto', structure='{table_format}') VALUES {values}",
) )
query = f"select sum(column1), sum(column2), sum(column3), min(_file), max(_path) from azureBlobStorage(azure_conf2, container='cont', blob_path='{unique_prefix}/*_{{a,b,c,d}}/?.csv', format='CSV', structure='{table_format}')" query = (
f"select sum(column1), sum(column2), sum(column3), min(_file), max(_path) from azureBlobStorage(azure_conf2, "
f"storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', container='cont', "
f"blob_path='{unique_prefix}/*_{{a,b,c,d}}/?.csv', format='CSV', structure='{table_format}')"
)
assert azure_query(node, query).splitlines() == [ assert azure_query(node, query).splitlines() == [
"450\t450\t900\t0.csv\t{bucket}/{max_path}".format( "450\t450\t900\t0.csv\t{bucket}/{max_path}".format(
bucket="cont", max_path=max_path bucket="cont", max_path=max_path
@ -576,10 +643,18 @@ def test_schema_inference_no_globs_tf(cluster):
node = cluster.instances["node"] # type: ClickHouseInstance node = cluster.instances["node"] # type: ClickHouseInstance
table_format = "column1 UInt32, column2 String, column3 UInt32" table_format = "column1 UInt32, column2 String, column3 UInt32"
query = f"insert into table function azureBlobStorage(azure_conf2, container='cont', blob_path='test_schema_inference_no_globs_tf.csv', format='CSVWithNames', structure='{table_format}') SELECT number, toString(number), number * number FROM numbers(1000)" query = (
f"insert into table function azureBlobStorage(azure_conf2, storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', "
f"container='cont', blob_path='test_schema_inference_no_globs_tf.csv', format='CSVWithNames', structure='{table_format}') "
f"SELECT number, toString(number), number * number FROM numbers(1000)"
)
azure_query(node, query) azure_query(node, query)
query = "select sum(column1), sum(length(column2)), sum(column3), min(_file), max(_path) from azureBlobStorage(azure_conf2, container='cont', blob_path='test_schema_inference_no_globs_tf.csv')" query = (
f"select sum(column1), sum(length(column2)), sum(column3), min(_file), max(_path) from azureBlobStorage(azure_conf2, "
f"storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', container='cont', "
f"blob_path='test_schema_inference_no_globs_tf.csv')"
)
assert azure_query(node, query).splitlines() == [ assert azure_query(node, query).splitlines() == [
"499500\t2890\t332833500\ttest_schema_inference_no_globs_tf.csv\tcont/test_schema_inference_no_globs_tf.csv" "499500\t2890\t332833500\ttest_schema_inference_no_globs_tf.csv\tcont/test_schema_inference_no_globs_tf.csv"
] ]
@ -600,10 +675,17 @@ def test_schema_inference_from_globs_tf(cluster):
max_path = max(path, max_path) max_path = max(path, max_path)
values = f"({i},{j},{i + j})" values = f"({i},{j},{i + j})"
query = f"insert into table function azureBlobStorage(azure_conf2, container='cont', blob_path='{path}', format='CSVWithNames', structure='{table_format}') VALUES {values}" query = (
f"insert into table function azureBlobStorage(azure_conf2, storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', "
f"container='cont', blob_path='{path}', format='CSVWithNames', structure='{table_format}') VALUES {values}"
)
azure_query(node, query) azure_query(node, query)
query = f"select sum(column1), sum(column2), sum(column3), min(_file), max(_path) from azureBlobStorage(azure_conf2, container='cont', blob_path='{unique_prefix}/*_{{a,b,c,d}}/?.csv')" query = (
f"select sum(column1), sum(column2), sum(column3), min(_file), max(_path) from azureBlobStorage(azure_conf2, "
f"storage_account_url = '{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', container='cont', "
f"blob_path='{unique_prefix}/*_{{a,b,c,d}}/?.csv')"
)
assert azure_query(node, query).splitlines() == [ assert azure_query(node, query).splitlines() == [
"450\t450\t900\t0.csv\t{bucket}/{max_path}".format( "450\t450\t900\t0.csv\t{bucket}/{max_path}".format(
bucket="cont", max_path=max_path bucket="cont", max_path=max_path
@ -617,15 +699,18 @@ def test_partition_by_tf(cluster):
partition_by = "column3" partition_by = "column3"
values = "(1, 2, 3), (3, 2, 1), (78, 43, 45)" values = "(1, 2, 3), (3, 2, 1), (78, 43, 45)"
filename = "test_partition_tf_{_partition_id}.csv" filename = "test_partition_tf_{_partition_id}.csv"
port = cluster.env_variables["AZURITE_PORT"]
azure_query( azure_query(
node, node,
f"INSERT INTO TABLE FUNCTION azureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', '{filename}', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV', 'auto', '{table_format}') PARTITION BY {partition_by} VALUES {values}", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', "
f"'cont', '{filename}', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', "
f"'CSV', 'auto', '{table_format}') PARTITION BY {partition_by} VALUES {values}",
) )
assert "1,2,3\n" == get_azure_file_content("test_partition_tf_3.csv") assert "1,2,3\n" == get_azure_file_content("test_partition_tf_3.csv", port)
assert "3,2,1\n" == get_azure_file_content("test_partition_tf_1.csv") assert "3,2,1\n" == get_azure_file_content("test_partition_tf_1.csv", port)
assert "78,43,45\n" == get_azure_file_content("test_partition_tf_45.csv") assert "78,43,45\n" == get_azure_file_content("test_partition_tf_45.csv", port)
def test_filter_using_file(cluster): def test_filter_using_file(cluster):
@ -637,45 +722,64 @@ def test_filter_using_file(cluster):
azure_query( azure_query(
node, node,
f"INSERT INTO TABLE FUNCTION azureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', '{filename}', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV', 'auto', '{table_format}') PARTITION BY {partition_by} VALUES {values}", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', 'cont', '{filename}', "
f"'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV', 'auto', "
f"'{table_format}') PARTITION BY {partition_by} VALUES {values}",
) )
query = f"select count(*) from azureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', 'test_partition_tf_*.csv', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV', 'auto', '{table_format}') WHERE _file='test_partition_tf_3.csv'" query = (
f"select count(*) from azureBlobStorage('{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', 'cont', 'test_partition_tf_*.csv', "
f"'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV', 'auto', "
f"'{table_format}') WHERE _file='test_partition_tf_3.csv'"
)
assert azure_query(node, query) == "1\n" assert azure_query(node, query) == "1\n"
def test_read_subcolumns(cluster): def test_read_subcolumns(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
storage_account_url = cluster.env_variables["AZURITE_STORAGE_ACCOUNT_URL"]
azure_query( azure_query(
node, node,
"INSERT INTO TABLE FUNCTION azureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', 'test_subcolumns.tsv', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto', 'a Tuple(b Tuple(c UInt32, d UInt32), e UInt32)') select ((1, 2), 3)", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_subcolumns.tsv', "
f"'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto',"
f" 'a Tuple(b Tuple(c UInt32, d UInt32), e UInt32)') select ((1, 2), 3)",
) )
azure_query( azure_query(
node, node,
"INSERT INTO TABLE FUNCTION azureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', 'test_subcolumns.jsonl', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto', 'a Tuple(b Tuple(c UInt32, d UInt32), e UInt32)') select ((1, 2), 3)", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_subcolumns.jsonl', "
f"'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto', "
f"'a Tuple(b Tuple(c UInt32, d UInt32), e UInt32)') select ((1, 2), 3)",
) )
res = node.query( res = node.query(
f"select a.b.d, _path, a.b, _file, a.e from azureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', 'test_subcolumns.tsv', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto', 'a Tuple(b Tuple(c UInt32, d UInt32), e UInt32)')" f"select a.b.d, _path, a.b, _file, a.e from azureBlobStorage('{storage_account_url}', 'cont', 'test_subcolumns.tsv',"
f" 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto',"
f" 'a Tuple(b Tuple(c UInt32, d UInt32), e UInt32)')"
) )
assert res == "2\tcont/test_subcolumns.tsv\t(1,2)\ttest_subcolumns.tsv\t3\n" assert res == "2\tcont/test_subcolumns.tsv\t(1,2)\ttest_subcolumns.tsv\t3\n"
res = node.query( res = node.query(
f"select a.b.d, _path, a.b, _file, a.e from azureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', 'test_subcolumns.jsonl', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto', 'a Tuple(b Tuple(c UInt32, d UInt32), e UInt32)')" f"select a.b.d, _path, a.b, _file, a.e from azureBlobStorage('{storage_account_url}', 'cont', 'test_subcolumns.jsonl',"
f" 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto', "
f"'a Tuple(b Tuple(c UInt32, d UInt32), e UInt32)')"
) )
assert res == "2\tcont/test_subcolumns.jsonl\t(1,2)\ttest_subcolumns.jsonl\t3\n" assert res == "2\tcont/test_subcolumns.jsonl\t(1,2)\ttest_subcolumns.jsonl\t3\n"
res = node.query( res = node.query(
f"select x.b.d, _path, x.b, _file, x.e from azureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', 'test_subcolumns.jsonl', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto', 'x Tuple(b Tuple(c UInt32, d UInt32), e UInt32)')" f"select x.b.d, _path, x.b, _file, x.e from azureBlobStorage('{storage_account_url}', 'cont', 'test_subcolumns.jsonl',"
f" 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto', "
f"'x Tuple(b Tuple(c UInt32, d UInt32), e UInt32)')"
) )
assert res == "0\tcont/test_subcolumns.jsonl\t(0,0)\ttest_subcolumns.jsonl\t0\n" assert res == "0\tcont/test_subcolumns.jsonl\t(0,0)\ttest_subcolumns.jsonl\t0\n"
res = node.query( res = node.query(
f"select x.b.d, _path, x.b, _file, x.e from azureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', 'test_subcolumns.jsonl', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto', 'x Tuple(b Tuple(c UInt32, d UInt32), e UInt32) default ((42, 42), 42)')" f"select x.b.d, _path, x.b, _file, x.e from azureBlobStorage('{storage_account_url}', 'cont', 'test_subcolumns.jsonl',"
f" 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto', "
f"'x Tuple(b Tuple(c UInt32, d UInt32), e UInt32) default ((42, 42), 42)')"
) )
assert res == "42\tcont/test_subcolumns.jsonl\t(42,42)\ttest_subcolumns.jsonl\t42\n" assert res == "42\tcont/test_subcolumns.jsonl\t(42,42)\ttest_subcolumns.jsonl\t42\n"
@ -683,15 +787,18 @@ def test_read_subcolumns(cluster):
def test_read_from_not_existing_container(cluster): def test_read_from_not_existing_container(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
query = f"select * from azureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont_not_exists', 'test_table.csv', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV', 'auto')" query = (
f"select * from azureBlobStorage('{cluster.env_variables['AZURITE_STORAGE_ACCOUNT_URL']}', 'cont_not_exists', 'test_table.csv', "
f"'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV', 'auto')"
)
expected_err_msg = "container does not exist" expected_err_msg = "container does not exist"
assert expected_err_msg in azure_query(node, query, expect_error="true") assert expected_err_msg in azure_query(node, query, expect_error="true")
def test_function_signatures(cluster): def test_function_signatures(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
connection_string = "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite1:10000/devstoreaccount1;" connection_string = cluster.env_variables["AZURITE_CONNECTION_STRING"]
storage_account_url = "http://azurite1:10000/devstoreaccount1" storage_account_url = cluster.env_variables["AZURITE_STORAGE_ACCOUNT_URL"]
account_name = "devstoreaccount1" account_name = "devstoreaccount1"
account_key = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" account_key = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
azure_query( azure_query(
@ -745,7 +852,8 @@ def check_profile_event_for_query(instance, file, profile_event, amount):
query_pattern = f"azureBlobStorage%{file}".replace("'", "\\'") query_pattern = f"azureBlobStorage%{file}".replace("'", "\\'")
res = int( res = int(
instance.query( instance.query(
f"select ProfileEvents['{profile_event}'] from system.query_log where query like '%{query_pattern}%' and query not like '%ProfileEvents%' and type = 'QueryFinish' order by query_start_time_microseconds desc limit 1" f"select ProfileEvents['{profile_event}'] from system.query_log where query like '%{query_pattern}%' and query not like '%ProfileEvents%' "
f"and type = 'QueryFinish' order by query_start_time_microseconds desc limit 1"
) )
) )
@ -804,15 +912,16 @@ def check_cache(instance, expected_files):
def test_schema_inference_cache(cluster): def test_schema_inference_cache(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
connection_string = "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite1:10000/devstoreaccount1;" connection_string = cluster.env_variables["AZURITE_CONNECTION_STRING"]
storage_account_url = "http://azurite1:10000/devstoreaccount1" storage_account_url = cluster.env_variables["AZURITE_STORAGE_ACCOUNT_URL"]
account_name = "devstoreaccount1" account_name = "devstoreaccount1"
account_key = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" account_key = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
node.query("system drop schema cache") node.query("system drop schema cache")
azure_query( azure_query(
node, node,
f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cache0.jsonl', '{account_name}', '{account_key}') select * from numbers(100)", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cache0.jsonl', '{account_name}', '{account_key}') "
f"select * from numbers(100)",
) )
time.sleep(1) time.sleep(1)
@ -826,7 +935,8 @@ def test_schema_inference_cache(cluster):
azure_query( azure_query(
node, node,
f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cache0.jsonl', '{account_name}', '{account_key}') select * from numbers(100) settings azure_truncate_on_insert=1", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cache0.jsonl', '{account_name}', '{account_key}') "
f"select * from numbers(100) settings azure_truncate_on_insert=1",
) )
time.sleep(1) time.sleep(1)
@ -836,7 +946,8 @@ def test_schema_inference_cache(cluster):
azure_query( azure_query(
node, node,
f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cache1.jsonl', '{account_name}', '{account_key}') select * from numbers(100) settings azure_truncate_on_insert=1", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cache1.jsonl', '{account_name}', '{account_key}') "
f"select * from numbers(100) settings azure_truncate_on_insert=1",
) )
time.sleep(1) time.sleep(1)
@ -849,7 +960,8 @@ def test_schema_inference_cache(cluster):
azure_query( azure_query(
node, node,
f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cache2.jsonl', '{account_name}', '{account_key}') select * from numbers(100) settings azure_truncate_on_insert=1", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cache2.jsonl', '{account_name}', '{account_key}') "
f"select * from numbers(100) settings azure_truncate_on_insert=1",
) )
time.sleep(1) time.sleep(1)
@ -895,7 +1007,8 @@ def test_schema_inference_cache(cluster):
azure_query( azure_query(
node, node,
f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cache3.jsonl', '{account_name}', '{account_key}') select * from numbers(100) settings azure_truncate_on_insert=1", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cache3.jsonl', '{account_name}', '{account_key}') "
f"select * from numbers(100) settings azure_truncate_on_insert=1",
) )
time.sleep(1) time.sleep(1)
@ -919,7 +1032,8 @@ def test_schema_inference_cache(cluster):
azure_query( azure_query(
node, node,
f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cache0.csv', '{account_name}', '{account_key}') select * from numbers(100) settings azure_truncate_on_insert=1", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cache0.csv', '{account_name}', '{account_key}') "
f"select * from numbers(100) settings azure_truncate_on_insert=1",
) )
time.sleep(1) time.sleep(1)
@ -943,7 +1057,8 @@ def test_schema_inference_cache(cluster):
azure_query( azure_query(
node, node,
f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cache0.csv', '{account_name}', '{account_key}') select * from numbers(200) settings azure_truncate_on_insert=1", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cache0.csv', '{account_name}', '{account_key}') "
f"select * from numbers(200) settings azure_truncate_on_insert=1",
) )
time.sleep(1) time.sleep(1)
@ -958,7 +1073,8 @@ def test_schema_inference_cache(cluster):
azure_query( azure_query(
node, node,
f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cache1.csv', '{account_name}', '{account_key}') select * from numbers(100) settings azure_truncate_on_insert=1", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cache1.csv', '{account_name}', '{account_key}') "
f"select * from numbers(100) settings azure_truncate_on_insert=1",
) )
time.sleep(1) time.sleep(1)
@ -991,7 +1107,8 @@ def test_schema_inference_cache(cluster):
azure_query( azure_query(
node, node,
f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cache.parquet', '{account_name}', '{account_key}') select * from numbers(100) settings azure_truncate_on_insert=1", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cache.parquet', '{account_name}', '{account_key}') "
f"select * from numbers(100) settings azure_truncate_on_insert=1",
) )
time.sleep(1) time.sleep(1)
@ -1007,23 +1124,29 @@ def test_schema_inference_cache(cluster):
def test_filtering_by_file_or_path(cluster): def test_filtering_by_file_or_path(cluster):
node = cluster.instances["node"] node = cluster.instances["node"]
storage_account_url = cluster.env_variables["AZURITE_STORAGE_ACCOUNT_URL"]
azure_query( azure_query(
node, node,
"INSERT INTO TABLE FUNCTION azureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', 'test_filter1.tsv', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto', 'x UInt64') select 1", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}','cont', 'test_filter1.tsv', 'devstoreaccount1', "
f"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto', 'x UInt64') select 1",
) )
azure_query( azure_query(
node, node,
"INSERT INTO TABLE FUNCTION azureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', 'test_filter2.tsv', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto', 'x UInt64') select 2", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}','cont', 'test_filter2.tsv', 'devstoreaccount1', "
f"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto', 'x UInt64') select 2",
) )
azure_query( azure_query(
node, node,
"INSERT INTO TABLE FUNCTION azureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', 'test_filter3.tsv', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto', 'x UInt64') select 3", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_filter3.tsv', 'devstoreaccount1', "
f"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto', 'x UInt64') select 3",
) )
node.query( node.query(
f"select count() from azureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', 'test_filter*.tsv', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto', 'x UInt64') where _file = 'test_filter1.tsv'" f"select count() from azureBlobStorage('{storage_account_url}', 'cont', 'test_filter*.tsv', 'devstoreaccount1', "
f"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', 'auto', 'x UInt64') "
f"where _file = 'test_filter1.tsv'"
) )
node.query("SYSTEM FLUSH LOGS") node.query("SYSTEM FLUSH LOGS")

View File

@ -49,9 +49,13 @@ def cluster():
cluster.shutdown() cluster.shutdown()
def get_azure_file_content(filename): def get_azure_file_content(filename, port):
container_name = "cont" container_name = "cont"
connection_string = "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;" connection_string = (
f"DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;"
f"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;"
f"BlobEndpoint=http://127.0.0.1:{port}/devstoreaccount1;"
)
blob_service_client = BlobServiceClient.from_connection_string(connection_string) blob_service_client = BlobServiceClient.from_connection_string(connection_string)
container_client = blob_service_client.get_container_client(container_name) container_client = blob_service_client.get_container_client(container_name)
blob_client = container_client.get_blob_client(filename) blob_client = container_client.get_blob_client(filename)
@ -61,31 +65,28 @@ def get_azure_file_content(filename):
def test_select_all(cluster): def test_select_all(cluster):
node = cluster.instances["node_0"] node = cluster.instances["node_0"]
port = cluster.env_variables["AZURITE_PORT"]
storage_account_url = cluster.env_variables["AZURITE_STORAGE_ACCOUNT_URL"]
azure_query( azure_query(
node, node,
"INSERT INTO TABLE FUNCTION azureBlobStorage(" f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cluster_select_all.csv', 'devstoreaccount1',"
"'http://azurite1:10000/devstoreaccount1', 'cont', 'test_cluster_select_all.csv', 'devstoreaccount1', " f"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV', 'auto', 'key UInt64, data String') "
"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV', " f"VALUES (1, 'a'), (2, 'b')",
"'auto', 'key UInt64, data String') VALUES (1, 'a'), (2, 'b')",
) )
print(get_azure_file_content("test_cluster_select_all.csv")) print(get_azure_file_content("test_cluster_select_all.csv", port))
pure_azure = azure_query( pure_azure = azure_query(
node, node,
""" f"SELECT * from azureBlobStorage('{storage_account_url}', 'cont', 'test_cluster_select_all.csv', 'devstoreaccount1',"
SELECT * from azureBlobStorage( f"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV','auto')",
'http://azurite1:10000/devstoreaccount1', 'cont', 'test_cluster_select_all.csv', 'devstoreaccount1',
'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV',
'auto')""",
) )
print(pure_azure) print(pure_azure)
distributed_azure = azure_query( distributed_azure = azure_query(
node, node,
""" f"SELECT * from azureBlobStorageCluster('simple_cluster', '{storage_account_url}', 'cont', 'test_cluster_select_all.csv', 'devstoreaccount1',"
SELECT * from azureBlobStorageCluster( f"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV',"
'simple_cluster', 'http://azurite1:10000/devstoreaccount1', 'cont', 'test_cluster_select_all.csv', 'devstoreaccount1', f"'auto')"
'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV', "",
'auto')""",
) )
print(distributed_azure) print(distributed_azure)
assert TSV(pure_azure) == TSV(distributed_azure) assert TSV(pure_azure) == TSV(distributed_azure)
@ -93,31 +94,28 @@ def test_select_all(cluster):
def test_count(cluster): def test_count(cluster):
node = cluster.instances["node_0"] node = cluster.instances["node_0"]
port = cluster.env_variables["AZURITE_PORT"]
storage_account_url = cluster.env_variables["AZURITE_STORAGE_ACCOUNT_URL"]
azure_query( azure_query(
node, node,
"INSERT INTO TABLE FUNCTION azureBlobStorage(" f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cluster_count.csv', 'devstoreaccount1', "
"'http://azurite1:10000/devstoreaccount1', 'cont', 'test_cluster_count.csv', 'devstoreaccount1', " f"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV', "
"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV', " f"'auto', 'key UInt64') VALUES (1), (2)",
"'auto', 'key UInt64') VALUES (1), (2)",
) )
print(get_azure_file_content("test_cluster_count.csv")) print(get_azure_file_content("test_cluster_count.csv", port))
pure_azure = azure_query( pure_azure = azure_query(
node, node,
""" f"SELECT count(*) from azureBlobStorage('{storage_account_url}', 'cont', 'test_cluster_count.csv', 'devstoreaccount1',"
SELECT count(*) from azureBlobStorage( f"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV',"
'http://azurite1:10000/devstoreaccount1', 'cont', 'test_cluster_count.csv', 'devstoreaccount1', f"'auto', 'key UInt64')",
'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV',
'auto', 'key UInt64')""",
) )
print(pure_azure) print(pure_azure)
distributed_azure = azure_query( distributed_azure = azure_query(
node, node,
""" f"SELECT count(*) from azureBlobStorageCluster('simple_cluster', '{storage_account_url}', 'cont', 'test_cluster_count.csv', "
SELECT count(*) from azureBlobStorageCluster( f"'devstoreaccount1','Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV',"
'simple_cluster', 'http://azurite1:10000/devstoreaccount1', 'cont', 'test_cluster_count.csv', 'devstoreaccount1', f"'auto', 'key UInt64')",
'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV',
'auto', 'key UInt64')""",
) )
print(distributed_azure) print(distributed_azure)
assert TSV(pure_azure) == TSV(distributed_azure) assert TSV(pure_azure) == TSV(distributed_azure)
@ -125,26 +123,25 @@ def test_count(cluster):
def test_union_all(cluster): def test_union_all(cluster):
node = cluster.instances["node_0"] node = cluster.instances["node_0"]
storage_account_url = cluster.env_variables["AZURITE_STORAGE_ACCOUNT_URL"]
azure_query( azure_query(
node, node,
"INSERT INTO TABLE FUNCTION azureBlobStorage(" f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_parquet_union_all', 'devstoreaccount1', "
"'http://azurite1:10000/devstoreaccount1', 'cont', 'test_parquet_union_all', 'devstoreaccount1', " f"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'Parquet', "
"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'Parquet', " f"'auto', 'a Int32, b String') VALUES (1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')",
"'auto', 'a Int32, b String') VALUES (1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')",
) )
pure_azure = azure_query( pure_azure = azure_query(
node, node,
""" f"""
SELECT * FROM SELECT * FROM
( (
SELECT * from azureBlobStorage( SELECT * from azureBlobStorage('{storage_account_url}', 'cont', 'test_parquet_union_all', 'devstoreaccount1',
'http://azurite1:10000/devstoreaccount1', 'cont', 'test_parquet_union_all', 'devstoreaccount1',
'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'Parquet', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'Parquet',
'auto', 'a Int32, b String') 'auto', 'a Int32, b String')
UNION ALL UNION ALL
SELECT * from azureBlobStorage( SELECT * from azureBlobStorage(
'http://azurite1:10000/devstoreaccount1', 'cont', 'test_parquet_union_all', 'devstoreaccount1', '{storage_account_url}', 'cont', 'test_parquet_union_all', 'devstoreaccount1',
'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'Parquet', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'Parquet',
'auto', 'a Int32, b String') 'auto', 'a Int32, b String')
) )
@ -153,18 +150,18 @@ def test_union_all(cluster):
) )
azure_distributed = azure_query( azure_distributed = azure_query(
node, node,
""" f"""
SELECT * FROM SELECT * FROM
( (
SELECT * from azureBlobStorageCluster( SELECT * from azureBlobStorageCluster(
'simple_cluster', 'simple_cluster',
'http://azurite1:10000/devstoreaccount1', 'cont', 'test_parquet_union_all', 'devstoreaccount1', '{storage_account_url}', 'cont', 'test_parquet_union_all', 'devstoreaccount1',
'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'Parquet', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'Parquet',
'auto', 'a Int32, b String') 'auto', 'a Int32, b String')
UNION ALL UNION ALL
SELECT * from azureBlobStorageCluster( SELECT * from azureBlobStorageCluster(
'simple_cluster', 'simple_cluster',
'http://azurite1:10000/devstoreaccount1', 'cont', 'test_parquet_union_all', 'devstoreaccount1', '{storage_account_url}', 'cont', 'test_parquet_union_all', 'devstoreaccount1',
'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'Parquet', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'Parquet',
'auto', 'a Int32, b String') 'auto', 'a Int32, b String')
) )
@ -177,22 +174,18 @@ def test_union_all(cluster):
def test_skip_unavailable_shards(cluster): def test_skip_unavailable_shards(cluster):
node = cluster.instances["node_0"] node = cluster.instances["node_0"]
storage_account_url = cluster.env_variables["AZURITE_STORAGE_ACCOUNT_URL"]
azure_query( azure_query(
node, node,
"INSERT INTO TABLE FUNCTION azureBlobStorage(" f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_skip_unavailable.csv', 'devstoreaccount1', "
"'http://azurite1:10000/devstoreaccount1', 'cont', 'test_skip_unavailable.csv', 'devstoreaccount1', " f"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', "
"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', " f"'auto', 'a UInt64') VALUES (1), (2)",
"'auto', 'a UInt64') VALUES (1), (2)",
) )
result = azure_query( result = azure_query(
node, node,
""" f"SELECT count(*) from azureBlobStorageCluster('cluster_non_existent_port','{storage_account_url}', 'cont', 'test_skip_unavailable.csv', "
SELECT count(*) from azureBlobStorageCluster( f"'devstoreaccount1','Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==') "
'cluster_non_existent_port', f"SETTINGS skip_unavailable_shards = 1",
'http://azurite1:10000/devstoreaccount1', 'cont', 'test_skip_unavailable.csv', 'devstoreaccount1',
'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==')
SETTINGS skip_unavailable_shards = 1
""",
) )
assert result == "2\n" assert result == "2\n"
@ -201,21 +194,17 @@ def test_skip_unavailable_shards(cluster):
def test_unset_skip_unavailable_shards(cluster): def test_unset_skip_unavailable_shards(cluster):
# Although skip_unavailable_shards is not set, cluster table functions should always skip unavailable shards. # Although skip_unavailable_shards is not set, cluster table functions should always skip unavailable shards.
node = cluster.instances["node_0"] node = cluster.instances["node_0"]
storage_account_url = cluster.env_variables["AZURITE_STORAGE_ACCOUNT_URL"]
azure_query( azure_query(
node, node,
"INSERT INTO TABLE FUNCTION azureBlobStorage(" f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_unset_skip_unavailable.csv', 'devstoreaccount1', "
"'http://azurite1:10000/devstoreaccount1', 'cont', 'test_unset_skip_unavailable.csv', 'devstoreaccount1', " f"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', "
"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', " f"'auto', 'a UInt64') VALUES (1), (2)",
"'auto', 'a UInt64') VALUES (1), (2)",
) )
result = azure_query( result = azure_query(
node, node,
""" f"SELECT count(*) from azureBlobStorageCluster('cluster_non_existent_port','{storage_account_url}', 'cont', 'test_skip_unavailable.csv', "
SELECT count(*) from azureBlobStorageCluster( f"'devstoreaccount1','Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==')",
'cluster_non_existent_port',
'http://azurite1:10000/devstoreaccount1', 'cont', 'test_skip_unavailable.csv', 'devstoreaccount1',
'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==')
""",
) )
assert result == "2\n" assert result == "2\n"
@ -223,58 +212,53 @@ def test_unset_skip_unavailable_shards(cluster):
def test_cluster_with_named_collection(cluster): def test_cluster_with_named_collection(cluster):
node = cluster.instances["node_0"] node = cluster.instances["node_0"]
storage_account_url = cluster.env_variables["AZURITE_STORAGE_ACCOUNT_URL"]
azure_query( azure_query(
node, node,
"INSERT INTO TABLE FUNCTION azureBlobStorage(" f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', 'test_cluster_with_named_collection.csv', 'devstoreaccount1', "
"'http://azurite1:10000/devstoreaccount1', 'cont', 'test_cluster_with_named_collection.csv', 'devstoreaccount1', " f"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', "
"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'auto', " f"'auto', 'a UInt64') VALUES (1), (2)",
"'auto', 'a UInt64') VALUES (1), (2)",
) )
pure_azure = azure_query( pure_azure = azure_query(
node, node,
""" f"SELECT * from azureBlobStorage('{storage_account_url}', 'cont', 'test_cluster_with_named_collection.csv', 'devstoreaccount1',"
SELECT * from azureBlobStorage( f"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==')",
'http://azurite1:10000/devstoreaccount1', 'cont', 'test_cluster_with_named_collection.csv', 'devstoreaccount1',
'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==')
""",
) )
azure_cluster = azure_query( azure_cluster = azure_query(
node, node,
""" f"SELECT * from azureBlobStorageCluster('simple_cluster', azure_conf2, storage_account_url = '{storage_account_url}', container='cont', "
SELECT * from azureBlobStorageCluster( f"blob_path='test_cluster_with_named_collection.csv')",
'simple_cluster', azure_conf2, container='cont', blob_path='test_cluster_with_named_collection.csv')
""",
) )
assert TSV(pure_azure) == TSV(azure_cluster) assert TSV(pure_azure) == TSV(azure_cluster)
def test_partition_parallel_readig_withcluster(cluster): def test_partition_parallel_reading_with_cluster(cluster):
node = cluster.instances["node_0"] node = cluster.instances["node_0"]
table_format = "column1 UInt32, column2 UInt32, column3 UInt32" table_format = "column1 UInt32, column2 UInt32, column3 UInt32"
partition_by = "column3" partition_by = "column3"
values = "(1, 2, 3), (3, 2, 1), (78, 43, 45)" values = "(1, 2, 3), (3, 2, 1), (78, 43, 45)"
filename = "test_tf_{_partition_id}.csv" filename = "test_tf_{_partition_id}.csv"
port = cluster.env_variables["AZURITE_PORT"]
storage_account_url = cluster.env_variables["AZURITE_STORAGE_ACCOUNT_URL"]
azure_query( azure_query(
node, node,
f"INSERT INTO TABLE FUNCTION azureBlobStorage('http://azurite1:10000/devstoreaccount1', 'cont', '{filename}', 'devstoreaccount1', 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV', 'auto', '{table_format}') PARTITION BY {partition_by} VALUES {values}", f"INSERT INTO TABLE FUNCTION azureBlobStorage('{storage_account_url}', 'cont', '{filename}', 'devstoreaccount1', "
f"'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==', 'CSV', 'auto', '{table_format}') "
f"PARTITION BY {partition_by} VALUES {values}",
) )
assert "1,2,3\n" == get_azure_file_content("test_tf_3.csv") assert "1,2,3\n" == get_azure_file_content("test_tf_3.csv", port)
assert "3,2,1\n" == get_azure_file_content("test_tf_1.csv") assert "3,2,1\n" == get_azure_file_content("test_tf_1.csv", port)
assert "78,43,45\n" == get_azure_file_content("test_tf_45.csv") assert "78,43,45\n" == get_azure_file_content("test_tf_45.csv", port)
azure_cluster = azure_query( azure_cluster = azure_query(
node, node,
""" f"SELECT count(*) from azureBlobStorageCluster('simple_cluster', azure_conf2, storage_account_url = '{storage_account_url}', "
SELECT count(*) from azureBlobStorageCluster( f"container='cont', blob_path='test_tf_*.csv', format='CSV', compression='auto', structure='column1 UInt32, column2 UInt32, column3 UInt32')",
'simple_cluster',
azure_conf2, container='cont', blob_path='test_tf_*.csv', format='CSV', compression='auto', structure='column1 UInt32, column2 UInt32, column3 UInt32')
""",
) )
assert azure_cluster == "3\n" assert azure_cluster == "3\n"

View File

@ -0,0 +1,69 @@
<test>
<create_query>
CREATE TABLE
window_test(id Int64, value Int64, partition Int64, msg String)
Engine=MergeTree
ORDER BY id
</create_query>
<fill_query>
INSERT INTO window_test
SELECT number, rand(1) % 500, number % 3000, randomPrintableASCII(2) FROM numbers(5000000)
</fill_query>
<query>
SELECT id,
AVG(value) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS frame1,
MAX(value) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS frame2,
sipHash64(frame1),
sipHash64(frame2)
FROM window_test
</query>
<query>
SELECT id AS key,
sipHash64(sum(frame)) AS value
FROM (
SELECT id,
AVG(value) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS frame
FROM window_test)
GROUP BY key
ORDER BY key, value
</query>
<query>
SELECT id % 100000 AS key,
sipHash64(sum(frame)) AS value
FROM (
SELECT id,
AVG(value) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS frame
FROM window_test)
GROUP BY key
ORDER BY key, value
</query>
<query>
WITH 'xxxxyyyyxxxxyyyyxxxxyyyyxxxxyyyy' AS cipherKey
SELECT id,
AVG(value) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS frame,
toString(frame) AS str,
encrypt('aes-256-ofb', str, cipherKey) AS enc,
decrypt('aes-256-ofb', str, cipherKey) AS dec
FROM window_test
</query>
<query>
SELECT id,
AVG(value) OVER (PARTITION by partition ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS frame
FROM window_test
ORDER BY id
</query>
<query>
SELECT DISTINCT AVG(value) OVER (PARTITION by partition ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS frame
FROM window_test
ORDER BY frame
</query>
<drop_query>DROP TABLE IF EXISTS window_test</drop_query>
</test>

View File

@ -1,7 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Tags: long, no-parallel, no-s3-storage # Tags: long, no-parallel
# FIXME: s3 storage should work OK, it
# reproduces bug which exists not only in S3 version.
CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=../shell_config.sh # shellcheck source=../shell_config.sh

View File

@ -1,18 +1,18 @@
-- { echo } -- { echo }
select row_number() over (order by dummy) from (select * from remote('127.0.0.{1,2}', system, one)); select row_number() over (order by dummy) as x from (select * from remote('127.0.0.{1,2}', system, one)) order by x;
1 1
2 2
select row_number() over (order by dummy) from remote('127.0.0.{1,2}', system, one); select row_number() over (order by dummy) as x from remote('127.0.0.{1,2}', system, one) order by x;
1 1
2 2
select max(identity(dummy + 1)) over () from remote('127.0.0.{1,2}', system, one); select max(identity(dummy + 1)) over () as x from remote('127.0.0.{1,2}', system, one) order by x;
1 1
1 1
drop table if exists t_01568; drop table if exists t_01568;
create table t_01568 engine Memory as create table t_01568 engine Memory as
select intDiv(number, 3) p, modulo(number, 3) o, number select intDiv(number, 3) p, modulo(number, 3) o, number
from numbers(9); from numbers(9);
select sum(number) over w, max(number) over w from t_01568 window w as (partition by p); select sum(number) over w as x, max(number) over w as y from t_01568 window w as (partition by p) order by x, y;
3 2 3 2
3 2 3 2
3 2 3 2
@ -22,7 +22,7 @@ select sum(number) over w, max(number) over w from t_01568 window w as (partitio
21 8 21 8
21 8 21 8
21 8 21 8
select sum(number) over w, max(number) over w from remote('127.0.0.{1,2}', '', t_01568) window w as (partition by p); select sum(number) over w as x, max(number) over w as y from remote('127.0.0.{1,2}', '', t_01568) window w as (partition by p) order by x, y;
6 2 6 2
6 2 6 2
6 2 6 2
@ -41,23 +41,23 @@ select sum(number) over w, max(number) over w from remote('127.0.0.{1,2}', '', t
42 8 42 8
42 8 42 8
42 8 42 8
select distinct sum(number) over w, max(number) over w from remote('127.0.0.{1,2}', '', t_01568) window w as (partition by p); select distinct sum(number) over w as x, max(number) over w as y from remote('127.0.0.{1,2}', '', t_01568) window w as (partition by p) order by x, y;
6 2 6 2
24 5 24 5
42 8 42 8
-- window functions + aggregation w/shards -- window functions + aggregation w/shards
select groupArray(groupArray(number)) over (rows unbounded preceding) from remote('127.0.0.{1,2}', '', t_01568) group by mod(number, 3); select groupArray(groupArray(number)) over (rows unbounded preceding) as x from remote('127.0.0.{1,2}', '', t_01568) group by mod(number, 3) order by x;
[[0,3,6,0,3,6]] [[0,3,6,0,3,6]]
[[0,3,6,0,3,6],[1,4,7,1,4,7]] [[0,3,6,0,3,6],[1,4,7,1,4,7]]
[[0,3,6,0,3,6],[1,4,7,1,4,7],[2,5,8,2,5,8]] [[0,3,6,0,3,6],[1,4,7,1,4,7],[2,5,8,2,5,8]]
select groupArray(groupArray(number)) over (rows unbounded preceding) from remote('127.0.0.{1,2}', '', t_01568) group by mod(number, 3) settings distributed_group_by_no_merge=1; select groupArray(groupArray(number)) over (rows unbounded preceding) as x from remote('127.0.0.{1,2}', '', t_01568) group by mod(number, 3) order by x settings distributed_group_by_no_merge=1;
[[0,3,6]] [[0,3,6]]
[[0,3,6],[1,4,7]] [[0,3,6],[1,4,7]]
[[0,3,6],[1,4,7],[2,5,8]] [[0,3,6],[1,4,7],[2,5,8]]
[[0,3,6]] [[0,3,6]]
[[0,3,6],[1,4,7]] [[0,3,6],[1,4,7]]
[[0,3,6],[1,4,7],[2,5,8]] [[0,3,6],[1,4,7],[2,5,8]]
select groupArray(groupArray(number)) over (rows unbounded preceding) from remote('127.0.0.{1,2}', '', t_01568) group by mod(number, 3) settings distributed_group_by_no_merge=2; -- { serverError 48 } select groupArray(groupArray(number)) over (rows unbounded preceding) as x from remote('127.0.0.{1,2}', '', t_01568) group by mod(number, 3) order by x settings distributed_group_by_no_merge=2; -- { serverError 48 }
-- proper ORDER BY w/window functions -- proper ORDER BY w/window functions
select p, o, count() over (partition by p) select p, o, count() over (partition by p)
from remote('127.0.0.{1,2}', '', t_01568) from remote('127.0.0.{1,2}', '', t_01568)

View File

@ -1,11 +1,11 @@
-- Tags: distributed -- Tags: distributed
-- { echo } -- { echo }
select row_number() over (order by dummy) from (select * from remote('127.0.0.{1,2}', system, one)); select row_number() over (order by dummy) as x from (select * from remote('127.0.0.{1,2}', system, one)) order by x;
select row_number() over (order by dummy) from remote('127.0.0.{1,2}', system, one); select row_number() over (order by dummy) as x from remote('127.0.0.{1,2}', system, one) order by x;
select max(identity(dummy + 1)) over () from remote('127.0.0.{1,2}', system, one); select max(identity(dummy + 1)) over () as x from remote('127.0.0.{1,2}', system, one) order by x;
drop table if exists t_01568; drop table if exists t_01568;
@ -13,16 +13,16 @@ create table t_01568 engine Memory as
select intDiv(number, 3) p, modulo(number, 3) o, number select intDiv(number, 3) p, modulo(number, 3) o, number
from numbers(9); from numbers(9);
select sum(number) over w, max(number) over w from t_01568 window w as (partition by p); select sum(number) over w as x, max(number) over w as y from t_01568 window w as (partition by p) order by x, y;
select sum(number) over w, max(number) over w from remote('127.0.0.{1,2}', '', t_01568) window w as (partition by p); select sum(number) over w as x, max(number) over w as y from remote('127.0.0.{1,2}', '', t_01568) window w as (partition by p) order by x, y;
select distinct sum(number) over w, max(number) over w from remote('127.0.0.{1,2}', '', t_01568) window w as (partition by p); select distinct sum(number) over w as x, max(number) over w as y from remote('127.0.0.{1,2}', '', t_01568) window w as (partition by p) order by x, y;
-- window functions + aggregation w/shards -- window functions + aggregation w/shards
select groupArray(groupArray(number)) over (rows unbounded preceding) from remote('127.0.0.{1,2}', '', t_01568) group by mod(number, 3); select groupArray(groupArray(number)) over (rows unbounded preceding) as x from remote('127.0.0.{1,2}', '', t_01568) group by mod(number, 3) order by x;
select groupArray(groupArray(number)) over (rows unbounded preceding) from remote('127.0.0.{1,2}', '', t_01568) group by mod(number, 3) settings distributed_group_by_no_merge=1; select groupArray(groupArray(number)) over (rows unbounded preceding) as x from remote('127.0.0.{1,2}', '', t_01568) group by mod(number, 3) order by x settings distributed_group_by_no_merge=1;
select groupArray(groupArray(number)) over (rows unbounded preceding) from remote('127.0.0.{1,2}', '', t_01568) group by mod(number, 3) settings distributed_group_by_no_merge=2; -- { serverError 48 } select groupArray(groupArray(number)) over (rows unbounded preceding) as x from remote('127.0.0.{1,2}', '', t_01568) group by mod(number, 3) order by x settings distributed_group_by_no_merge=2; -- { serverError 48 }
-- proper ORDER BY w/window functions -- proper ORDER BY w/window functions
select p, o, count() over (partition by p) select p, o, count() over (partition by p)

View File

@ -19,7 +19,8 @@ system stop merges order_by_const;
INSERT INTO order_by_const(a, b, c, d) VALUES (1, 1, 101, 1), (1, 2, 102, 1), (1, 3, 103, 1), (1, 4, 104, 1); INSERT INTO order_by_const(a, b, c, d) VALUES (1, 1, 101, 1), (1, 2, 102, 1), (1, 3, 103, 1), (1, 4, 104, 1);
INSERT INTO order_by_const(a, b, c, d) VALUES (1, 5, 104, 1), (1, 6, 105, 1), (2, 1, 106, 2), (2, 1, 107, 2); INSERT INTO order_by_const(a, b, c, d) VALUES (1, 5, 104, 1), (1, 6, 105, 1), (2, 1, 106, 2), (2, 1, 107, 2);
INSERT INTO order_by_const(a, b, c, d) VALUES (2, 2, 107, 2), (2, 3, 108, 2), (2, 4, 109, 2); INSERT INTO order_by_const(a, b, c, d) VALUES (2, 2, 107, 2), (2, 3, 108, 2), (2, 4, 109, 2);
SELECT row_number() OVER (order by 1, a) FROM order_by_const; -- output 1 sorted stream
SELECT row_number() OVER (order by 1, a) FROM order_by_const SETTINGS query_plan_enable_multithreading_after_window_functions=0;
1 1
2 2
3 3

View File

@ -20,7 +20,9 @@ system stop merges order_by_const;
INSERT INTO order_by_const(a, b, c, d) VALUES (1, 1, 101, 1), (1, 2, 102, 1), (1, 3, 103, 1), (1, 4, 104, 1); INSERT INTO order_by_const(a, b, c, d) VALUES (1, 1, 101, 1), (1, 2, 102, 1), (1, 3, 103, 1), (1, 4, 104, 1);
INSERT INTO order_by_const(a, b, c, d) VALUES (1, 5, 104, 1), (1, 6, 105, 1), (2, 1, 106, 2), (2, 1, 107, 2); INSERT INTO order_by_const(a, b, c, d) VALUES (1, 5, 104, 1), (1, 6, 105, 1), (2, 1, 106, 2), (2, 1, 107, 2);
INSERT INTO order_by_const(a, b, c, d) VALUES (2, 2, 107, 2), (2, 3, 108, 2), (2, 4, 109, 2); INSERT INTO order_by_const(a, b, c, d) VALUES (2, 2, 107, 2), (2, 3, 108, 2), (2, 4, 109, 2);
SELECT row_number() OVER (order by 1, a) FROM order_by_const;
-- output 1 sorted stream
SELECT row_number() OVER (order by 1, a) FROM order_by_const SETTINGS query_plan_enable_multithreading_after_window_functions=0;
drop table order_by_const; drop table order_by_const;

View File

@ -30,7 +30,11 @@ EOF
${CLICKHOUSE_CLIENT} -q "SYSTEM STOP MERGES lazy_mark_test" ${CLICKHOUSE_CLIENT} -q "SYSTEM STOP MERGES lazy_mark_test"
${CLICKHOUSE_CLIENT} -q "INSERT INTO lazy_mark_test select number, number % 3, number % 5, number % 10, number % 13, number % 15, number % 17, number % 18, number % 22, number % 25 from numbers(1000000)" ${CLICKHOUSE_CLIENT} -q "INSERT INTO lazy_mark_test select number, number % 3, number % 5, number % 10, number % 13, number % 15, number % 17, number % 18, number % 22, number % 25 from numbers(1000000)"
${CLICKHOUSE_CLIENT} -q "SYSTEM DROP MARK CACHE" ${CLICKHOUSE_CLIENT} -q "SYSTEM DROP MARK CACHE"
${CLICKHOUSE_CLIENT} --log_queries=1 --query_id "${QUERY_ID}" -q "SELECT * FROM lazy_mark_test WHERE n3==11 SETTINGS load_marks_asynchronously=0" # max_threads=1 is needed because otherwise OpenedFileCache makes ProfileEvents['FileOpen'] nondeterministic
# (usually all threads access the file at overlapping times, and the file is opened just once;
# but sometimes a thread is much slower than others and ends opening the same file a second time)
${CLICKHOUSE_CLIENT} --log_queries=1 --query_id "${QUERY_ID}" -q "SELECT * FROM lazy_mark_test WHERE n3==11 SETTINGS load_marks_asynchronously=0, max_threads=1"
${CLICKHOUSE_CLIENT} -q "SYSTEM FLUSH LOGS" ${CLICKHOUSE_CLIENT} -q "SYSTEM FLUSH LOGS"
# Expect 2 open files: n3 marks and n3 data.
${CLICKHOUSE_CLIENT} -q "select ProfileEvents['FileOpen'] from system.query_log where query_id = '${QUERY_ID}' and type = 'QueryFinish' and current_database = currentDatabase()" ${CLICKHOUSE_CLIENT} -q "select ProfileEvents['FileOpen'] from system.query_log where query_id = '${QUERY_ID}' and type = 'QueryFinish' and current_database = currentDatabase()"

View File

@ -8,6 +8,8 @@ CREATE TABLE test_zk_connection_table (
ENGINE ReplicatedMergeTree('zookeeper2:/clickhouse/{database}/02731_zk_connection/{shard}', '{replica}') ENGINE ReplicatedMergeTree('zookeeper2:/clickhouse/{database}/02731_zk_connection/{shard}', '{replica}')
ORDER BY tuple(); ORDER BY tuple();
SET session_timezone = 'UTC';
select name, host, port, index, is_expired, keeper_api_version, (connected_time between yesterday() and now()), select name, host, port, index, is_expired, keeper_api_version, (connected_time between yesterday() and now()),
(abs(session_uptime_elapsed_seconds - zookeeperSessionUptime()) < 10), enabled_feature_flags (abs(session_uptime_elapsed_seconds - zookeeperSessionUptime()) < 10), enabled_feature_flags
from system.zookeeper_connection where name='default'; from system.zookeeper_connection where name='default';

View File

@ -0,0 +1,6 @@
0 334
1 333
2 333
0 334
1 333
2 333

View File

@ -0,0 +1,19 @@
DROP TABLE IF EXISTS 02898_parallel_replicas_final;
CREATE TABLE 02898_parallel_replicas_final (x String, y Int32) ENGINE = ReplacingMergeTree ORDER BY cityHash64(x);
INSERT INTO 02898_parallel_replicas_final SELECT toString(number), number % 3 FROM numbers(1000);
SELECT y, count()
FROM cluster(test_cluster_one_shard_three_replicas_localhost, currentDatabase(), 02898_parallel_replicas_final) FINAL
GROUP BY y
ORDER BY y
SETTINGS max_parallel_replicas=3, parallel_replicas_custom_key='cityHash64(y)', parallel_replicas_custom_key_filter_type='default';
SELECT y, count()
FROM cluster(test_cluster_one_shard_three_replicas_localhost, currentDatabase(), 02898_parallel_replicas_final) FINAL
GROUP BY y
ORDER BY y
SETTINGS max_parallel_replicas=3, parallel_replicas_custom_key='cityHash64(y)', parallel_replicas_custom_key_filter_type='range';
DROP TABLE 02898_parallel_replicas_final;

View File

@ -0,0 +1,10 @@
0
1
2 bobr
0
1
1
1
1
1
2 bobr

View File

@ -0,0 +1,34 @@
#!/usr/bin/env bash
CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=../shell_config.sh
. "$CURDIR"/../shell_config.sh
$CLICKHOUSE_CLIENT -q "drop table if exists 02900_buffer"
$CLICKHOUSE_CLIENT -q "drop table if exists 02900_destination"
$CLICKHOUSE_CLIENT -q "create table 02900_destination (k Int8, v String) engine Memory"
$CLICKHOUSE_CLIENT -q "create table 02900_buffer (k Int8) engine Buffer(currentDatabase(), '02900_destination', 1, 1000, 1000, 10000, 10000, 1000000, 1000000)"
$CLICKHOUSE_CLIENT -q "insert into 02900_buffer (k) select 0"
# Start a long-running INSERT that uses the old schema.
$CLICKHOUSE_CLIENT -q "insert into 02900_buffer (k) select sleepEachRow(1)+1 from numbers(5) settings max_block_size=1, max_insert_block_size=1, min_insert_block_size_rows=0, min_insert_block_size_bytes=0" &
sleep 1
$CLICKHOUSE_CLIENT -q "alter table 02900_buffer add column v String"
$CLICKHOUSE_CLIENT -q "insert into 02900_buffer (k, v) select 2, 'bobr'"
wait
# The data produced by the long-running INSERT after the ALTER is not visible until flushed.
$CLICKHOUSE_CLIENT -q "select k, any(v) from 02900_buffer group by k order by k"
$CLICKHOUSE_CLIENT -q "optimize table 02900_buffer"
$CLICKHOUSE_CLIENT -q "select * from 02900_buffer order by k"
$CLICKHOUSE_CLIENT -q "drop table 02900_buffer"
$CLICKHOUSE_CLIENT -q "drop table 02900_destination"

View File

@ -0,0 +1,23 @@
ignore
2036-02-07 06:28:16
2027-10-18 11:03:27
2036-02-07 06:28:16
2027-10-17 11:03:28
2013-04-29 17:31:44
2079-06-07
1970-01-01
2120-07-26
2079-06-07
2120-07-26
No output on `throw`
saturate
1970-01-01 00:00:00
2106-02-07 06:28:15
1970-01-01 00:00:00
2106-02-07 00:00:00
2106-02-07 00:00:00
1970-01-01
2149-06-06
2149-06-06
1970-01-01
2149-06-06

View File

@ -0,0 +1,50 @@
SET session_timezone = 'UTC';
SELECT 'ignore';
SET date_time_overflow_behavior = 'ignore';
SELECT toDateTime(toDateTime64('1900-01-01 00:00:00.123', 3));
SELECT toDateTime(toDateTime64('2299-12-31 23:59:59.999', 3));
SELECT toDateTime(toDate32('1900-01-01'));
SELECT toDateTime(toDate32('2299-12-31'));
SELECT toDateTime(toDate('2149-06-06'));
SELECT toDate(toDateTime64('1900-01-01 00:00:00.123', 3));
SELECT toDate(toDateTime64('2149-06-07 00:00:00.123', 3));
SELECT toDate(toDateTime64('2299-12-31 23:59:59.999', 3));
SELECT toDate(toDate32('1900-01-01'));
SELECT toDate(toDate32('2299-12-31'));
SELECT 'No output on `throw`';
SET date_time_overflow_behavior = 'throw';
SELECT toDateTime(toDateTime64('1900-01-01 00:00:00.123', 3)); -- { serverError VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE }
SELECT toDateTime(toDateTime64('2299-12-31 23:59:59.999', 3)); -- { serverError VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE }
SELECT toDateTime(toDate32('1900-01-01')); -- { serverError VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE }
SELECT toDateTime(toDate32('2299-12-31')); -- { serverError VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE }
SELECT toDateTime(toDate('2149-06-06')); -- { serverError VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE }
SELECT toDate(toDateTime64('1900-01-01 00:00:00.123', 3)); -- { serverError VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE }
SELECT toDate(toDateTime64('2299-12-31 23:59:59.999', 3)); -- { serverError VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE }
SELECT toDate(toDate32('1900-01-01')); -- { serverError VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE }
SELECT toDate(toDate32('2299-12-31')); -- { serverError VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE }
SELECT 'saturate';
SET date_time_overflow_behavior = 'saturate';
SELECT toDateTime(toDateTime64('1900-01-01 00:00:00.123', 3));
SELECT toDateTime(toDateTime64('2299-12-31 23:59:59.999', 3));
SELECT toDateTime(toDate32('1900-01-01'));
SELECT toDateTime(toDate32('2299-12-31'));
SELECT toDateTime(toDate('2149-06-06'));
SELECT toDate(toDateTime64('1900-01-01 00:00:00.123', 3));
SELECT toDate(toDateTime64('2149-06-07 00:00:00.123', 3));
SELECT toDate(toDateTime64('2299-12-31 23:59:59.999', 3));
SELECT toDate(toDate32('1900-01-01'));
SELECT toDate(toDate32('2299-12-31'));

View File

@ -0,0 +1,4 @@
0
0
\N
\N

View File

@ -0,0 +1,10 @@
set precise_float_parsing = 1;
select cast('2023-01-01' as Float64); -- { serverError 6 }
select cast('2023-01-01' as Float32); -- { serverError 6 }
select toFloat32('2023-01-01'); -- { serverError 6 }
select toFloat64('2023-01-01'); -- { serverError 6 }
select toFloat32OrZero('2023-01-01');
select toFloat64OrZero('2023-01-01');
select toFloat32OrNull('2023-01-01');
select toFloat64OrNull('2023-01-01');

View File

@ -0,0 +1,3 @@
1
1
1

View File

@ -0,0 +1,4 @@
SELECT 1 WINDOW x AS (PARTITION BY x); -- { serverError UNKNOWN_IDENTIFIER }
SELECT 1 WINDOW x AS (PARTITION BY dummy);
SELECT 1 WINDOW dummy AS (PARTITION BY dummy);
SELECT count() OVER dummy WINDOW dummy AS (PARTITION BY dummy);

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,7 @@
-- Comparing the same types is ok:
SELECT INTERVAL 1 SECOND = INTERVAL 1 SECOND;
-- It is reasonable to not give an answer for this:
SELECT INTERVAL 30 DAY < INTERVAL 1 MONTH; -- { serverError 386 }
-- This we could change in the future:
SELECT INTERVAL 1 SECOND = INTERVAL 1 YEAR; -- { serverError 386 }
SELECT INTERVAL 1 SECOND <= INTERVAL 1 YEAR; -- { serverError 386 }