Merge branch 'ClickHouse:master' into zvonand-datetime-ranges

This commit is contained in:
Andrey Zvonov 2023-10-26 14:14:32 +02:00 committed by GitHub
commit 71287788d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1173 additions and 135 deletions

View File

@ -131,3 +131,29 @@ void sort(RandomIt first, RandomIt last)
using comparator = std::less<value_type>;
::sort(first, last, comparator());
}
/** Try to fast sort elements for common sorting patterns:
* 1. If elements are already sorted.
* 2. If elements are already almost sorted.
* 3. If elements are already sorted in reverse order.
*
* Returns true if fast sort was performed or elements were already sorted, false otherwise.
*/
template <typename RandomIt, typename Compare>
bool trySort(RandomIt first, RandomIt last, Compare compare)
{
#ifndef NDEBUG
::shuffle(first, last);
#endif
ComparatorWrapper<Compare> compare_wrapper = compare;
return ::pdqsort_try_sort(first, last, compare_wrapper);
}
template <typename RandomIt>
bool trySort(RandomIt first, RandomIt last)
{
using value_type = typename std::iterator_traits<RandomIt>::value_type;
using comparator = std::less<value_type>;
return ::trySort(first, last, comparator());
}

View File

@ -54,8 +54,10 @@ namespace pdqsort_detail {
block_size = 64,
// Cacheline size, assumes power of two.
cacheline_size = 64
cacheline_size = 64,
/// Try sort allowed iterations
try_sort_iterations = 3,
};
#if __cplusplus >= 201103L
@ -501,6 +503,167 @@ namespace pdqsort_detail {
leftmost = false;
}
}
template<class Iter, class Compare, bool Branchless>
inline bool pdqsort_try_sort_loop(Iter begin,
Iter end,
Compare comp,
size_t bad_allowed,
size_t iterations_allowed,
bool force_sort = false,
bool leftmost = true) {
typedef typename std::iterator_traits<Iter>::difference_type diff_t;
// Use a while loop for tail recursion elimination.
while (true) {
if (!force_sort && iterations_allowed == 0) {
return false;
}
diff_t size = end - begin;
// Insertion sort is faster for small arrays.
if (size < insertion_sort_threshold) {
if (leftmost) insertion_sort(begin, end, comp);
else unguarded_insertion_sort(begin, end, comp);
return true;
}
// Choose pivot as median of 3 or pseudomedian of 9.
diff_t s2 = size / 2;
if (size > ninther_threshold) {
sort3(begin, begin + s2, end - 1, comp);
sort3(begin + 1, begin + (s2 - 1), end - 2, comp);
sort3(begin + 2, begin + (s2 + 1), end - 3, comp);
sort3(begin + (s2 - 1), begin + s2, begin + (s2 + 1), comp);
std::iter_swap(begin, begin + s2);
} else sort3(begin + s2, begin, end - 1, comp);
// If *(begin - 1) is the end of the right partition of a previous partition operation
// there is no element in [begin, end) that is smaller than *(begin - 1). Then if our
// pivot compares equal to *(begin - 1) we change strategy, putting equal elements in
// the left partition, greater elements in the right partition. We do not have to
// recurse on the left partition, since it's sorted (all equal).
if (!leftmost && !comp(*(begin - 1), *begin)) {
begin = partition_left(begin, end, comp) + 1;
continue;
}
// Partition and get results.
std::pair<Iter, bool> part_result =
Branchless ? partition_right_branchless(begin, end, comp)
: partition_right(begin, end, comp);
Iter pivot_pos = part_result.first;
bool already_partitioned = part_result.second;
// Check for a highly unbalanced partition.
diff_t l_size = pivot_pos - begin;
diff_t r_size = end - (pivot_pos + 1);
bool highly_unbalanced = l_size < size / 8 || r_size < size / 8;
// If we got a highly unbalanced partition we shuffle elements to break many patterns.
if (highly_unbalanced) {
if (!force_sort) {
return false;
}
// If we had too many bad partitions, switch to heapsort to guarantee O(n log n).
if (--bad_allowed == 0) {
std::make_heap(begin, end, comp);
std::sort_heap(begin, end, comp);
return true;
}
if (l_size >= insertion_sort_threshold) {
std::iter_swap(begin, begin + l_size / 4);
std::iter_swap(pivot_pos - 1, pivot_pos - l_size / 4);
if (l_size > ninther_threshold) {
std::iter_swap(begin + 1, begin + (l_size / 4 + 1));
std::iter_swap(begin + 2, begin + (l_size / 4 + 2));
std::iter_swap(pivot_pos - 2, pivot_pos - (l_size / 4 + 1));
std::iter_swap(pivot_pos - 3, pivot_pos - (l_size / 4 + 2));
}
}
if (r_size >= insertion_sort_threshold) {
std::iter_swap(pivot_pos + 1, pivot_pos + (1 + r_size / 4));
std::iter_swap(end - 1, end - r_size / 4);
if (r_size > ninther_threshold) {
std::iter_swap(pivot_pos + 2, pivot_pos + (2 + r_size / 4));
std::iter_swap(pivot_pos + 3, pivot_pos + (3 + r_size / 4));
std::iter_swap(end - 2, end - (1 + r_size / 4));
std::iter_swap(end - 3, end - (2 + r_size / 4));
}
}
} else {
// If we were decently balanced and we tried to sort an already partitioned
// sequence try to use insertion sort.
if (already_partitioned && partial_insertion_sort(begin, pivot_pos, comp)
&& partial_insertion_sort(pivot_pos + 1, end, comp)) {
return true;
}
}
// Sort the left partition first using recursion and do tail recursion elimination for
// the right-hand partition.
if (pdqsort_try_sort_loop<Iter, Compare, Branchless>(begin,
pivot_pos,
comp,
bad_allowed,
iterations_allowed - 1,
force_sort,
leftmost)) {
force_sort = true;
} else {
return false;
}
--iterations_allowed;
begin = pivot_pos + 1;
leftmost = false;
}
return false;
}
template<class Iter, class Compare, bool Branchless>
inline bool pdqsort_try_sort_impl(Iter begin, Iter end, Compare comp, size_t bad_allowed)
{
typedef typename std::iterator_traits<Iter>::difference_type diff_t;
static constexpr size_t iterations_allowed = pdqsort_detail::try_sort_iterations;
static constexpr size_t num_to_try = 16;
diff_t size = end - begin;
if (size > num_to_try * 10)
{
size_t out_of_order_elements = 0;
for (size_t i = 1; i < num_to_try; ++i)
{
diff_t offset = size / num_to_try;
diff_t prev_position = offset * (i - 1);
diff_t curr_position = offset * i;
diff_t next_position = offset * (i + 1) - 1;
bool prev_less_than_curr = comp(*(begin + prev_position), *(begin + curr_position));
bool curr_less_than_next = comp(*(begin + curr_position), *(begin + next_position));
if ((prev_less_than_curr && curr_less_than_next) || (!prev_less_than_curr && !curr_less_than_next))
continue;
++out_of_order_elements;
if (out_of_order_elements > iterations_allowed)
return false;
}
}
return pdqsort_try_sort_loop<Iter, Compare, Branchless>(begin, end, comp, bad_allowed, iterations_allowed);
}
}
@ -538,6 +701,41 @@ inline void pdqsort_branchless(Iter begin, Iter end) {
pdqsort_branchless(begin, end, std::less<T>());
}
template<class Iter, class Compare>
inline bool pdqsort_try_sort(Iter begin, Iter end, Compare comp) {
if (begin == end) return true;
#if __cplusplus >= 201103L
return pdqsort_detail::pdqsort_try_sort_impl<Iter, Compare,
pdqsort_detail::is_default_compare<typename std::decay<Compare>::type>::value &&
std::is_arithmetic<typename std::iterator_traits<Iter>::value_type>::value>(
begin, end, comp, pdqsort_detail::log2(end - begin));
#else
return pdqsort_detail::pdqsort_try_sort_impl<Iter, Compare, false>(
begin, end, comp, pdqsort_detail::log2(end - begin));
#endif
}
template<class Iter>
inline bool pdqsort_try_sort(Iter begin, Iter end) {
typedef typename std::iterator_traits<Iter>::value_type T;
return pdqsort_try_sort(begin, end, std::less<T>());
}
template<class Iter, class Compare>
inline bool pdqsort_try_sort_branchless(Iter begin, Iter end, Compare comp) {
if (begin == end) return true;
return pdqsort_detail::pdqsort_try_sort_impl<Iter, Compare, true>(
begin, end, comp, pdqsort_detail::log2(end - begin));
}
template<class Iter>
inline bool pdqsort_try_sort_branchless(Iter begin, Iter end) {
typedef typename std::iterator_traits<Iter>::value_type T;
return pdqsort_try_sort_branchless(begin, end, std::less<T>());
}
#undef PDQSORT_PREFER_MOVE

View File

@ -4,7 +4,7 @@ sidebar_position: 105
sidebar_label: JSON
---
There are two sets of functions to parse JSON.
There are two sets of functions to parse JSON.
- `visitParam*` (`simpleJSON*`) is made to parse a special very limited subset of a JSON, but these functions are extremely fast.
- `JSONExtract*` is made to parse normal JSON.
@ -367,7 +367,7 @@ SELECT JSON_EXISTS('{"hello":["world"]}', '$.hello[*]');
SELECT JSON_EXISTS('{"hello":["world"]}', '$.hello[0]');
```
:::note
:::note
Before version 21.11 the order of arguments was wrong, i.e. JSON_EXISTS(path, json)
:::
@ -394,7 +394,7 @@ Result:
[2]
String
```
:::note
:::note
Before version 21.11 the order of arguments was wrong, i.e. JSON_QUERY(path, json)
:::
@ -424,7 +424,7 @@ world
String
```
:::note
:::note
Before version 21.11 the order of arguments was wrong, i.e. JSON_VALUE(path, json)
:::
@ -513,7 +513,7 @@ SELECT
## jsonMergePatch
Return the merged JSON object string which is formed by merging multiple JSON objects.
Returns the merged JSON object string which is formed by merging multiple JSON objects.
**Syntax**

View File

@ -6,7 +6,7 @@ sidebar_label: "Что такое ClickHouse"
# Что такое ClickHouse {#what-is-clickhouse}
ClickHouse - столбцовая система управления базами данных (СУБД) для онлайн обработки аналитических запросов (OLAP).
ClickHouse столбцовая система управления базами данных (СУБД) для онлайн-обработки аналитических запросов (OLAP).
В обычной, «строковой» СУБД, данные хранятся в таком порядке:
@ -19,10 +19,10 @@ ClickHouse - столбцовая система управления базам
То есть, значения, относящиеся к одной строке, физически хранятся рядом.
Примеры строковых СУБД: MySQL, Postgres, MS SQL Server.
Примеры строковых СУБД: MySQL, PostgreSQL, MS SQL Server.
{: .grey }
В столбцовых СУБД, данные хранятся в таком порядке:
В столбцовых СУБД данные хранятся в таком порядке:
| Строка: | #0 | #1 | #2 | #N |
|-------------|---------------------|---------------------|---------------------|-----|
@ -33,37 +33,37 @@ ClickHouse - столбцовая система управления базам
| EventTime: | 2016-05-18 05:19:20 | 2016-05-18 08:10:20 | 2016-05-18 07:38:00 | … |
В примерах изображён только порядок расположения данных.
То есть, значения из разных столбцов хранятся отдельно, а данные одного столбца - вместе.
То есть значения из разных столбцов хранятся отдельно, а данные одного столбца вместе.
Примеры столбцовых СУБД: Vertica, Paraccel (Actian Matrix, Amazon Redshift), Sybase IQ, Exasol, Infobright, InfiniDB, MonetDB (VectorWise, Actian Vector), LucidDB, SAP HANA, Google Dremel, Google PowerDrill, Druid, kdb+.
{: .grey }
Разный порядок хранения данных лучше подходит для разных сценариев работы.
Сценарий работы с данными - это то, какие производятся запросы, как часто и в каком соотношении; сколько читается данных на запросы каждого вида - строк, столбцов, байт; как соотносятся чтения и обновления данных; какой рабочий размер данных и насколько локально он используется; используются ли транзакции и с какой изолированностью; какие требования к дублированию данных и логической целостности; требования к задержкам на выполнение и пропускной способности запросов каждого вида и т. п.
Сценарий работы с данными это то, какие производятся запросы, как часто и в каком соотношении; сколько читается данных на запросы каждого вида — строк, столбцов, байтов; как соотносятся чтения и обновления данных; какой рабочий размер данных и насколько локально он используется; используются ли транзакции и с какой изолированностью; какие требования к дублированию данных и логической целостности; требования к задержкам на выполнение и пропускной способности запросов каждого вида и т. п.
Чем больше нагрузка на систему, тем более важной становится специализация под сценарий работы, и тем более конкретной становится эта специализация. Не существует системы, одинаково хорошо подходящей под существенно различные сценарии работы. Если система подходит под широкое множество сценариев работы, то при достаточно большой нагрузке, система будет справляться со всеми сценариями работы плохо, или справляться хорошо только с одним из сценариев работы.
## Ключевые особенности OLAP сценария работы {#kliuchevye-osobennosti-olap-stsenariia-raboty}
## Ключевые особенности OLAP-сценария работы {#kliuchevye-osobennosti-olap-stsenariia-raboty}
- подавляющее большинство запросов - на чтение;
- подавляющее большинство запросов на чтение;
- данные обновляются достаточно большими пачками (\> 1000 строк), а не по одной строке, или не обновляются вообще;
- данные добавляются в БД, но не изменяются;
- при чтении, вынимается достаточно большое количество строк из БД, но только небольшое подмножество столбцов;
- таблицы являются «широкими», то есть, содержат большое количество столбцов;
- при чтении «вынимается» достаточно большое количество строк из БД, но только небольшое подмножество столбцов;
- таблицы являются «широкими», то есть содержат большое количество столбцов;
- запросы идут сравнительно редко (обычно не более сотни в секунду на сервер);
- при выполнении простых запросов, допустимы задержки в районе 50 мс;
- значения в столбцах достаточно мелкие - числа и небольшие строки (пример - 60 байт на URL);
- требуется высокая пропускная способность при обработке одного запроса (до миллиардов строк в секунду на один сервер);
- значения в столбцах достаточно мелкие — числа и небольшие строки (например, 60 байт на URL);
- требуется высокая пропускная способность при обработке одного запроса (до миллиардов строк в секунду на один узел);
- транзакции отсутствуют;
- низкие требования к консистентности данных;
- в запросе одна большая таблица, все таблицы кроме одной маленькие;
- результат выполнения запроса существенно меньше исходных данных - то есть, данные фильтруются или агрегируются; результат выполнения помещается в оперативку на одном сервере.
- результат выполнения запроса существенно меньше исходных данных — то есть данные фильтруются или агрегируются; результат выполнения помещается в оперативную память одного узла.
Легко видеть, что OLAP сценарий работы существенно отличается от других распространённых сценариев работы (например, OLTP или Key-Value сценариев работы). Таким образом, не имеет никакого смысла пытаться использовать OLTP или Key-Value БД для обработки аналитических запросов, если вы хотите получить приличную производительность («выше плинтуса»). Например, если вы попытаетесь использовать для аналитики MongoDB или Redis - вы получите анекдотически низкую производительность по сравнению с OLAP-СУБД.
Легко видеть, что OLAP-сценарий работы существенно отличается от других распространённых сценариев работы (например, OLTP или Key-Value сценариев работы). Таким образом, не имеет никакого смысла пытаться использовать OLTP-системы или системы класса «ключ — значение» для обработки аналитических запросов, если вы хотите получить приличную производительность («выше плинтуса»). Например, если вы попытаетесь использовать для аналитики MongoDB или Redis вы получите анекдотически низкую производительность по сравнению с OLAP-СУБД.
## Причины, по которым столбцовые СУБД лучше подходят для OLAP сценария {#prichiny-po-kotorym-stolbtsovye-subd-luchshe-podkhodiat-dlia-olap-stsenariia}
## Причины, по которым столбцовые СУБД лучше подходят для OLAP-сценария {#prichiny-po-kotorym-stolbtsovye-subd-luchshe-podkhodiat-dlia-olap-stsenariia}
Столбцовые СУБД лучше (от 100 раз по скорости обработки большинства запросов) подходят для OLAP сценария работы. Причины в деталях будут разъяснены ниже, а сам факт проще продемонстрировать визуально:
Столбцовые СУБД лучше (от 100 раз по скорости обработки большинства запросов) подходят для OLAP-сценария работы. Причины в деталях будут разъяснены ниже, а сам факт проще продемонстрировать визуально:
**Строковые СУБД**
@ -94,6 +94,6 @@ ClickHouse - столбцовая система управления базам
2. Кодогенерация. Для запроса генерируется код, в котором подставлены все косвенные вызовы.
В «обычных» БД этого не делается, так как не имеет смысла при выполнении простых запросов. Хотя есть исключения. Например, в MemSQL кодогенерация используется для уменьшения latency при выполнении SQL запросов. Для сравнения, в аналитических СУБД требуется оптимизация throughput, а не latency.
В «обычных» СУБД этого не делается, так как не имеет смысла при выполнении простых запросов. Хотя есть исключения. Например, в MemSQL кодогенерация используется для уменьшения времени отклика при выполнении SQL-запросов. Для сравнения: в аналитических СУБД требуется оптимизация по пропускной способности (throughput, ГБ/с), а не времени отклика (latency, с).
Стоит заметить, что для эффективности по CPU требуется, чтобы язык запросов был декларативным (SQL, MDX) или хотя бы векторным (J, K). То есть, чтобы запрос содержал циклы только в неявном виде, открывая возможности для оптимизации.
Стоит заметить, что для эффективности по CPU требуется, чтобы язык запросов был декларативным (SQL, MDX) или хотя бы векторным (J, K). То есть необходимо, чтобы запрос содержал циклы только в неявном виде, открывая возможности для оптимизации.

View File

@ -8,11 +8,11 @@ sidebar_label: "Отличительные возможности ClickHouse"
## По-настоящему столбцовая СУБД {#po-nastoiashchemu-stolbtsovaia-subd}
В по-настоящему столбцовой СУБД рядом со значениями не хранится никаких лишних данных. Например, должны поддерживаться значения постоянной длины, чтобы не хранить рядом со значениями типа «число» их длины. Для примера, миллиард значений типа UInt8 должен действительно занимать в несжатом виде около 1GB, иначе это сильно ударит по эффективности использования CPU. Очень важно хранить данные компактно (без «мусора») в том числе в несжатом виде, так как скорость разжатия (использование CPU) зависит, в основном, от объёма несжатых данных.
В по-настоящему столбцовой СУБД рядом со значениями не хранится никаких лишних данных. Например, должны поддерживаться значения постоянной длины, чтобы не хранить рядом со значениями типа «число» их длины. Для примера, миллиард значений типа UInt8 должен действительно занимать в несжатом виде около 1 ГБ, иначе это сильно ударит по эффективности использования CPU. Очень важно хранить данные компактно (без «мусора») в том числе в несжатом виде, так как скорость разжатия (использование CPU) зависит, в основном, от объёма несжатых данных.
Этот пункт пришлось выделить, так как существуют системы, которые могут хранить значения отдельных столбцов по отдельности, но не могут эффективно выполнять аналитические запросы в силу оптимизации под другой сценарий работы. Примеры: HBase, BigTable, Cassandra, HyperTable. В этих системах вы получите пропускную способность в районе сотен тысяч строк в секунду, но не сотен миллионов строк в секунду.
Также стоит заметить, что ClickHouse является системой управления базами данных, а не одной базой данных. То есть, ClickHouse позволяет создавать таблицы и базы данных в runtime, загружать данные и выполнять запросы без переконфигурирования и перезапуска сервера.
Также стоит заметить, что ClickHouse является системой управления базами данных, а не системой для одной базой данных. То есть, ClickHouse позволяет создавать таблицы и базы данных во время выполнения (runtime), загружать данные и выполнять запросы без переконфигурирования и перезапуска сервера.
## Сжатие данных {#szhatie-dannykh}
@ -20,7 +20,7 @@ sidebar_label: "Отличительные возможности ClickHouse"
## Хранение данных на диске {#khranenie-dannykh-na-diske}
Многие столбцовые СУБД (SAP HANA, Google PowerDrill) могут работать только в оперативной памяти. Такой подход стимулирует выделять больший бюджет на оборудование, чем фактически требуется для анализа в реальном времени. ClickHouse спроектирован для работы на обычных жестких дисках, что обеспечивает низкую стоимость хранения на гигабайт данных, но SSD и дополнительная оперативная память тоже полноценно используются, если доступны.
Многие столбцовые СУБД (SAP HANA, Google PowerDrill) могут работать только в оперативной памяти. Такой подход стимулирует выделять больший бюджет на оборудование, чем фактически требуется для анализа в реальном времени. ClickHouse спроектирован для работы на обычных жестких дисках, что обеспечивает низкую стоимость хранения на гигабайт данных. При этом твердотельные накопители (SSD) и дополнительная оперативная память тоже полноценно используются, если доступны.
## Параллельная обработка запроса на многих процессорных ядрах {#parallelnaia-obrabotka-zaprosa-na-mnogikh-protsessornykh-iadrakh}
@ -29,11 +29,11 @@ sidebar_label: "Отличительные возможности ClickHouse"
## Распределённая обработка запроса на многих серверах {#raspredelionnaia-obrabotka-zaprosa-na-mnogikh-serverakh}
Почти все перечисленные ранее столбцовые СУБД не поддерживают распределённую обработку запроса.
В ClickHouse данные могут быть расположены на разных шардах. Каждый шард может представлять собой группу реплик, которые используются для отказоустойчивости. Запрос будет выполнен на всех шардах параллельно. Это делается прозрачно для пользователя.
В ClickHouse данные могут быть расположены на разных сегментах (shards). Каждый сегмент может представлять собой группу реплик, которые используются для отказоустойчивости. Запрос будет выполнен на всех сегментах параллельно. Это делается прозрачно для пользователя.
## Поддержка SQL {#sql-support}
ClickHouse поддерживает [декларативный язык запросов на основе SQL](../sql-reference/index.md) и во [многих случаях](../sql-reference/ansi.mdx) совпадающий с SQL стандартом.
ClickHouse поддерживает [декларативный язык запросов на основе SQL](../sql-reference/index.md) и во [многих случаях](../sql-reference/ansi.mdx) совпадающий с SQL-стандартом.
Поддерживаются [GROUP BY](../sql-reference/statements/select/group-by.md), [ORDER BY](../sql-reference/statements/select/order-by.md), подзапросы в секциях [FROM](../sql-reference/statements/select/from.md), [IN](../sql-reference/operators/in.md), [JOIN](../sql-reference/statements/select/join.md), [функции window](../sql-reference/window-functions/index.mdx), а также скалярные подзапросы.
@ -41,17 +41,17 @@ ClickHouse поддерживает [декларативный язык зап
## Векторный движок {#vektornyi-dvizhok}
Данные не только хранятся по столбцам, но и обрабатываются по векторам - кусочкам столбцов. За счёт этого достигается высокая эффективность по CPU.
Данные не только хранятся по столбцам, но и обрабатываются по векторам — фрагментам столбцов. За счёт этого достигается высокая эффективность по CPU.
## Обновление данных в реальном времени {#obnovlenie-dannykh-v-realnom-vremeni}
ClickHouse поддерживает таблицы с первичным ключом. Для того, чтобы можно было быстро выполнять запросы по диапазону первичного ключа, данные инкрементально сортируются с помощью merge дерева. За счёт этого, поддерживается постоянное добавление данных в таблицу. Блокировки при добавлении данных отсутствуют.
ClickHouse поддерживает таблицы с первичным ключом. Для того, чтобы можно было быстро выполнять запросы по диапазону первичного ключа, данные инкрементально сортируются с помощью дерева со слиянием (merge tree). За счёт этого поддерживается постоянное добавление данных в таблицу. Блокировки при добавлении данных отсутствуют.
## Наличие индекса {#nalichie-indeksa}
Физическая сортировка данных по первичному ключу позволяет получать данные для конкретных его значений или их диапазонов с низкими задержками - менее десятков миллисекунд.
Физическая сортировка данных по первичному ключу позволяет получать данные для конкретных его значений или их диапазонов с низкими задержками менее десятков миллисекунд.
## Подходит для онлайн запросов {#podkhodit-dlia-onlain-zaprosov}
## Подходит для онлайн-запросов {#podkhodit-dlia-onlain-zaprosov}
Низкие задержки позволяют не откладывать выполнение запроса и не подготавливать ответ заранее, а выполнять его именно в момент загрузки страницы пользовательского интерфейса. То есть, в режиме онлайн.
@ -60,12 +60,12 @@ ClickHouse поддерживает таблицы с первичным клю
ClickHouse предоставляет различные способы разменять точность вычислений на производительность:
1. Система содержит агрегатные функции для приближённого вычисления количества различных значений, медианы и квантилей.
2. Поддерживается возможность выполнить запрос на основе части (выборки) данных и получить приближённый результат. При этом, с диска будет считано пропорционально меньше данных.
2. Поддерживается возможность выполнить запрос на основе части (выборки) данных и получить приближённый результат. При этом с диска будет считано пропорционально меньше данных.
3. Поддерживается возможность выполнить агрегацию не для всех ключей, а для ограниченного количества первых попавшихся ключей. При выполнении некоторых условий на распределение ключей в данных, это позволяет получить достаточно точный результат с использованием меньшего количества ресурсов.
## Репликация данных и поддержка целостности {#replikatsiia-dannykh-i-podderzhka-tselostnosti}
Используется асинхронная multimaster репликация. После записи на любую доступную реплику, данные распространяются на все остальные реплики в фоне. Система поддерживает полную идентичность данных на разных репликах. Восстановление после большинства сбоев осуществляется автоматически, а в сложных случаях — полуавтоматически. При необходимости, можно [включить кворумную запись](../operations/settings/settings.md) данных.
Используется асинхронная multimaster-репликация. После записи на любую доступную реплику, данные распространяются на все остальные реплики в фоне. Система поддерживает полную идентичность данных на разных репликах. Восстановление после большинства сбоев осуществляется автоматически, а в сложных случаях — полуавтоматически. При необходимости, можно [включить кворумную запись](../operations/settings/settings.md) данных.
Подробнее смотрите раздел [Репликация данных](../engines/table-engines/mergetree-family/replication.md).

View File

@ -1918,9 +1918,10 @@ void ClientBase::processParsedSingleQuery(const String & full_query, const Strin
if (is_interactive)
{
std::cout << std::endl
<< processed_rows << " row" << (processed_rows == 1 ? "" : "s")
<< " in set. Elapsed: " << progress_indication.elapsedSeconds() << " sec. ";
std::cout << std::endl;
if (!server_exception || processed_rows != 0)
std::cout << processed_rows << " row" << (processed_rows == 1 ? "" : "s") << " in set. ";
std::cout << "Elapsed: " << progress_indication.elapsedSeconds() << " sec. ";
progress_indication.writeFinalProgress();
std::cout << std::endl << std::endl;
}

View File

@ -4,6 +4,7 @@
#include <Common/assert_cast.h>
#include <Common/WeakHash.h>
#include <Common/HashTable/Hash.h>
#include <Common/RadixSort.h>
#include <base/unaligned.h>
#include <base/sort.h>
@ -15,6 +16,7 @@
#include <Columns/ColumnDecimal.h>
#include <Columns/ColumnCompressed.h>
#include <Columns/MaskOperations.h>
#include <Columns/RadixSortHelper.h>
#include <Processors/Transforms/ColumnGathererTransform.h>
@ -159,6 +161,59 @@ void ColumnDecimal<T>::getPermutation(IColumn::PermutationSortDirection directio
return data[lhs] > data[rhs];
};
size_t data_size = data.size();
res.resize(data_size);
if (limit >= data_size)
limit = 0;
for (size_t i = 0; i < data_size; ++i)
res[i] = i;
if constexpr (is_arithmetic_v<NativeT> && !is_big_int_v<NativeT>)
{
if (!limit)
{
/// A case for radix sort
/// LSD RadixSort is stable
bool reverse = direction == IColumn::PermutationSortDirection::Descending;
bool ascending = direction == IColumn::PermutationSortDirection::Ascending;
bool sort_is_stable = stability == IColumn::PermutationSortStability::Stable;
/// TODO: LSD RadixSort is currently not stable if direction is descending
bool use_radix_sort = (sort_is_stable && ascending) || !sort_is_stable;
/// Thresholds on size. Lower threshold is arbitrary. Upper threshold is chosen by the type for histogram counters.
if (data_size >= 256 && data_size <= std::numeric_limits<UInt32>::max() && use_radix_sort)
{
for (size_t i = 0; i < data_size; ++i)
res[i] = i;
bool try_sort = false;
if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Unstable)
try_sort = trySort(res.begin(), res.end(), comparator_ascending);
else if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Stable)
try_sort = trySort(res.begin(), res.end(), comparator_ascending_stable);
else if (direction == IColumn::PermutationSortDirection::Descending && stability == IColumn::PermutationSortStability::Unstable)
try_sort = trySort(res.begin(), res.end(), comparator_descending);
else
try_sort = trySort(res.begin(), res.end(), comparator_descending_stable);
if (try_sort)
return;
PaddedPODArray<ValueWithIndex<NativeT>> pairs(data_size);
for (UInt32 i = 0; i < static_cast<UInt32>(data_size); ++i)
pairs[i] = {data[i].value, i};
RadixSort<RadixSortTraits<NativeT>>::executeLSD(pairs.data(), data_size, reverse, res.data());
return;
}
}
}
if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Unstable)
this->getPermutationImpl(limit, res, comparator_ascending, DefaultSort(), DefaultPartialSort());
else if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Stable)
@ -191,7 +246,37 @@ void ColumnDecimal<T>::updatePermutation(IColumn::PermutationSortDirection direc
return data[lhs] < data[rhs];
};
auto equals_comparator = [this](size_t lhs, size_t rhs) { return data[lhs] == data[rhs]; };
auto sort = [](auto begin, auto end, auto pred) { ::sort(begin, end, pred); };
auto sort = [&](auto begin, auto end, auto pred)
{
bool reverse = direction == IColumn::PermutationSortDirection::Descending;
bool ascending = direction == IColumn::PermutationSortDirection::Ascending;
bool sort_is_stable = stability == IColumn::PermutationSortStability::Stable;
/// TODO: LSD RadixSort is currently not stable if direction is descending
bool use_radix_sort = (sort_is_stable && ascending) || !sort_is_stable;
size_t size = end - begin;
if (size >= 256 && size <= std::numeric_limits<UInt32>::max() && use_radix_sort)
{
bool try_sort = trySort(begin, end, pred);
if (try_sort)
return;
PaddedPODArray<ValueWithIndex<NativeT>> pairs(size);
size_t index = 0;
for (auto * it = begin; it != end; ++it)
{
pairs[index] = {data[*it].value, static_cast<UInt32>(*it)};
++index;
}
RadixSort<RadixSortTraits<NativeT>>::executeLSD(pairs.data(), size, reverse, begin);
return;
}
::sort(begin, end, pred);
};
auto partial_sort = [](auto begin, auto mid, auto end, auto pred) { ::partial_sort(begin, mid, end, pred); };
if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Unstable)

View File

@ -3,6 +3,7 @@
#include <Columns/ColumnsCommon.h>
#include <Columns/ColumnCompressed.h>
#include <Columns/MaskOperations.h>
#include <Columns/RadixSortHelper.h>
#include <Processors/Transforms/ColumnGathererTransform.h>
#include <IO/WriteHelpers.h>
#include <Common/Arena.h>
@ -192,26 +193,6 @@ struct ColumnVector<T>::equals
bool operator()(size_t lhs, size_t rhs) const { return CompareHelper<T>::equals(parent.data[lhs], parent.data[rhs], nan_direction_hint); }
};
namespace
{
template <typename T>
struct ValueWithIndex
{
T value;
UInt32 index;
};
template <typename T>
struct RadixSortTraits : RadixSortNumTraits<T>
{
using Element = ValueWithIndex<T>;
using Result = size_t;
static T & extractKey(Element & elem) { return elem.value; }
static size_t extractResult(Element & elem) { return elem.index; }
};
}
#if USE_EMBEDDED_COMPILER
template <typename T>
@ -254,35 +235,25 @@ template <typename T>
void ColumnVector<T>::getPermutation(IColumn::PermutationSortDirection direction, IColumn::PermutationSortStability stability,
size_t limit, int nan_direction_hint, IColumn::Permutation & res) const
{
size_t s = data.size();
res.resize(s);
size_t data_size = data.size();
res.resize(data_size);
if (s == 0)
if (data_size == 0)
return;
if (limit >= s)
if (limit >= data_size)
limit = 0;
if (limit)
{
for (size_t i = 0; i < s; ++i)
res[i] = i;
for (size_t i = 0; i < data_size; ++i)
res[i] = i;
if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Unstable)
::partial_sort(res.begin(), res.begin() + limit, res.end(), less(*this, nan_direction_hint));
else if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Stable)
::partial_sort(res.begin(), res.begin() + limit, res.end(), less_stable(*this, nan_direction_hint));
else if (direction == IColumn::PermutationSortDirection::Descending && stability == IColumn::PermutationSortStability::Unstable)
::partial_sort(res.begin(), res.begin() + limit, res.end(), greater(*this, nan_direction_hint));
else if (direction == IColumn::PermutationSortDirection::Descending && stability == IColumn::PermutationSortStability::Stable)
::partial_sort(res.begin(), res.begin() + limit, res.end(), greater_stable(*this, nan_direction_hint));
}
else
if constexpr (is_arithmetic_v<T> && !is_big_int_v<T>)
{
/// A case for radix sort
/// LSD RadixSort is stable
if constexpr (is_arithmetic_v<T> && !is_big_int_v<T>)
if (!limit)
{
/// A case for radix sort
/// LSD RadixSort is stable
bool reverse = direction == IColumn::PermutationSortDirection::Descending;
bool ascending = direction == IColumn::PermutationSortDirection::Ascending;
bool sort_is_stable = stability == IColumn::PermutationSortStability::Stable;
@ -291,13 +262,27 @@ void ColumnVector<T>::getPermutation(IColumn::PermutationSortDirection direction
bool use_radix_sort = (sort_is_stable && ascending && !std::is_floating_point_v<T>) || !sort_is_stable;
/// Thresholds on size. Lower threshold is arbitrary. Upper threshold is chosen by the type for histogram counters.
if (s >= 256 && s <= std::numeric_limits<UInt32>::max() && use_radix_sort)
if (data_size >= 256 && data_size <= std::numeric_limits<UInt32>::max() && use_radix_sort)
{
PaddedPODArray<ValueWithIndex<T>> pairs(s);
for (UInt32 i = 0; i < static_cast<UInt32>(s); ++i)
bool try_sort = false;
if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Unstable)
try_sort = trySort(res.begin(), res.end(), less(*this, nan_direction_hint));
else if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Stable)
try_sort = trySort(res.begin(), res.end(), less_stable(*this, nan_direction_hint));
else if (direction == IColumn::PermutationSortDirection::Descending && stability == IColumn::PermutationSortStability::Unstable)
try_sort = trySort(res.begin(), res.end(), greater(*this, nan_direction_hint));
else
try_sort = trySort(res.begin(), res.end(), greater_stable(*this, nan_direction_hint));
if (try_sort)
return;
PaddedPODArray<ValueWithIndex<T>> pairs(data_size);
for (UInt32 i = 0; i < static_cast<UInt32>(data_size); ++i)
pairs[i] = {data[i], i};
RadixSort<RadixSortTraits<T>>::executeLSD(pairs.data(), s, reverse, res.data());
RadixSort<RadixSortTraits<T>>::executeLSD(pairs.data(), data_size, reverse, res.data());
/// Radix sort treats all NaNs to be greater than all numbers.
/// If the user needs the opposite, we must move them accordingly.
@ -305,9 +290,9 @@ void ColumnVector<T>::getPermutation(IColumn::PermutationSortDirection direction
{
size_t nans_to_move = 0;
for (size_t i = 0; i < s; ++i)
for (size_t i = 0; i < data_size; ++i)
{
if (isNaN(data[res[reverse ? i : s - 1 - i]]))
if (isNaN(data[res[reverse ? i : data_size - 1 - i]]))
++nans_to_move;
else
break;
@ -315,38 +300,35 @@ void ColumnVector<T>::getPermutation(IColumn::PermutationSortDirection direction
if (nans_to_move)
{
std::rotate(std::begin(res), std::begin(res) + (reverse ? nans_to_move : s - nans_to_move), std::end(res));
std::rotate(std::begin(res), std::begin(res) + (reverse ? nans_to_move : data_size - nans_to_move), std::end(res));
}
}
return;
}
}
/// Default sorting algorithm.
for (size_t i = 0; i < s; ++i)
res[i] = i;
if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Unstable)
::sort(res.begin(), res.end(), less(*this, nan_direction_hint));
else if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Stable)
::sort(res.begin(), res.end(), less_stable(*this, nan_direction_hint));
else if (direction == IColumn::PermutationSortDirection::Descending && stability == IColumn::PermutationSortStability::Unstable)
::sort(res.begin(), res.end(), greater(*this, nan_direction_hint));
else if (direction == IColumn::PermutationSortDirection::Descending && stability == IColumn::PermutationSortStability::Stable)
::sort(res.begin(), res.end(), greater_stable(*this, nan_direction_hint));
}
if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Unstable)
this->getPermutationImpl(limit, res, less(*this, nan_direction_hint), DefaultSort(), DefaultPartialSort());
else if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Stable)
this->getPermutationImpl(limit, res, less_stable(*this, nan_direction_hint), DefaultSort(), DefaultPartialSort());
else if (direction == IColumn::PermutationSortDirection::Descending && stability == IColumn::PermutationSortStability::Unstable)
this->getPermutationImpl(limit, res, greater(*this, nan_direction_hint), DefaultSort(), DefaultPartialSort());
else
this->getPermutationImpl(limit, res, greater_stable(*this, nan_direction_hint), DefaultSort(), DefaultPartialSort());
}
template <typename T>
void ColumnVector<T>::updatePermutation(IColumn::PermutationSortDirection direction, IColumn::PermutationSortStability stability,
size_t limit, int nan_direction_hint, IColumn::Permutation & res, EqualRanges & equal_ranges) const
{
bool reverse = direction == IColumn::PermutationSortDirection::Descending;
bool ascending = direction == IColumn::PermutationSortDirection::Ascending;
bool sort_is_stable = stability == IColumn::PermutationSortStability::Stable;
auto sort = [&](auto begin, auto end, auto pred)
{
bool reverse = direction == IColumn::PermutationSortDirection::Descending;
bool ascending = direction == IColumn::PermutationSortDirection::Ascending;
bool sort_is_stable = stability == IColumn::PermutationSortStability::Stable;
/// A case for radix sort
if constexpr (is_arithmetic_v<T> && !is_big_int_v<T>)
{
@ -357,6 +339,10 @@ void ColumnVector<T>::updatePermutation(IColumn::PermutationSortDirection direct
/// Thresholds on size. Lower threshold is arbitrary. Upper threshold is chosen by the type for histogram counters.
if (size >= 256 && size <= std::numeric_limits<UInt32>::max() && use_radix_sort)
{
bool try_sort = trySort(begin, end, pred);
if (try_sort)
return;
PaddedPODArray<ValueWithIndex<T>> pairs(size);
size_t index = 0;

View File

@ -0,0 +1,25 @@
#pragma once
#include <Common/RadixSort.h>
namespace DB
{
template <typename T>
struct ValueWithIndex
{
T value;
UInt32 index;
};
template <typename T>
struct RadixSortTraits : RadixSortNumTraits<T>
{
using Element = ValueWithIndex<T>;
using Result = size_t;
static T & extractKey(Element & elem) { return elem.value; }
static size_t extractResult(Element & elem) { return elem.index; }
};
}

View File

@ -586,7 +586,6 @@
M(704, CANNOT_USE_QUERY_CACHE_WITH_NONDETERMINISTIC_FUNCTIONS) \
M(705, TABLE_NOT_EMPTY) \
M(706, LIBSSH_ERROR) \
M(707, ILLEGAL_JSON_OBJECT_FORMAT) \
M(999, KEEPER_EXCEPTION) \
M(1000, POCO_EXCEPTION) \
M(1001, STD_EXCEPTION) \

View File

@ -20,11 +20,13 @@
namespace DB
{
namespace ErrorCodes
{
extern const int BAD_ARGUMENTS;
extern const int ILLEGAL_COLUMN;
extern const int ILLEGAL_JSON_OBJECT_FORMAT;
extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
extern const int ILLEGAL_TYPE_OF_ARGUMENT;
}
namespace
@ -42,7 +44,6 @@ namespace
static FunctionPtr create(ContextPtr) { return std::make_shared<FunctionjsonMergePatch>(); }
String getName() const override { return name; }
bool isVariadic() const override { return true; }
bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return true; }
@ -54,20 +55,25 @@ namespace
if (arguments.empty())
throw Exception(ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH, "Function {} requires at least one argument.", getName());
for (const auto & arg : arguments)
if (!isString(arg.type))
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Function {} requires string arguments", getName());
return std::make_shared<DataTypeString>();
}
ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr &, size_t input_rows_count) const override
{
rapidjson::Document merged_json;
merged_json.SetObject();
rapidjson::Document::AllocatorType& allocator = merged_json.GetAllocator();
chassert(!arguments.empty());
std::function<void(rapidjson::Value&, const rapidjson::Value&)> mergeObjects;
mergeObjects = [&mergeObjects, &allocator](rapidjson::Value& dest, const rapidjson::Value& src) -> void
rapidjson::Document::AllocatorType allocator;
std::function<void(rapidjson::Value &, const rapidjson::Value &)> merge_objects;
merge_objects = [&merge_objects, &allocator](rapidjson::Value & dest, const rapidjson::Value & src) -> void
{
if (!src.IsObject())
return;
for (auto it = src.MemberBegin(); it != src.MemberEnd(); ++it)
{
rapidjson::Value key(it->name, allocator);
@ -75,7 +81,7 @@ namespace
if (dest.HasMember(key))
{
if (dest[key].IsObject() && value.IsObject())
mergeObjects(dest[key], value);
merge_objects(dest[key], value);
else
dest[key] = value;
}
@ -86,34 +92,57 @@ namespace
}
};
for (const auto & arg : arguments)
auto parse_json_document = [](const ColumnString & column, rapidjson::Document & document, size_t i)
{
const ColumnPtr column = arg.column;
const ColumnString * col = typeid_cast<const ColumnString *>(column.get());
if (!col)
throw Exception(ErrorCodes::ILLEGAL_COLUMN, "First argument of function {} must be string", getName());
auto str_ref = column.getDataAt(i);
const char * json = str_ref.data;
document.Parse(json);
if (document.HasParseError() || !document.IsObject())
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Wrong JSON string to merge. Expected JSON object");
};
const auto * first_string = typeid_cast<const ColumnString *>(arguments[0].column.get());
if (!first_string)
throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Arguments of function {} must be strings", getName());
std::vector<rapidjson::Document> merged_jsons;
merged_jsons.reserve(input_rows_count);
for (size_t i = 0; i < input_rows_count; ++i)
{
auto & merged_json = merged_jsons.emplace_back(rapidjson::Type::kObjectType, &allocator);
parse_json_document(*first_string, merged_json, i);
}
for (size_t col_idx = 1; col_idx < arguments.size(); ++col_idx)
{
const auto * column_string = typeid_cast<const ColumnString *>(arguments[col_idx].column.get());
if (!column_string)
throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Arguments of function {} must be strings", getName());
for (size_t i = 0; i < input_rows_count; ++i)
{
auto str_ref = col->getDataAt(i);
const char* json = str_ref.data;
rapidjson::Document document;
document.Parse(json);
if (!document.IsObject())
throw Exception(ErrorCodes::ILLEGAL_JSON_OBJECT_FORMAT, "Wrong input Json object format");
mergeObjects(merged_json, document);
rapidjson::Document document(&allocator);
parse_json_document(*column_string, document, i);
merge_objects(merged_jsons[i], document);
}
}
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
merged_json.Accept(writer);
std::string json_string = buffer.GetString();
auto result = ColumnString::create();
auto & result_string = assert_cast<ColumnString &>(*result);
rapidjson::CrtAllocator buffer_allocator;
auto res = ColumnString::create();
res->insertData(json_string.c_str(), json_string.size());
for (size_t i = 0; i < input_rows_count; ++i)
{
rapidjson::StringBuffer buffer(&buffer_allocator);
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
return res;
merged_jsons[i].Accept(writer);
result_string.insertData(buffer.GetString(), buffer.GetSize());
}
return result;
}
};
@ -122,7 +151,7 @@ namespace
REGISTER_FUNCTION(jsonMergePatch)
{
factory.registerFunction<FunctionjsonMergePatch>(FunctionDocumentation{
.description="Return the merged JSON object string, which is formed by merging multiple JSON objects."});
.description="Returns the merged JSON object string, which is formed by merging multiple JSON objects."});
}
}

View File

@ -29,7 +29,13 @@ void NormalizeSelectWithUnionQueryMatcher::getSelectsFromUnionListNode(ASTPtr as
void NormalizeSelectWithUnionQueryMatcher::visit(ASTPtr & ast, Data & data)
{
if (auto * select_union = ast->as<ASTSelectWithUnionQuery>())
{
/// The rewrite of ASTSelectWithUnionQuery may strip the format info, so
/// we need to keep and restore it.
auto format = select_union->format;
visit(*select_union, data);
select_union->format = format;
}
}
void NormalizeSelectWithUnionQueryMatcher::visit(ASTSelectWithUnionQuery & ast, Data & data)

View File

@ -159,7 +159,7 @@ def generate_random_files(
values_csv = (
"\n".join((",".join(map(str, row)) for row in rand_values)) + "\n"
).encode()
print(f"File {filename}, content: {total_values}")
print(f"File {filename}, content: {rand_values}")
put_s3_file_content(started_cluster, filename, values_csv)
return total_values

View File

@ -18,15 +18,29 @@
<value>merge_tree_insert_6</value>
</values>
</substitution>
<substitution>
<name>decimal_primary_key_table_name</name>
<values>
<value>merge_tree_insert_7</value>
<value>merge_tree_insert_8</value>
<value>merge_tree_insert_9</value>
</values>
</substitution>
</substitutions>
<create_query>CREATE TABLE merge_tree_insert_1 (value_1 UInt64, value_2 UInt64, value_3 UInt64) ENGINE = MergeTree ORDER BY (value_1)</create_query>
<create_query>CREATE TABLE merge_tree_insert_2 (value_1 UInt64, value_2 UInt64, value_3 UInt64) ENGINE = MergeTree ORDER BY (value_1, value_2)</create_query>
<create_query>CREATE TABLE merge_tree_insert_3 (value_1 UInt64, value_2 UInt64, value_3 UInt64) ENGINE = MergeTree ORDER BY (value_1, value_2, value_3)</create_query>
<create_query>CREATE TABLE merge_tree_insert_4 (value_1 String, value_2 String, value_3 String) ENGINE = MergeTree ORDER BY (value_1)</create_query>
<create_query>CREATE TABLE merge_tree_insert_5 (value_1 String, value_2 String, value_3 String) ENGINE = MergeTree ORDER BY (value_1, value_2)</create_query>
<create_query>CREATE TABLE merge_tree_insert_6 (value_1 String, value_2 String, value_3 String) ENGINE = MergeTree ORDER BY (value_1, value_2, value_3)</create_query>
<create_query>CREATE TABLE merge_tree_insert_7 (value_1 Decimal64(8), value_2 Decimal64(8), value_3 Decimal64(8)) ENGINE = MergeTree ORDER BY (value_1)</create_query>
<create_query>CREATE TABLE merge_tree_insert_8 (value_1 Decimal64(8), value_2 Decimal64(8), value_3 Decimal64(8)) ENGINE = MergeTree ORDER BY (value_1, value_2)</create_query>
<create_query>CREATE TABLE merge_tree_insert_9 (value_1 Decimal64(8), value_2 Decimal64(8), value_3 Decimal64(8)) ENGINE = MergeTree ORDER BY (value_1, value_2, value_3)</create_query>
<query>INSERT INTO {integer_primary_key_table_name} SELECT rand64(0), rand64(1), rand64(2) FROM system.numbers LIMIT 500000</query>
<query>INSERT INTO {integer_primary_key_table_name} SELECT rand64(0), rand64(1), rand64(2) FROM system.numbers LIMIT 1000000</query>
<query>INSERT INTO {integer_primary_key_table_name} SELECT rand64(0), rand64(1), rand64(2) FROM system.numbers LIMIT 1500000</query>
@ -35,7 +49,12 @@
<query>INSERT INTO {string_primary_key_table_name} SELECT toString(rand64(0)), toString(rand64(1)), toString(rand64(2)) FROM system.numbers LIMIT 1000000</query>
<query>INSERT INTO {string_primary_key_table_name} SELECT toString(rand64(0)), toString(rand64(1)), toString(rand64(2)) FROM system.numbers LIMIT 1500000</query>
<query>INSERT INTO {decimal_primary_key_table_name} SELECT rand64(0) % 1000000, rand64(1) % 1500000, rand64(2) % 2000000 FROM system.numbers LIMIT 500000</query>
<query>INSERT INTO {decimal_primary_key_table_name} SELECT rand64(0) % 1000000, rand64(1) % 1500000, rand64(2) % 2000000 FROM system.numbers LIMIT 1000000</query>
<query>INSERT INTO {decimal_primary_key_table_name} SELECT rand64(0) % 1000000, rand64(1) % 1500000, rand64(2) % 2000000 FROM system.numbers LIMIT 1500000</query>
<drop_query>DROP TABLE IF EXISTS {integer_primary_key_table_name}</drop_query>
<drop_query>DROP TABLE IF EXISTS {string_primary_key_table_name}</drop_query>
<drop_query>DROP TABLE IF EXISTS {decimal_primary_key_table_name}</drop_query>
</test>

View File

@ -0,0 +1,28 @@
<test>
<substitutions>
<substitution>
<name>integer_type</name>
<values>
<value>UInt32</value>
<value>UInt64</value>
</values>
</substitution>
<substitution>
<name>sort_expression</name>
<values>
<value>key</value>
<value>key, value</value>
<value>key DESC</value>
<value>key DESC, value DESC</value>
</values>
</substitution>
</substitutions>
<create_query>CREATE TABLE sequential_{integer_type} (key {integer_type}, value {integer_type}) Engine = Memory</create_query>
<fill_query>INSERT INTO sequential_{integer_type} SELECT number, number FROM numbers(500000000)</fill_query>
<query>SELECT key, value FROM sequential_{integer_type} ORDER BY {sort_expression} FORMAT Null</query>
<drop_query>DROP TABLE IF EXISTS sequential_{integer_type}</drop_query>
</test>

View File

@ -8,3 +8,13 @@
{"a":{"b":[3,4],"c":2}}
{"a":5,"b":2,"c":4,"d":6}
{"a":1,"b":null}
{"k0":0,"k1":1}
{"k2":2,"k3":3}
{"k4":4,"k5":5}
{"k6":6,"k7":7}
{"k8":8,"k9":9}
{"k10":222,"k11":11}
{"k12":222,"k13":13}
{"k14":222,"k15":15}
{"k16":222,"k17":17}
{"k18":222,"k19":19}

View File

@ -10,5 +10,15 @@ select jsonMergePatch('{"a": {"b": 1, "c": 2}}', '{"a": {"b": [3, 4]}}');
select jsonMergePatch('{ "a": 1, "b":2 }','{ "a": 3, "c":4 }','{ "a": 5, "d":6 }');
select jsonMergePatch('{"a":1, "b":2}', '{"b":null}');
select jsonMergePatch('[1]'); -- { serverError ILLEGAL_JSON_OBJECT_FORMAT }
select jsonMergePatch('{"a": "1","b": 2,"c": [true,"qrdzkzjvnos": true,"yxqhipj": false,"oesax": "33o8_6AyUy"}]}', '{"c": "1"}'); -- { serverError ILLEGAL_JSON_OBJECT_FORMAT }
select jsonMergePatch('[1]'); -- { serverError BAD_ARGUMENTS }
select jsonMergePatch('{"a": "1","b": 2,"c": [true,"qrdzkzjvnos": true,"yxqhipj": false,"oesax": "33o8_6AyUy"}]}', '{"c": "1"}'); -- { serverError BAD_ARGUMENTS }
drop table if exists t_json_merge;
create table t_json_merge (id UInt64, s1 String, s2 String) engine = Memory;
insert into t_json_merge select number, format('{{ "k{0}": {0} }}', toString(number * 2)), format('{{ "k{0}": {0} }}', toString(number * 2 + 1)) from numbers(5);
insert into t_json_merge select number, format('{{ "k{0}": {0} }}', toString(number * 2)), format('{{ "k{0}": {0}, "k{1}": 222 }}', toString(number * 2 + 1), toString(number * 2)) from numbers(5, 5);
select jsonMergePatch(s1, s2) from t_json_merge ORDER BY id;
drop table t_json_merge;

View File

@ -0,0 +1,7 @@
┌─1─┐
│ 1 │
└───┘
┌─a─┐
│ 1 │
│ 2 │
└───┘

View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=../shell_config.sh
. "$CURDIR"/../shell_config.sh
curl -d@- -sS "${CLICKHOUSE_URL}" <<< 'SELECT 1 UNION DISTINCT SELECT 1 FORMAT PrettyCompactMonoBlock'
curl -d@- -sS "${CLICKHOUSE_URL}" <<< 'SELECT * FROM (SELECT 1 as a UNION DISTINCT SELECT 2 as a) ORDER BY a FORMAT PrettyCompactMonoBlock'

View File

@ -0,0 +1,600 @@
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 1
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 3
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 5
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 7
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 9
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 11
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 13
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 15
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 17
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
-1 19
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 2
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 4
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 6
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 8
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 10
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 14
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 16
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18
0 18

View File

@ -0,0 +1 @@
select -number % 2 as i, toDecimal32(number % 20, 3) as j from numbers(600) order by i, j;