Merge branch 'master' into break_some_tests

This commit is contained in:
mergify[bot] 2021-08-25 08:38:00 +00:00 committed by GitHub
commit eedf9f0936
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 1475 additions and 62 deletions

View File

@ -9,7 +9,7 @@ assignees: ''
> You have to provide the following information whenever possible.
**Describe the bug**
**Describe what's wrong**
> A clear and concise description of what works not as it is supposed to.

View File

@ -45,6 +45,7 @@ include (cmake/arch.cmake)
include (cmake/target.cmake)
include (cmake/tools.cmake)
include (cmake/analysis.cmake)
include (cmake/git_status.cmake)
# Ignore export() since we don't use it,
# but it gets broken with a global targets via link_libraries()

17
cmake/git_status.cmake Normal file
View File

@ -0,0 +1,17 @@
# Print the status of the git repository (if git is available).
# This is useful for troubleshooting build failure reports
find_package(Git)
if (Git_FOUND)
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_COMMIT_ID
OUTPUT_STRIP_TRAILING_WHITESPACE)
message(STATUS "HEAD's commit hash ${GIT_COMMIT_ID}")
execute_process(
COMMAND ${GIT_EXECUTABLE} status
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
else()
message(STATUS "The git program could not be found.")
endif()

2
contrib/nanodbc vendored

@ -1 +1 @@
Subproject commit 9fc459675515d491401727ec67fca38db721f28c
Subproject commit df52a1232dfa182f9af60974d001b91823afe9bc

2
contrib/replxx vendored

@ -1 +1 @@
Subproject commit c81be6c68b146f15f2096b7ef80e3f21fe27004c
Subproject commit f97765df14f4a6236d69b8f14b53ef2051ebd95a

View File

@ -79,8 +79,9 @@ RUN python3 -m pip install \
pytest-timeout \
pytest-xdist \
pytest-repeat \
pytz \
redis \
tzlocal \
tzlocal==2.1 \
urllib3 \
requests-kerberos \
pyhdfs

View File

@ -37,7 +37,7 @@ RUN apt-get update \
ENV TZ=Europe/Moscow
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN pip3 install urllib3 testflows==1.7.20 docker-compose==1.29.1 docker==5.0.0 dicttoxml kazoo tzlocal python-dateutil numpy
RUN pip3 install urllib3 testflows==1.7.20 docker-compose==1.29.1 docker==5.0.0 dicttoxml kazoo tzlocal==2.1 pytz python-dateutil numpy
ENV DOCKER_CHANNEL stable
ENV DOCKER_VERSION 20.10.6

View File

@ -9,9 +9,9 @@ A date. Stored in two bytes as the number of days since 1970-01-01 (unsigned). A
The date value is stored without the time zone.
## Examples {#examples}
**Example**
**1.** Creating a table with a `DateTime`-type column and inserting data into it:
Creating a table with a `Date`-type column and inserting data into it:
``` sql
CREATE TABLE dt
@ -23,10 +23,7 @@ ENGINE = TinyLog;
```
``` sql
INSERT INTO dt Values (1546300800, 1), ('2019-01-01', 2);
```
``` sql
INSERT INTO dt VALUES (1546300800, 1), ('2019-01-01', 2);
SELECT * FROM dt;
```
@ -37,11 +34,8 @@ SELECT * FROM dt;
└────────────┴──────────┘
```
## See Also {#see-also}
**See Also**
- [Functions for working with dates and times](../../sql-reference/functions/date-time-functions.md)
- [Operators for working with dates and times](../../sql-reference/operators/index.md#operators-datetime)
- [`DateTime` data type](../../sql-reference/data-types/datetime.md)
[Original article](https://clickhouse.tech/docs/en/data_types/date/) <!--hide-->

View File

@ -0,0 +1,40 @@
---
toc_priority: 48
toc_title: Date32
---
# Date32 {#data_type-datetime32}
A date. Supports the date range same with [Datetime64](../../sql-reference/data-types/datetime64.md). Stored in four bytes as the number of days since 1925-01-01. Allows storing values till 2283-11-11.
**Examples**
Creating a table with a `Date32`-type column and inserting data into it:
``` sql
CREATE TABLE new
(
`timestamp` Date32,
`event_id` UInt8
)
ENGINE = TinyLog;
```
``` sql
INSERT INTO new VALUES (4102444800, 1), ('2100-01-01', 2);
SELECT * FROM new;
```
``` text
┌──timestamp─┬─event_id─┐
│ 2100-01-01 │ 1 │
│ 2100-01-01 │ 2 │
└────────────┴──────────┘
```
**See Also**
- [toDate32](../../sql-reference/functions/type-conversion-functions.md#todate32)
- [toDate32OrZero](../../sql-reference/functions/type-conversion-functions.md#todate32-or-zero)
- [toDate32OrNull](../../sql-reference/functions/type-conversion-functions.md#todate32-or-null)

View File

@ -17,7 +17,7 @@ DateTime64(precision, [timezone])
Internally, stores data as a number of ticks since epoch start (1970-01-01 00:00:00 UTC) as Int64. The tick resolution is determined by the precision parameter. Additionally, the `DateTime64` type can store time zone that is the same for the entire column, that affects how the values of the `DateTime64` type values are displayed in text format and how the values specified as strings are parsed (2020-01-01 05:00:01.000). The time zone is not stored in the rows of the table (or in resultset), but is stored in the column metadata. See details in [DateTime](../../sql-reference/data-types/datetime.md).
Supported range from January 1, 1925 till December 31, 2283.
Supported range from January 1, 1925 till November 11, 2283.
## Examples {#examples}

View File

@ -152,6 +152,104 @@ Alias: `DATE`.
## toDateTimeOrNull {#todatetimeornull}
## toDate32 {#todate32}
Converts the argument to the [Date32](../../sql-reference/data-types/date32.md) data type. If the value is outside the range returns the border values supported by `Date32`. If the argument has [Date](../../sql-reference/data-types/date.md) type, borders of `Date` are taken into account.
**Syntax**
``` sql
toDate32(expr)
```
**Arguments**
- `expr` — The value. [String](../../sql-reference/data-types/string.md), [UInt32](../../sql-reference/data-types/int-uint.md) or [Date](../../sql-reference/data-types/date.md).
**Returned value**
- A calendar date.
Type: [Date32](../../sql-reference/data-types/date32.md).
**Example**
1. The value is within the range:
``` sql
SELECT toDate32('1955-01-01') AS value, toTypeName(value);
```
``` text
┌──────value─┬─toTypeName(toDate32('1925-01-01'))─┐
│ 1955-01-01 │ Date32 │
└────────────┴────────────────────────────────────┘
```
2. The value is outside the range:
``` sql
SELECT toDate32('1924-01-01') AS value, toTypeName(value);
```
``` text
┌──────value─┬─toTypeName(toDate32('1925-01-01'))─┐
│ 1925-01-01 │ Date32 │
└────────────┴────────────────────────────────────┘
```
3. With `Date`-type argument:
``` sql
SELECT toDate32(toDate('1924-01-01')) AS value, toTypeName(value);
```
``` text
┌──────value─┬─toTypeName(toDate32(toDate('1924-01-01')))─┐
│ 1970-01-01 │ Date32 │
└────────────┴────────────────────────────────────────────┘
```
## toDate32OrZero {#todate32-or-zero}
The same as [toDate32](#todate32) but returns the min value of [Date32](../../sql-reference/data-types/date32.md) if invalid argument is received.
**Example**
Query:
``` sql
SELECT toDate32OrZero('1924-01-01'), toDate32OrZero('');
```
Result:
``` text
┌─toDate32OrZero('1924-01-01')─┬─toDate32OrZero('')─┐
│ 1925-01-01 │ 1925-01-01 │
└──────────────────────────────┴────────────────────┘
```
## toDate32OrNull {#todate32-or-null}
The same as [toDate32](#todate32) but returns `NULL` if invalid argument is received.
**Example**
Query:
``` sql
SELECT toDate32OrNull('1955-01-01'), toDate32OrNull('');
```
Result:
``` text
┌─toDate32OrNull('1955-01-01')─┬─toDate32OrNull('')─┐
│ 1955-01-01 │ ᴺᵁᴸᴸ │
└──────────────────────────────┴────────────────────┘
```
## toDecimal(32\|64\|128\|256) {#todecimal3264128256}
Converts `value` to the [Decimal](../../sql-reference/data-types/decimal.md) data type with precision of `S`. The `value` can be a number or a string. The `S` (scale) parameter specifies the number of decimal places.

View File

@ -14,7 +14,7 @@ You can use table functions in:
The method for creating a temporary table that is available only in the current query. The table is deleted when the query finishes.
- [CREATE TABLE AS \<table_function()\>](../../sql-reference/statements/create/table.md) query.
- [CREATE TABLE AS table_function()](../../sql-reference/statements/create/table.md) query.
It's one of the methods of creating a table.

View File

@ -36,7 +36,7 @@ ClickHouse - полноценная колоночная СУБД. Данные
`IDataType` и `IColumn` слабо связаны друг с другом. Различные типы данных могут быть представлены в памяти с помощью одной реализации `IColumn`. Например, и `DataTypeUInt32`, и `DataTypeDateTime` в памяти представлены как `ColumnUInt32` или `ColumnConstUInt32`. В добавок к этому, один тип данных может быть представлен различными реализациями `IColumn`. Например, `DataTypeUInt8` может быть представлен как `ColumnUInt8` и `ColumnConstUInt8`.
`IDataType` хранит только метаданные. Например, `DataTypeUInt8` не хранить ничего (кроме скрытого указателя `vptr`), а `DataTypeFixedString` хранит только `N` (фиксированный размер строки).
`IDataType` хранит только метаданные. Например, `DataTypeUInt8` не хранит ничего (кроме скрытого указателя `vptr`), а `DataTypeFixedString` хранит только `N` (фиксированный размер строки).
В `IDataType` есть вспомогательные методы для данных различного формата. Среди них методы сериализации значений, допускающих использование кавычек, сериализации значения в JSON или XML. Среди них нет прямого соответствия форматам данных. Например, различные форматы `Pretty` и `TabSeparated` могут использовать один вспомогательный метод `serializeTextEscaped` интерфейса `IDataType`.
@ -62,7 +62,7 @@ ClickHouse - полноценная колоночная СУБД. Данные
> Потоки блоков используют «втягивающий» (pull) подход к управлению потоком выполнения: когда вы вытягиваете блок из первого потока, он, следовательно, вытягивает необходимые блоки из вложенных потоков, так и работает весь конвейер выполнения. Ни «pull» ни «push» не имеют явного преимущества, потому что поток управления неявный, и это ограничивает в реализации различных функций, таких как одновременное выполнение нескольких запросов (слияние нескольких конвейеров вместе). Это ограничение можно преодолеть с помощью сопрограмм (coroutines) или просто запуском дополнительных потоков, которые ждут друг друга. У нас может быть больше возможностей, если мы сделаем поток управления явным: если мы локализуем логику для передачи данных из одной расчетной единицы в другую вне этих расчетных единиц. Читайте эту [статью](http://journal.stuffwithstuff.com/2013/01/13/iteration-inside-and-out/) для углубленного изучения.
Следует отметить, что конвейер выполнения запроса создает временные данные на каждом шаге. Мы стараемся сохранить размер блока достаточно маленьким, чтобы временные данные помещались в кэш процессора. При таком допущении запись и чтение временных данных практически бесплатны по сравнению с другими расчетами. Мы могли бы рассмотреть альтернативу, которая заключается в том, чтобы объединить многие операции в конвеере вместе. Это может сделать конвейер как можно короче и удалить большую часть временных данных, что может быть преимуществом, но у такого подхода также есть недостатки. Например, разделенный конвейер позволяет легко реализовать кэширование промежуточных данных, использование промежуточных данных из аналогичных запросов, выполняемых одновременно, и объединение конвейеров для аналогичных запросов.
Следует отметить, что конвейер выполнения запроса создает временные данные на каждом шаге. Мы стараемся сохранить размер блока достаточно маленьким, чтобы временные данные помещались в кэш процессора. При таком допущении запись и чтение временных данных практически бесплатны по сравнению с другими расчетами. Мы могли бы рассмотреть альтернативу, которая заключается в том, чтобы объединить многие операции в конвейере вместе. Это может сделать конвейер как можно короче и удалить большую часть временных данных, что может быть преимуществом, но у такого подхода также есть недостатки. Например, разделенный конвейер позволяет легко реализовать кэширование промежуточных данных, использование промежуточных данных из аналогичных запросов, выполняемых одновременно, и объединение конвейеров для аналогичных запросов.
## Форматы {#formats}
@ -119,7 +119,7 @@ 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), эксплуатируют блочную обработку и нарушают независимость строк.
@ -162,7 +162,7 @@ ClickHouse имеет сильную типизацию, поэтому нет
Сервера в кластере в основном независимы. Вы можете создать `Распределенную` (`Distributed`) таблицу на одном или всех серверах в кластере. Такая таблица сама по себе не хранит данные - она только предоставляет возможность "просмотра" всех локальных таблиц на нескольких узлах кластера. При выполнении `SELECT` распределенная таблица переписывает запрос, выбирает удаленные узлы в соответствии с настройками балансировки нагрузки и отправляет им запрос. Распределенная таблица просит удаленные сервера обработать запрос до той стадии, когда промежуточные результаты с разных серверов могут быть объединены. Затем он получает промежуточные результаты и объединяет их. Распределенная таблица пытается возложить как можно больше работы на удаленные серверы и сократить объем промежуточных данных, передаваемых по сети.
Ситуация усложняется, при использовании подзапросов в случае `IN` или `JOIN`, когда каждый из них использует таблицу `Distributed`. Есть разные стратегии для выполнения таких запросов.
Ситуация усложняется при использовании подзапросов в случае `IN` или `JOIN`, когда каждый из них использует таблицу `Distributed`. Есть разные стратегии для выполнения таких запросов.
Глобального плана выполнения распределенных запросов не существует. Каждый узел имеет собственный локальный план для своей части работы. У нас есть простое однонаправленное выполнение распределенных запросов: мы отправляем запросы на удаленные узлы и затем объединяем результаты. Но это невозможно для сложных запросов `GROUP BY` высокой кардинальности или запросов с большим числом временных данных в `JOIN`: в таких случаях нам необходимо перераспределить («reshuffle») данные между серверами, что требует дополнительной координации. ClickHouse не поддерживает выполнение запросов такого рода, и нам нужно работать над этим.

View File

@ -9,9 +9,9 @@ toc_title: Date
Дата хранится без учёта часового пояса.
## Примеры {#examples}
**Пример**
**1.** Создание таблицы и добавление в неё данных:
Создание таблицы и добавление в неё данных:
``` sql
CREATE TABLE dt
@ -24,9 +24,6 @@ ENGINE = TinyLog;
``` sql
INSERT INTO dt Values (1546300800, 1), ('2019-01-01', 2);
```
``` sql
SELECT * FROM dt;
```
@ -37,7 +34,7 @@ SELECT * FROM dt;
└────────────┴──────────┘
```
## Смотрите также {#see-also}
**См. также**
- [Функции для работы с датой и временем](../../sql-reference/functions/date-time-functions.md)
- [Операторы для работы с датой и временем](../../sql-reference/operators/index.md#operators-datetime)

View File

@ -0,0 +1,40 @@
---
toc_priority: 48
toc_title: Date32
---
# Date32 {#data_type-datetime32}
Дата. Поддерживается такой же диапазон дат, как для типа [Datetime64](../../sql-reference/data-types/datetime64.md). Значение хранится в четырех байтах и соответствует числу дней с 1925-01-01 по 2283-11-11.
**Пример**
Создание таблицы со столбцом типа `Date32`и добавление в нее данных:
``` sql
CREATE TABLE new
(
`timestamp` Date32,
`event_id` UInt8
)
ENGINE = TinyLog;
```
``` sql
INSERT INTO new VALUES (4102444800, 1), ('2100-01-01', 2);
SELECT * FROM new;
```
``` text
┌──timestamp─┬─event_id─┐
│ 2100-01-01 │ 1 │
│ 2100-01-01 │ 2 │
└────────────┴──────────┘
```
**См. также**
- [toDate32](../../sql-reference/functions/type-conversion-functions.md#todate32)
- [toDate32OrZero](../../sql-reference/functions/type-conversion-functions.md#todate32-or-zero)
- [toDate32OrNull](../../sql-reference/functions/type-conversion-functions.md#todate32-or-null)

View File

@ -17,7 +17,7 @@ DateTime64(precision, [timezone])
Данные хранятся в виде количества ‘тиков’, прошедших с момента начала эпохи (1970-01-01 00:00:00 UTC), в Int64. Размер тика определяется параметром precision. Дополнительно, тип `DateTime64` позволяет хранить часовой пояс, единый для всей колонки, который влияет на то, как будут отображаться значения типа `DateTime64` в текстовом виде и как будут парситься значения заданные в виде строк (2020-01-01 05:00:01.000). Часовой пояс не хранится в строках таблицы (выборки), а хранится в метаданных колонки. Подробнее см. [DateTime](datetime.md).
Поддерживаются значения от 1 января 1925 г. и до 31 декабря 2283 г.
Поддерживаются значения от 1 января 1925 г. и до 11 ноября 2283 г.
## Примеры {#examples}

View File

@ -152,6 +152,104 @@ Cиноним: `DATE`.
## toDateTimeOrNull {#todatetimeornull}
## toDate32 {#todate32}
Конвертирует аргумент в значение типа [Date32](../../sql-reference/data-types/date32.md). Если значение выходит за границы диапазона, возвращается пограничное значение `Date32`. Если аргумент имеет тип [Date](../../sql-reference/data-types/date.md), учитываются границы типа `Date`.
**Синтаксис**
``` sql
toDate32(value)
```
**Аргументы**
- `value` — Значение даты. [String](../../sql-reference/data-types/string.md), [UInt32](../../sql-reference/data-types/int-uint.md) или [Date](../../sql-reference/data-types/date.md).
**Возвращаемое значение**
- Календарная дата.
Тип: [Date32](../../sql-reference/data-types/date32.md).
**Пример**
1. Значение находится в границах диапазона:
``` sql
SELECT toDate32('1955-01-01') AS value, toTypeName(value);
```
``` text
┌──────value─┬─toTypeName(toDate32('1925-01-01'))─┐
│ 1955-01-01 │ Date32 │
└────────────┴────────────────────────────────────┘
```
2. Значение выходит за границы диапазона:
``` sql
SELECT toDate32('1924-01-01') AS value, toTypeName(value);
```
``` text
┌──────value─┬─toTypeName(toDate32('1925-01-01'))─┐
│ 1925-01-01 │ Date32 │
└────────────┴────────────────────────────────────┘
```
3. С аргументом типа `Date`:
``` sql
SELECT toDate32(toDate('1924-01-01')) AS value, toTypeName(value);
```
``` text
┌──────value─┬─toTypeName(toDate32(toDate('1924-01-01')))─┐
│ 1970-01-01 │ Date32 │
└────────────┴────────────────────────────────────────────┘
```
## toDate32OrZero {#todate32-or-zero}
То же самое, что и [toDate32](#todate32), но возвращает минимальное значение типа [Date32](../../sql-reference/data-types/date32.md), если получен недопустимый аргумент.
**Пример**
Запрос:
``` sql
SELECT toDate32OrZero('1924-01-01'), toDate32OrZero('');
```
Результат:
``` text
┌─toDate32OrZero('1924-01-01')─┬─toDate32OrZero('')─┐
│ 1925-01-01 │ 1925-01-01 │
└──────────────────────────────┴────────────────────┘
```
## toDate32OrNull {#todate32-or-null}
То же самое, что и [toDate32](#todate32), но возвращает `NULL`, если получен недопустимый аргумент.
**Пример**
Запрос:
``` sql
SELECT toDate32OrNull('1955-01-01'), toDate32OrNull('');
```
Результат:
``` text
┌─toDate32OrNull('1955-01-01')─┬─toDate32OrNull('')─┐
│ 1955-01-01 │ ᴺᵁᴸᴸ │
└──────────────────────────────┴────────────────────┘
```
## toDecimal(32\|64\|128\|256) {#todecimal3264128}
Преобразует `value` к типу данных [Decimal](../../sql-reference/functions/type-conversion-functions.md) с точностью `S`. `value` может быть числом или строкой. Параметр `S` (scale) задаёт число десятичных знаков.

View File

@ -12,6 +12,7 @@
#include <Interpreters/executeQuery.h>
#include <Interpreters/loadMetadata.h>
#include <Interpreters/DatabaseCatalog.h>
#include <Interpreters/UserDefinedObjectsLoader.h>
#include <Interpreters/Session.h>
#include <Common/Exception.h>
#include <Common/Macros.h>
@ -287,6 +288,12 @@ try
/// Lock path directory before read
status.emplace(path + "status", StatusFile::write_full_info);
fs::create_directories(fs::path(path) / "user_defined/");
LOG_DEBUG(log, "Loading user defined objects from {}", path);
Poco::File(path + "user_defined/").createDirectories();
UserDefinedObjectsLoader::instance().loadObjects(global_context);
LOG_DEBUG(log, "Loaded user defined objects.");
LOG_DEBUG(log, "Loading metadata from {}", path);
fs::create_directories(fs::path(path) / "data/");
fs::create_directories(fs::path(path) / "metadata/");

View File

@ -53,6 +53,7 @@
#include <Interpreters/DNSCacheUpdater.h>
#include <Interpreters/ExternalLoaderXMLConfigRepository.h>
#include <Interpreters/InterserverCredentials.h>
#include <Interpreters/UserDefinedObjectsLoader.h>
#include <Interpreters/JIT/CompiledExpressionCache.h>
#include <Access/AccessControlManager.h>
#include <Storages/StorageReplicatedMergeTree.h>
@ -774,6 +775,7 @@ if (ThreadFuzzer::instance().isEffective())
{
fs::create_directories(path / "data/");
fs::create_directories(path / "metadata/");
fs::create_directories(path / "user_defined/");
/// Directory with metadata of tables, which was marked as dropped by Atomic database
fs::create_directories(path / "metadata_dropped/");
@ -1083,6 +1085,9 @@ if (ThreadFuzzer::instance().isEffective())
/// Wait server pool to avoid use-after-free of destroyed context in the handlers
server_pool.joinAll();
// Uses a raw pointer to global context for getting ZooKeeper.
main_config_reloader.reset();
/** Explicitly destroy Context. It is more convenient than in destructor of Server, because logger is still available.
* At this moment, no one could own shared part of Context.
*/
@ -1095,6 +1100,18 @@ if (ThreadFuzzer::instance().isEffective())
/// system logs may copy global context.
global_context->setCurrentDatabaseNameInGlobalContext(default_database);
LOG_INFO(log, "Loading user defined objects from {}", path_str);
try
{
UserDefinedObjectsLoader::instance().loadObjects(global_context);
}
catch (...)
{
tryLogCurrentException(log, "Caught exception while loading user defined objects");
throw;
}
LOG_DEBUG(log, "Loaded user defined objects");
LOG_INFO(log, "Loading metadata from {}", path_str);
try
@ -1514,7 +1531,6 @@ if (ThreadFuzzer::instance().isEffective())
LOG_INFO(log, "Closed connections.");
dns_cache_updater.reset();
main_config_reloader.reset();
if (current_connections)
{

View File

@ -87,6 +87,7 @@ enum class AccessType
M(CREATE_DICTIONARY, "", DICTIONARY, CREATE) /* allows to execute {CREATE|ATTACH} DICTIONARY */\
M(CREATE_TEMPORARY_TABLE, "", GLOBAL, CREATE) /* allows to create and manipulate temporary tables;
implicitly enabled by the grant CREATE_TABLE on any table */ \
M(CREATE_FUNCTION, "", DATABASE, CREATE) /* allows to execute CREATE FUNCTION */ \
M(CREATE, "", GROUP, ALL) /* allows to execute {CREATE|ATTACH} */ \
\
M(DROP_DATABASE, "", DATABASE, DROP) /* allows to execute {DROP|DETACH} DATABASE */\
@ -94,6 +95,7 @@ enum class AccessType
M(DROP_VIEW, "", VIEW, DROP) /* allows to execute {DROP|DETACH} TABLE for views;
implicitly enabled by the grant DROP_TABLE */\
M(DROP_DICTIONARY, "", DICTIONARY, DROP) /* allows to execute {DROP|DETACH} DICTIONARY */\
M(DROP_FUNCTION, "", DATABASE, DROP) /* allows to execute DROP FUNCTION */\
M(DROP, "", GROUP, ALL) /* allows to execute {DROP|DETACH} */\
\
M(TRUNCATE, "TRUNCATE TABLE", TABLE, ALL) \

View File

@ -45,7 +45,7 @@ TEST(AccessRights, Union)
lhs.grant(AccessType::INSERT);
rhs.grant(AccessType::ALL, "db1");
lhs.makeUnion(rhs);
ASSERT_EQ(lhs.toString(), "GRANT INSERT ON *.*, GRANT SHOW, SELECT, ALTER, CREATE DATABASE, CREATE TABLE, CREATE VIEW, CREATE DICTIONARY, DROP, TRUNCATE, OPTIMIZE, SYSTEM MERGES, SYSTEM TTL MERGES, SYSTEM FETCHES, SYSTEM MOVES, SYSTEM SENDS, SYSTEM REPLICATION QUEUES, SYSTEM DROP REPLICA, SYSTEM SYNC REPLICA, SYSTEM RESTART REPLICA, SYSTEM RESTORE REPLICA, SYSTEM FLUSH DISTRIBUTED, dictGet ON db1.*");
ASSERT_EQ(lhs.toString(), "GRANT INSERT ON *.*, GRANT SHOW, SELECT, ALTER, CREATE DATABASE, CREATE TABLE, CREATE VIEW, CREATE DICTIONARY, CREATE FUNCTION, DROP, TRUNCATE, OPTIMIZE, SYSTEM MERGES, SYSTEM TTL MERGES, SYSTEM FETCHES, SYSTEM MOVES, SYSTEM SENDS, SYSTEM REPLICATION QUEUES, SYSTEM DROP REPLICA, SYSTEM SYNC REPLICA, SYSTEM RESTART REPLICA, SYSTEM RESTORE REPLICA, SYSTEM FLUSH DISTRIBUTED, dictGet ON db1.*");
}

View File

@ -578,6 +578,12 @@
M(607, BACKUP_ELEMENT_DUPLICATE) \
M(608, CANNOT_RESTORE_TABLE) \
\
M(598, FUNCTION_ALREADY_EXISTS) \
M(599, CANNOT_DROP_SYSTEM_FUNCTION) \
M(600, CANNOT_CREATE_RECURSIVE_FUNCTION) \
M(601, OBJECT_ALREADY_STORED_ON_DISK) \
M(602, OBJECT_WAS_NOT_STORED_ON_DISK) \
\
M(998, POSTGRESQL_CONNECTION_FAILURE) \
M(999, KEEPER_EXCEPTION) \
M(1000, POCO_EXCEPTION) \

View File

@ -26,8 +26,8 @@ const String & getFunctionCanonicalNameIfAny(const String & name)
return FunctionFactory::instance().getCanonicalNameIfAny(name);
}
void FunctionFactory::registerFunction(const
std::string & name,
void FunctionFactory::registerFunction(
const std::string & name,
Value creator,
CaseSensitiveness case_sensitiveness)
{
@ -119,8 +119,8 @@ FunctionOverloadResolverPtr FunctionFactory::tryGetImpl(
}
FunctionOverloadResolverPtr FunctionFactory::tryGet(
const std::string & name,
ContextPtr context) const
const std::string & name,
ContextPtr context) const
{
auto impl = tryGetImpl(name, context);
return impl ? std::move(impl) : nullptr;

View File

@ -1,7 +1,6 @@
#include <Poco/Net/NetException.h>
#include <IO/ReadBufferFromPocoSocket.h>
#include <IO/TimeoutSetter.h>
#include <Common/Exception.h>
#include <Common/NetException.h>
#include <Common/Stopwatch.h>

View File

@ -16,7 +16,7 @@ class InDepthNodeVisitor
public:
using Data = typename Matcher::Data;
InDepthNodeVisitor(Data & data_, WriteBuffer * ostr_ = nullptr)
explicit InDepthNodeVisitor(Data & data_, WriteBuffer * ostr_ = nullptr)
: data(data_),
visit_depth(0),
ostr(ostr_)

View File

@ -0,0 +1,117 @@
#include <Access/ContextAccess.h>
#include <Parsers/ASTCreateFunctionQuery.h>
#include <Parsers/ASTIdentifier.h>
#include <Interpreters/Context.h>
#include <Interpreters/ExpressionActions.h>
#include <Interpreters/ExpressionAnalyzer.h>
#include <Interpreters/InterpreterCreateFunctionQuery.h>
#include <Interpreters/FunctionNameNormalizer.h>
#include <Interpreters/UserDefinedObjectsLoader.h>
#include <Interpreters/UserDefinedFunctionFactory.h>
namespace DB
{
namespace ErrorCodes
{
extern const int UNKNOWN_IDENTIFIER;
extern const int CANNOT_CREATE_RECURSIVE_FUNCTION;
extern const int UNSUPPORTED_METHOD;
}
BlockIO InterpreterCreateFunctionQuery::execute()
{
auto current_context = getContext();
current_context->checkAccess(AccessType::CREATE_FUNCTION);
FunctionNameNormalizer().visit(query_ptr.get());
auto * create_function_query = query_ptr->as<ASTCreateFunctionQuery>();
if (!create_function_query)
throw Exception(ErrorCodes::UNSUPPORTED_METHOD, "Expected CREATE FUNCTION query");
auto & function_name = create_function_query->function_name;
validateFunction(create_function_query->function_core, function_name);
UserDefinedFunctionFactory::instance().registerFunction(function_name, query_ptr);
if (!is_internal)
{
try
{
UserDefinedObjectsLoader::instance().storeObject(current_context, UserDefinedObjectType::Function, function_name, *query_ptr);
}
catch (Exception & exception)
{
UserDefinedFunctionFactory::instance().unregisterFunction(function_name);
exception.addMessage(fmt::format("while storing user defined function {} on disk", backQuote(function_name)));
throw;
}
}
return {};
}
void InterpreterCreateFunctionQuery::validateFunction(ASTPtr function, const String & name)
{
const auto * args_tuple = function->as<ASTFunction>()->arguments->children.at(0)->as<ASTFunction>();
std::unordered_set<String> arguments;
for (const auto & argument : args_tuple->arguments->children)
{
const auto & argument_name = argument->as<ASTIdentifier>()->name();
auto [_, inserted] = arguments.insert(argument_name);
if (!inserted)
throw Exception(ErrorCodes::UNSUPPORTED_METHOD, "Identifier {} already used as function parameter", argument_name);
}
ASTPtr function_body = function->as<ASTFunction>()->children.at(0)->children.at(1);
std::unordered_set<String> identifiers_in_body = getIdentifiers(function_body);
for (const auto & identifier : identifiers_in_body)
{
if (!arguments.contains(identifier))
throw Exception(ErrorCodes::UNKNOWN_IDENTIFIER, "Identifier {} does not exist in arguments", backQuote(identifier));
}
validateFunctionRecursiveness(function_body, name);
}
std::unordered_set<String> InterpreterCreateFunctionQuery::getIdentifiers(ASTPtr node)
{
std::unordered_set<String> identifiers;
std::stack<ASTPtr> ast_nodes_to_process;
ast_nodes_to_process.push(node);
while (!ast_nodes_to_process.empty())
{
auto ast_node_to_process = ast_nodes_to_process.top();
ast_nodes_to_process.pop();
for (const auto & child : ast_node_to_process->children)
{
auto identifier_name_opt = tryGetIdentifierName(child);
if (identifier_name_opt)
identifiers.insert(identifier_name_opt.value());
ast_nodes_to_process.push(child);
}
}
return identifiers;
}
void InterpreterCreateFunctionQuery::validateFunctionRecursiveness(ASTPtr node, const String & function_to_create)
{
for (const auto & child : node->children)
{
auto function_name_opt = tryGetFunctionName(child);
if (function_name_opt && function_name_opt.value() == function_to_create)
throw Exception(ErrorCodes::CANNOT_CREATE_RECURSIVE_FUNCTION, "You cannot create recursive function");
validateFunctionRecursiveness(child, function_to_create);
}
}
}

View File

@ -0,0 +1,32 @@
#pragma once
#include <Interpreters/IInterpreter.h>
namespace DB
{
class Context;
class InterpreterCreateFunctionQuery : public IInterpreter, WithContext
{
public:
InterpreterCreateFunctionQuery(const ASTPtr & query_ptr_, ContextPtr context_, bool is_internal_)
: WithContext(context_)
, query_ptr(query_ptr_)
, is_internal(is_internal_) {}
BlockIO execute() override;
void setInternal(bool internal_);
private:
static void validateFunction(ASTPtr function, const String & name);
static std::unordered_set<String> getIdentifiers(ASTPtr node);
static void validateFunctionRecursiveness(ASTPtr node, const String & function_to_create);
ASTPtr query_ptr;
bool is_internal;
};
}

View File

@ -0,0 +1,27 @@
#include <Access/ContextAccess.h>
#include <Interpreters/Context.h>
#include <Interpreters/FunctionNameNormalizer.h>
#include <Interpreters/InterpreterDropFunctionQuery.h>
#include <Interpreters/UserDefinedObjectsLoader.h>
#include <Interpreters/UserDefinedFunctionFactory.h>
#include <Parsers/ASTDropFunctionQuery.h>
namespace DB
{
BlockIO InterpreterDropFunctionQuery::execute()
{
auto current_context = getContext();
current_context->checkAccess(AccessType::DROP_FUNCTION);
FunctionNameNormalizer().visit(query_ptr.get());
auto & drop_function_query = query_ptr->as<ASTDropFunctionQuery &>();
UserDefinedFunctionFactory::instance().unregisterFunction(drop_function_query.function_name);
UserDefinedObjectsLoader::instance().removeObject(current_context, UserDefinedObjectType::Function, drop_function_query.function_name);
return {};
}
}

View File

@ -0,0 +1,21 @@
#pragma once
#include <Interpreters/IInterpreter.h>
namespace DB
{
class Context;
class InterpreterDropFunctionQuery : public IInterpreter, WithMutableContext
{
public:
InterpreterDropFunctionQuery(const ASTPtr & query_ptr_, ContextMutablePtr context_) : WithMutableContext(context_), query_ptr(query_ptr_) {}
BlockIO execute() override;
private:
ASTPtr query_ptr;
};
}

View File

@ -7,7 +7,9 @@
#include <Parsers/ASTCreateRowPolicyQuery.h>
#include <Parsers/ASTCreateSettingsProfileQuery.h>
#include <Parsers/ASTCreateUserQuery.h>
#include <Parsers/ASTCreateFunctionQuery.h>
#include <Parsers/ASTDropAccessEntityQuery.h>
#include <Parsers/ASTDropFunctionQuery.h>
#include <Parsers/ASTDropQuery.h>
#include <Parsers/ASTExplainQuery.h>
#include <Parsers/ASTGrantQuery.h>
@ -36,6 +38,7 @@
#include <Interpreters/InterpreterAlterQuery.h>
#include <Interpreters/InterpreterBackupQuery.h>
#include <Interpreters/InterpreterCheckQuery.h>
#include <Interpreters/InterpreterCreateFunctionQuery.h>
#include <Interpreters/InterpreterCreateQuery.h>
#include <Interpreters/InterpreterCreateQuotaQuery.h>
#include <Interpreters/InterpreterCreateRoleQuery.h>
@ -44,6 +47,7 @@
#include <Interpreters/InterpreterCreateUserQuery.h>
#include <Interpreters/InterpreterDescribeQuery.h>
#include <Interpreters/InterpreterDropAccessEntityQuery.h>
#include <Interpreters/InterpreterDropFunctionQuery.h>
#include <Interpreters/InterpreterDropQuery.h>
#include <Interpreters/InterpreterExistsQuery.h>
#include <Interpreters/InterpreterExplainQuery.h>
@ -272,6 +276,14 @@ std::unique_ptr<IInterpreter> InterpreterFactory::get(ASTPtr & query, ContextMut
{
return std::make_unique<InterpreterExternalDDLQuery>(query, context);
}
else if (query->as<ASTCreateFunctionQuery>())
{
return std::make_unique<InterpreterCreateFunctionQuery>(query, context, false /*is_internal*/);
}
else if (query->as<ASTDropFunctionQuery>())
{
return std::make_unique<InterpreterDropFunctionQuery>(query, context);
}
else if (query->as<ASTBackupQuery>())
{
return std::make_unique<InterpreterBackupQuery>(query, context);

View File

@ -14,6 +14,7 @@
#include <Interpreters/CollectJoinOnKeysVisitor.h>
#include <Interpreters/RequiredSourceColumnsVisitor.h>
#include <Interpreters/GetAggregatesVisitor.h>
#include <Interpreters/UserDefinedFunctionsVisitor.h>
#include <Interpreters/TableJoin.h>
#include <Interpreters/ExpressionActions.h> /// getSmallestColumn()
#include <Interpreters/getTableExpressions.h>
@ -1045,6 +1046,9 @@ TreeRewriterResultPtr TreeRewriter::analyze(
void TreeRewriter::normalize(
ASTPtr & query, Aliases & aliases, const NameSet & source_columns_set, bool ignore_alias, const Settings & settings, bool allow_self_aliases)
{
UserDefinedFunctionsVisitor::Data data_user_defined_functions_visitor;
UserDefinedFunctionsVisitor(data_user_defined_functions_visitor).visit(query);
CustomizeCountDistinctVisitor::Data data_count_distinct{settings.count_distinct_implementation};
CustomizeCountDistinctVisitor(data_count_distinct).visit(query);

View File

@ -0,0 +1,83 @@
#include "UserDefinedFunctionFactory.h"
#include <Functions/FunctionFactory.h>
#include <AggregateFunctions/AggregateFunctionFactory.h>
namespace DB
{
namespace ErrorCodes
{
extern const int FUNCTION_ALREADY_EXISTS;
extern const int UNKNOWN_FUNCTION;
extern const int CANNOT_DROP_SYSTEM_FUNCTION;
}
UserDefinedFunctionFactory & UserDefinedFunctionFactory::instance()
{
static UserDefinedFunctionFactory result;
return result;
}
void UserDefinedFunctionFactory::registerFunction(const String & function_name, ASTPtr create_function_query)
{
if (FunctionFactory::instance().hasNameOrAlias(function_name))
throw Exception(ErrorCodes::FUNCTION_ALREADY_EXISTS, "The function '{}' already exists", function_name);
if (AggregateFunctionFactory::instance().hasNameOrAlias(function_name))
throw Exception(ErrorCodes::FUNCTION_ALREADY_EXISTS, "The aggregate function '{}' already exists", function_name);
auto [_, inserted] = function_name_to_create_query.emplace(function_name, std::move(create_function_query));
if (!inserted)
throw Exception(ErrorCodes::FUNCTION_ALREADY_EXISTS,
"The function name '{}' is not unique",
function_name);
}
void UserDefinedFunctionFactory::unregisterFunction(const String & function_name)
{
if (FunctionFactory::instance().hasNameOrAlias(function_name) ||
AggregateFunctionFactory::instance().hasNameOrAlias(function_name))
throw Exception(ErrorCodes::CANNOT_DROP_SYSTEM_FUNCTION, "Cannot drop system function '{}'", function_name);
auto it = function_name_to_create_query.find(function_name);
if (it == function_name_to_create_query.end())
throw Exception(ErrorCodes::UNKNOWN_FUNCTION,
"The function name '{}' is not registered",
function_name);
function_name_to_create_query.erase(it);
}
ASTPtr UserDefinedFunctionFactory::get(const String & function_name) const
{
auto it = function_name_to_create_query.find(function_name);
if (it == function_name_to_create_query.end())
throw Exception(ErrorCodes::UNKNOWN_FUNCTION,
"The function name '{}' is not registered",
function_name);
return it->second;
}
ASTPtr UserDefinedFunctionFactory::tryGet(const std::string & function_name) const
{
auto it = function_name_to_create_query.find(function_name);
if (it == function_name_to_create_query.end())
return nullptr;
return it->second;
}
std::vector<std::string> UserDefinedFunctionFactory::getAllRegisteredNames() const
{
std::vector<std::string> registered_names;
registered_names.reserve(function_name_to_create_query.size());
for (const auto & [name, _] : function_name_to_create_query)
registered_names.emplace_back(name);
return registered_names;
}
}

View File

@ -0,0 +1,32 @@
#pragma once
#include <unordered_map>
#include <Common/NamePrompter.h>
#include <Parsers/ASTCreateFunctionQuery.h>
namespace DB
{
class UserDefinedFunctionFactory : public IHints<1, UserDefinedFunctionFactory>
{
public:
static UserDefinedFunctionFactory & instance();
void registerFunction(const String & function_name, ASTPtr create_function_query);
void unregisterFunction(const String & function_name);
ASTPtr get(const String & function_name) const;
ASTPtr tryGet(const String & function_name) const;
std::vector<String> getAllRegisteredNames() const override;
private:
std::unordered_map<String, ASTPtr> function_name_to_create_query;
};
}

View File

@ -0,0 +1,99 @@
#include "UserDefinedFunctionsVisitor.h"
#include <unordered_map>
#include <stack>
#include <Parsers/ASTFunction.h>
#include <Parsers/ASTCreateFunctionQuery.h>
#include <Parsers/ASTExpressionList.h>
#include <Parsers/ASTIdentifier.h>
#include <Interpreters/UserDefinedFunctionFactory.h>
namespace DB
{
namespace ErrorCodes
{
extern const int UNSUPPORTED_METHOD;
}
void UserDefinedFunctionsMatcher::visit(ASTPtr & ast, Data &)
{
auto * function = ast->as<ASTFunction>();
if (!function)
return;
auto result = tryToReplaceFunction(*function);
if (result)
ast = result;
}
bool UserDefinedFunctionsMatcher::needChildVisit(const ASTPtr &, const ASTPtr &)
{
return true;
}
ASTPtr UserDefinedFunctionsMatcher::tryToReplaceFunction(const ASTFunction & function)
{
auto user_defined_function = UserDefinedFunctionFactory::instance().tryGet(function.name);
if (!user_defined_function)
return nullptr;
const auto & function_arguments_list = function.children.at(0)->as<ASTExpressionList>();
auto & function_arguments = function_arguments_list->children;
const auto & create_function_query = user_defined_function->as<ASTCreateFunctionQuery>();
auto & function_core_expression = create_function_query->function_core->children.at(0);
const auto & identifiers_expression_list = function_core_expression->children.at(0)->children.at(0)->as<ASTExpressionList>();
const auto & identifiers_raw = identifiers_expression_list->children;
if (function_arguments.size() != identifiers_raw.size())
throw Exception(ErrorCodes::UNSUPPORTED_METHOD,
"Function {} expects {} arguments actual arguments {}",
create_function_query->function_name,
identifiers_raw.size(),
function_arguments.size());
std::unordered_map<std::string, ASTPtr> identifier_name_to_function_argument;
for (size_t parameter_index = 0; parameter_index < identifiers_raw.size(); ++parameter_index)
{
const auto & identifier = identifiers_raw[parameter_index]->as<ASTIdentifier>();
const auto & function_argument = function_arguments[parameter_index];
const auto & identifier_name = identifier->name();
identifier_name_to_function_argument.emplace(identifier_name, function_argument);
}
auto function_body_to_update = function_core_expression->children.at(1)->clone();
std::stack<ASTPtr> ast_nodes_to_update;
ast_nodes_to_update.push(function_body_to_update);
while (!ast_nodes_to_update.empty())
{
auto ast_node_to_update = ast_nodes_to_update.top();
ast_nodes_to_update.pop();
for (auto & child : ast_node_to_update->children)
{
auto identifier_name_opt = tryGetIdentifierName(child);
if (identifier_name_opt)
{
auto function_argument_it = identifier_name_to_function_argument.find(*identifier_name_opt);
assert(function_argument_it != identifier_name_to_function_argument.end());
child = function_argument_it->second->clone();
continue;
}
ast_nodes_to_update.push(child);
}
}
return function_body_to_update;
}
}

View File

@ -0,0 +1,44 @@
#pragma once
#include <Interpreters/Aliases.h>
#include <Interpreters/InDepthNodeVisitor.h>
namespace DB
{
class ASTFunction;
/** Visits ASTFunction nodes and if it is used defined function replace it with function body.
* Example:
*
* CREATE FUNCTION test_function AS a -> a + 1;
*
* Before applying visitor:
* SELECT test_function(number) FROM system.numbers LIMIT 10;
*
* After applying visitor:
* SELECT number + 1 FROM system.numbers LIMIT 10;
*/
class UserDefinedFunctionsMatcher
{
public:
using Visitor = InDepthNodeVisitor<UserDefinedFunctionsMatcher, true>;
struct Data
{
};
static void visit(ASTPtr & ast, Data & data);
static bool needChildVisit(const ASTPtr & node, const ASTPtr & child);
private:
static void visit(ASTFunction & func, const Data & data);
static ASTPtr tryToReplaceFunction(const ASTFunction & function);
};
/// Visits AST nodes and collect their aliases in one map (with links to source nodes).
using UserDefinedFunctionsVisitor = UserDefinedFunctionsMatcher::Visitor;
}

View File

@ -0,0 +1,164 @@
#include "UserDefinedObjectsLoader.h"
#include <filesystem>
#include <Common/escapeForFileName.h>
#include <Common/quoteString.h>
#include <Common/StringUtils/StringUtils.h>
#include <IO/ReadBufferFromFile.h>
#include <IO/ReadHelpers.h>
#include <IO/WriteBufferFromFile.h>
#include <IO/WriteHelpers.h>
#include <Interpreters/Context.h>
#include <Interpreters/InterpreterCreateFunctionQuery.h>
#include <Parsers/parseQuery.h>
#include <Parsers/ASTCreateFunctionQuery.h>
#include <Parsers/formatAST.h>
#include <Parsers/ParserCreateFunctionQuery.h>
#include <Poco/DirectoryIterator.h>
#include <Poco/Logger.h>
namespace DB
{
namespace ErrorCodes
{
extern const int OBJECT_ALREADY_STORED_ON_DISK;
extern const int OBJECT_WAS_NOT_STORED_ON_DISK;
}
UserDefinedObjectsLoader & UserDefinedObjectsLoader::instance()
{
static UserDefinedObjectsLoader ret;
return ret;
}
UserDefinedObjectsLoader::UserDefinedObjectsLoader()
: log(&Poco::Logger::get("UserDefinedObjectsLoader"))
{}
void UserDefinedObjectsLoader::loadUserDefinedObject(ContextPtr context, UserDefinedObjectType object_type, const std::string_view & name, const String & path)
{
auto name_ref = StringRef(name.data(), name.size());
LOG_DEBUG(log, "Loading user defined object {} from file {}", backQuote(name_ref), path);
/// There is .sql file with user defined object creation statement.
ReadBufferFromFile in(path);
String object_create_query;
readStringUntilEOF(object_create_query, in);
try
{
switch (object_type)
{
case UserDefinedObjectType::Function:
{
ParserCreateFunctionQuery parser;
ASTPtr ast = parseQuery(
parser,
object_create_query.data(),
object_create_query.data() + object_create_query.size(),
"in file " + path,
0,
context->getSettingsRef().max_parser_depth);
InterpreterCreateFunctionQuery interpreter(ast, context, true /*is internal*/);
interpreter.execute();
}
}
}
catch (Exception & e)
{
e.addMessage(fmt::format("while loading user defined objects {} from path {}", backQuote(name_ref), path));
throw;
}
}
void UserDefinedObjectsLoader::loadObjects(ContextPtr context)
{
LOG_DEBUG(log, "loading user defined objects");
String dir_path = context->getPath() + "user_defined/";
Poco::DirectoryIterator dir_end;
for (Poco::DirectoryIterator it(dir_path); it != dir_end; ++it)
{
if (it->isLink())
continue;
const auto & file_name = it.name();
/// For '.svn', '.gitignore' directory and similar.
if (file_name.at(0) == '.')
continue;
if (!it->isDirectory() && endsWith(file_name, ".sql"))
{
std::string_view object_name = file_name;
object_name.remove_suffix(strlen(".sql"));
object_name.remove_prefix(strlen("function_"));
loadUserDefinedObject(context, UserDefinedObjectType::Function, object_name, dir_path + it.name());
}
}
}
void UserDefinedObjectsLoader::storeObject(ContextPtr context, UserDefinedObjectType object_type, const String & object_name, const IAST & ast)
{
String dir_path = context->getPath() + "user_defined/";
String file_path;
switch (object_type)
{
case UserDefinedObjectType::Function:
{
file_path = dir_path + "function_" + escapeForFileName(object_name) + ".sql";
}
}
if (std::filesystem::exists(file_path))
throw Exception(ErrorCodes::OBJECT_ALREADY_STORED_ON_DISK, "User defined object {} already stored on disk", backQuote(file_path));
LOG_DEBUG(log, "Storing object {} to file {}", backQuote(object_name), file_path);
WriteBufferFromOwnString create_statement_buf;
formatAST(ast, create_statement_buf, false);
writeChar('\n', create_statement_buf);
String create_statement = create_statement_buf.str();
WriteBufferFromFile out(file_path, create_statement.size(), O_WRONLY | O_CREAT | O_EXCL);
writeString(create_statement, out);
out.next();
if (context->getSettingsRef().fsync_metadata)
out.sync();
out.close();
LOG_DEBUG(log, "Stored object {}", backQuote(object_name));
}
void UserDefinedObjectsLoader::removeObject(ContextPtr context, UserDefinedObjectType object_type, const String & object_name)
{
String dir_path = context->getPath() + "user_defined/";
LOG_DEBUG(log, "Removing file for user defined object {} from {}", backQuote(object_name), dir_path);
std::filesystem::path file_path;
switch (object_type)
{
case UserDefinedObjectType::Function:
{
file_path = dir_path + "function_" + escapeForFileName(object_name) + ".sql";
}
}
if (!std::filesystem::exists(file_path))
throw Exception(ErrorCodes::OBJECT_WAS_NOT_STORED_ON_DISK, "User defined object {} was not stored on disk", backQuote(file_path.string()));
std::filesystem::remove(file_path);
}
}

View File

@ -0,0 +1,33 @@
#pragma once
#include <Interpreters/Context_fwd.h>
#include <Parsers/IAST.h>
#include <boost/noncopyable.hpp>
namespace DB
{
enum class UserDefinedObjectType
{
Function
};
class UserDefinedObjectsLoader : private boost::noncopyable
{
public:
static UserDefinedObjectsLoader & instance();
UserDefinedObjectsLoader();
void loadObjects(ContextPtr context);
void storeObject(ContextPtr context, UserDefinedObjectType object_type, const String & object_name, const IAST & ast);
void removeObject(ContextPtr context, UserDefinedObjectType object_type, const String & object_name);
private:
void loadUserDefinedObject(ContextPtr context, UserDefinedObjectType object_type, const std::string_view & object_name, const String & file_path);
Poco::Logger * log;
};
}

View File

@ -71,6 +71,7 @@ SRCS(
InternalTextLogsQueue.cpp
InterpreterAlterQuery.cpp
InterpreterCheckQuery.cpp
InterpreterCreateFunctionQuery.cpp
InterpreterCreateQuery.cpp
InterpreterCreateQuotaQuery.cpp
InterpreterCreateRoleQuery.cpp
@ -79,6 +80,7 @@ SRCS(
InterpreterCreateUserQuery.cpp
InterpreterDescribeQuery.cpp
InterpreterDropAccessEntityQuery.cpp
InterpreterDropFunctionQuery.cpp
InterpreterDropQuery.cpp
InterpreterExistsQuery.cpp
InterpreterExplainQuery.cpp
@ -89,6 +91,7 @@ SRCS(
InterpreterKillQueryQuery.cpp
InterpreterOptimizeQuery.cpp
InterpreterRenameQuery.cpp
InterpreterSelectIntersectExceptQuery.cpp
InterpreterSelectQuery.cpp
InterpreterSelectWithUnionQuery.cpp
InterpreterSetQuery.cpp
@ -142,6 +145,7 @@ SRCS(
RewriteFunctionToSubcolumnVisitor.cpp
RewriteSumIfFunctionVisitor.cpp
RowRefs.cpp
SelectIntersectExceptQueryVisitor.cpp
Set.cpp
SetVariants.cpp
SortedBlocksWriter.cpp
@ -157,6 +161,9 @@ SRCS(
TranslateQualifiedNamesVisitor.cpp
TreeOptimizer.cpp
TreeRewriter.cpp
UserDefinedFunctionFactory.cpp
UserDefinedFunctionsVisitor.cpp
UserDefinedObjectsLoader.cpp
WindowDescription.cpp
ZooKeeperLog.cpp
addMissingDefaults.cpp

View File

@ -0,0 +1,21 @@
#include <Common/quoteString.h>
#include <IO/Operators.h>
#include <Parsers/ASTCreateFunctionQuery.h>
namespace DB
{
ASTPtr ASTCreateFunctionQuery::clone() const
{
return std::make_shared<ASTCreateFunctionQuery>(*this);
}
void ASTCreateFunctionQuery::formatImpl(const IAST::FormatSettings & settings, IAST::FormatState & state, IAST::FormatStateStacked frame) const
{
settings.ostr << (settings.hilite ? hilite_keyword : "") << "CREATE FUNCTION " << (settings.hilite ? hilite_none : "");
settings.ostr << (settings.hilite ? hilite_identifier : "") << backQuoteIfNeed(function_name) << (settings.hilite ? hilite_none : "");
settings.ostr << (settings.hilite ? hilite_keyword : "") << " AS " << (settings.hilite ? hilite_none : "");
function_core->formatImpl(settings, state, frame);
}
}

View File

@ -0,0 +1,22 @@
#pragma once
#include <Parsers/ASTExpressionList.h>
#include <Parsers/ASTFunction.h>
namespace DB
{
class ASTCreateFunctionQuery : public IAST
{
public:
String function_name;
ASTPtr function_core;
String getID(char) const override { return "CreateFunctionQuery"; }
ASTPtr clone() const override;
void formatImpl(const FormatSettings & s, FormatState & state, FormatStateStacked frame) const override;
};
}

View File

@ -0,0 +1,19 @@
#include <Parsers/ASTDropFunctionQuery.h>
#include <Common/quoteString.h>
#include <IO/Operators.h>
namespace DB
{
ASTPtr ASTDropFunctionQuery::clone() const
{
return std::make_shared<ASTDropFunctionQuery>(*this);
}
void ASTDropFunctionQuery::formatImpl(const IAST::FormatSettings & settings, IAST::FormatState &, IAST::FormatStateStacked) const
{
settings.ostr << (settings.hilite ? hilite_keyword : "") << "DROP FUNCTION " << (settings.hilite ? hilite_none : "");
settings.ostr << (settings.hilite ? hilite_identifier : "") << backQuoteIfNeed(function_name) << (settings.hilite ? hilite_none : "");
}
}

View File

@ -0,0 +1,20 @@
#pragma once
#include "IAST.h"
namespace DB
{
class ASTDropFunctionQuery : public IAST
{
public:
String function_name;
String getID(char) const override { return "DropFunctionQuery"; }
ASTPtr clone() const override;
void formatImpl(const FormatSettings & s, FormatState & state, FormatStateStacked frame) const override;
};
}

View File

@ -13,6 +13,7 @@
#include <Parsers/ASTLiteral.h>
#include <Parsers/ASTSubquery.h>
#include <Parsers/ASTWithAlias.h>
#include <Parsers/queryToString.h>
namespace DB
@ -21,6 +22,7 @@ namespace DB
namespace ErrorCodes
{
extern const int UNEXPECTED_EXPRESSION;
extern const int UNEXPECTED_AST_STRUCTURE;
}
void ASTFunction::appendColumnNameImpl(WriteBuffer & ostr) const
@ -557,4 +559,33 @@ void ASTFunction::formatImplWithoutAlias(const FormatSettings & settings, Format
}
}
String getFunctionName(const IAST * ast)
{
String res;
if (tryGetFunctionNameInto(ast, res))
return res;
throw Exception(ast ? queryToString(*ast) + " is not an function" : "AST node is nullptr", ErrorCodes::UNEXPECTED_AST_STRUCTURE);
}
std::optional<String> tryGetFunctionName(const IAST * ast)
{
String res;
if (tryGetFunctionNameInto(ast, res))
return res;
return {};
}
bool tryGetFunctionNameInto(const IAST * ast, String & name)
{
if (ast)
{
if (const auto * node = ast->as<ASTFunction>())
{
name = node->name;
return true;
}
}
return false;
}
}

View File

@ -71,4 +71,14 @@ std::shared_ptr<ASTFunction> makeASTFunction(const String & name, Args &&... arg
return function;
}
/// ASTFunction Helpers: hide casts and semantic.
String getFunctionName(const IAST * ast);
std::optional<String> tryGetFunctionName(const IAST * ast);
bool tryGetFunctionNameInto(const IAST * ast, String & name);
inline String getFunctionName(const ASTPtr & ast) { return getFunctionName(ast.get()); }
inline std::optional<String> tryGetFunctionName(const ASTPtr & ast) { return tryGetFunctionName(ast.get()); }
inline bool tryGetFunctionNameInto(const ASTPtr & ast, String & name) { return tryGetFunctionNameInto(ast.get(), name); }
}

View File

@ -0,0 +1,47 @@
#include <Parsers/ASTCreateFunctionQuery.h>
#include <Parsers/ASTExpressionList.h>
#include <Parsers/ASTIdentifier.h>
#include <Parsers/CommonParsers.h>
#include <Parsers/ExpressionElementParsers.h>
#include <Parsers/ExpressionListParsers.h>
#include <Parsers/ParserCreateFunctionQuery.h>
namespace DB
{
bool ParserCreateFunctionQuery::parseImpl(IParser::Pos & pos, ASTPtr & node, Expected & expected)
{
ParserKeyword s_create("CREATE");
ParserKeyword s_function("FUNCTION");
ParserIdentifier function_name_p;
ParserKeyword s_as("AS");
ParserLambdaExpression lambda_p;
ASTPtr function_name;
ASTPtr function_core;
if (!s_create.ignore(pos, expected))
return false;
if (!s_function.ignore(pos, expected))
return false;
if (!function_name_p.parse(pos, function_name, expected))
return false;
if (!s_as.ignore(pos, expected))
return false;
if (!lambda_p.parse(pos, function_core, expected))
return false;
auto create_function_query = std::make_shared<ASTCreateFunctionQuery>();
node = create_function_query;
create_function_query->function_name = function_name->as<ASTIdentifier &>().name();
create_function_query->function_core = function_core;
return true;
}
}

View File

@ -0,0 +1,16 @@
#pragma once
#include "IParserBase.h"
namespace DB
{
/// CREATE FUNCTION test AS x -> x || '1'
class ParserCreateFunctionQuery : public IParserBase
{
protected:
const char * getName() const override { return "CREATE FUNCTION query"; }
bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override;
};
}

View File

@ -0,0 +1,35 @@
#include <Parsers/ASTDropFunctionQuery.h>
#include <Parsers/ASTIdentifier.h>
#include <Parsers/CommonParsers.h>
#include <Parsers/ExpressionElementParsers.h>
#include <Parsers/ParserDropFunctionQuery.h>
namespace DB
{
bool ParserDropFunctionQuery::parseImpl(IParser::Pos & pos, ASTPtr & node, Expected & expected)
{
ParserKeyword s_drop("DROP");
ParserKeyword s_function("FUNCTION");
ParserIdentifier function_name_p;
ASTPtr function_name;
if (!s_drop.ignore(pos, expected))
return false;
if (!s_function.ignore(pos, expected))
return false;
if (!function_name_p.parse(pos, function_name, expected))
return false;
auto drop_function_query = std::make_shared<ASTDropFunctionQuery>();
node = drop_function_query;
drop_function_query->function_name = function_name->as<ASTIdentifier &>().name();
return true;
}
}

View File

@ -0,0 +1,14 @@
#pragma once
#include "IParserBase.h"
namespace DB
{
/// DROP FUNCTION function1
class ParserDropFunctionQuery : public IParserBase
{
protected:
const char * getName() const override { return "DROP FUNCTION query"; }
bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override;
};
}

View File

@ -1,4 +1,5 @@
#include <Parsers/ParserAlterQuery.h>
#include <Parsers/ParserCreateFunctionQuery.h>
#include <Parsers/ParserBackupQuery.h>
#include <Parsers/ParserCreateQuery.h>
#include <Parsers/ParserCreateQuotaQuery.h>
@ -7,6 +8,7 @@
#include <Parsers/ParserCreateSettingsProfileQuery.h>
#include <Parsers/ParserCreateUserQuery.h>
#include <Parsers/ParserDropAccessEntityQuery.h>
#include <Parsers/ParserDropFunctionQuery.h>
#include <Parsers/ParserDropQuery.h>
#include <Parsers/ParserGrantQuery.h>
#include <Parsers/ParserInsertQuery.h>
@ -37,6 +39,8 @@ bool ParserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
ParserCreateQuotaQuery create_quota_p;
ParserCreateRowPolicyQuery create_row_policy_p;
ParserCreateSettingsProfileQuery create_settings_profile_p;
ParserCreateFunctionQuery create_function_p;
ParserDropFunctionQuery drop_function_p;
ParserDropAccessEntityQuery drop_access_entity_p;
ParserGrantQuery grant_p;
ParserSetRoleQuery set_role_p;
@ -54,6 +58,8 @@ bool ParserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
|| create_quota_p.parse(pos, node, expected)
|| create_row_policy_p.parse(pos, node, expected)
|| create_settings_profile_p.parse(pos, node, expected)
|| create_function_p.parse(pos, node, expected)
|| drop_function_p.parse(pos, node, expected)
|| drop_access_entity_p.parse(pos, node, expected)
|| grant_p.parse(pos, node, expected)
|| external_ddl_p.parse(pos, node, expected)

View File

@ -15,6 +15,7 @@ SRCS(
ASTColumnsMatcher.cpp
ASTColumnsTransformers.cpp
ASTConstraintDeclaration.cpp
ASTCreateFunctionQuery.cpp
ASTCreateQuery.cpp
ASTCreateQuotaQuery.cpp
ASTCreateRoleQuery.cpp
@ -25,6 +26,7 @@ SRCS(
ASTDictionary.cpp
ASTDictionaryAttributeDeclaration.cpp
ASTDropAccessEntityQuery.cpp
ASTDropFunctionQuery.cpp
ASTDropQuery.cpp
ASTExpressionList.cpp
ASTFunction.cpp
@ -89,6 +91,7 @@ SRCS(
ParserAlterQuery.cpp
ParserCase.cpp
ParserCheckQuery.cpp
ParserCreateFunctionQuery.cpp
ParserCreateQuery.cpp
ParserCreateQuotaQuery.cpp
ParserCreateRoleQuery.cpp
@ -101,6 +104,7 @@ SRCS(
ParserDictionary.cpp
ParserDictionaryAttributeDeclaration.cpp
ParserDropAccessEntityQuery.cpp
ParserDropFunctionQuery.cpp
ParserDropQuery.cpp
ParserExplainQuery.cpp
ParserExternalDDLQuery.cpp

View File

@ -1,26 +1,38 @@
#include <AggregateFunctions/AggregateFunctionFactory.h>
#include <DataTypes/DataTypeString.h>
#include <DataTypes/DataTypesNumber.h>
#include <Parsers/queryToString.h>
#include <Functions/FunctionFactory.h>
#include <Functions/IFunction.h>
#include <Interpreters/Context.h>
#include <Interpreters/UserDefinedFunctionFactory.h>
#include <Storages/System/StorageSystemFunctions.h>
namespace DB
{
namespace
{
template <typename Factory>
void fillRow(MutableColumns & res_columns, const String & name, UInt64 is_aggregate, const Factory & f)
void fillRow(MutableColumns & res_columns, const String & name, UInt64 is_aggregate, const String & create_query, const Factory & f)
{
res_columns[0]->insert(name);
res_columns[1]->insert(is_aggregate);
res_columns[2]->insert(f.isCaseInsensitive(name));
if (f.isAlias(name))
res_columns[3]->insert(f.aliasTo(name));
else
if constexpr (std::is_same_v<Factory, UserDefinedFunctionFactory>)
{
res_columns[2]->insert(false);
res_columns[3]->insertDefault();
}
else
{
res_columns[2]->insert(f.isCaseInsensitive(name));
if (f.isAlias(name))
res_columns[3]->insert(f.aliasTo(name));
else
res_columns[3]->insertDefault();
}
res_columns[4]->insert(create_query);
}
}
@ -31,6 +43,7 @@ NamesAndTypesList StorageSystemFunctions::getNamesAndTypes()
{"is_aggregate", std::make_shared<DataTypeUInt8>()},
{"case_insensitive", std::make_shared<DataTypeUInt8>()},
{"alias_to", std::make_shared<DataTypeString>()},
{"create_query", std::make_shared<DataTypeString>()}
};
}
@ -40,14 +53,22 @@ void StorageSystemFunctions::fillData(MutableColumns & res_columns, ContextPtr,
const auto & function_names = functions_factory.getAllRegisteredNames();
for (const auto & function_name : function_names)
{
fillRow(res_columns, function_name, UInt64(0), functions_factory);
fillRow(res_columns, function_name, UInt64(0), "", functions_factory);
}
const auto & aggregate_functions_factory = AggregateFunctionFactory::instance();
const auto & aggregate_function_names = aggregate_functions_factory.getAllRegisteredNames();
for (const auto & function_name : aggregate_function_names)
{
fillRow(res_columns, function_name, UInt64(1), aggregate_functions_factory);
fillRow(res_columns, function_name, UInt64(1), "", aggregate_functions_factory);
}
const auto & user_defined_functions_factory = UserDefinedFunctionFactory::instance();
const auto & user_defined_functions_names = user_defined_functions_factory.getAllRegisteredNames();
for (const auto & function_name : user_defined_functions_names)
{
auto create_query = queryToString(user_defined_functions_factory.get(function_name));
fillRow(res_columns, function_name, UInt64(0), create_query, user_defined_functions_factory);
}
}
}

View File

@ -38,7 +38,7 @@ sudo -H pip install \
pytest \
pytest-timeout \
redis \
tzlocal \
tzlocal==2.1 \
urllib3 \
requests-kerberos \
dict2xml \

View File

@ -0,0 +1,39 @@
import pytest
from helpers.cluster import ClickHouseCluster
cluster = ClickHouseCluster(__file__)
instance = cluster.add_instance('instance')
@pytest.fixture(scope="module", autouse=True)
def started_cluster():
try:
cluster.start()
yield cluster
finally:
cluster.shutdown()
def test_access_rights_for_funtion():
create_function_query = "CREATE FUNCTION MySum AS (a, b) -> a + b"
instance.query("CREATE USER A")
instance.query("CREATE USER B")
assert "it's necessary to have grant CREATE FUNCTION ON *.*" in instance.query_and_get_error(create_function_query, user = 'A')
instance.query("GRANT CREATE FUNCTION on *.* TO A")
instance.query(create_function_query, user = 'A')
assert instance.query("SELECT MySum(1, 2)") == "3\n"
assert "it's necessary to have grant DROP FUNCTION ON *.*" in instance.query_and_get_error("DROP FUNCTION MySum", user = 'B')
instance.query("GRANT DROP FUNCTION ON *.* TO B")
instance.query("DROP FUNCTION MySum", user = 'B')
assert "Unknown function MySum" in instance.query_and_get_error("SELECT MySum(1, 2)")
instance.query("REVOKE CREATE FUNCTION ON *.* FROM A")
assert "it's necessary to have grant CREATE FUNCTION ON *.*" in instance.query_and_get_error(create_function_query, user = 'A')
instance.query("DROP USER IF EXISTS A")
instance.query("DROP USER IF EXISTS B")

View File

@ -0,0 +1,39 @@
import pytest
from helpers.cluster import ClickHouseCluster
cluster = ClickHouseCluster(__file__)
instance = cluster.add_instance('instance', stay_alive=True)
@pytest.fixture(scope="module", autouse=True)
def started_cluster():
try:
cluster.start()
yield cluster
finally:
cluster.shutdown()
def test_persistence():
create_function_query1 = "CREATE FUNCTION MySum1 AS (a, b) -> a + b"
create_function_query2 = "CREATE FUNCTION MySum2 AS (a, b) -> MySum1(a, b) + b"
instance.query(create_function_query1)
instance.query(create_function_query2)
assert instance.query("SELECT MySum1(1,2)") == "3\n"
assert instance.query("SELECT MySum2(1,2)") == "5\n"
instance.restart_clickhouse()
assert instance.query("SELECT MySum1(1,2)") == "3\n"
assert instance.query("SELECT MySum2(1,2)") == "5\n"
instance.query("DROP FUNCTION MySum2")
instance.query("DROP FUNCTION MySum1")
instance.restart_clickhouse()
assert "Unknown function MySum1" in instance.query_and_get_error("SELECT MySum1(1, 2)")
assert "Unknown function MySum2" in instance.query_and_get_error("SELECT MySum2(1, 2)")

View File

@ -1,5 +1,3 @@
import threading
import os
from tempfile import NamedTemporaryFile
@ -35,18 +33,21 @@ def started_cluster():
# NOTE this test have to be ported to Keeper
def test_secure_connection(started_cluster):
assert node1.query("SELECT count() FROM system.zookeeper WHERE path = '/'") == '2\n'
assert node2.query("SELECT count() FROM system.zookeeper WHERE path = '/'") == '2\n'
# no asserts, connection works
node1.query("SELECT count() FROM system.zookeeper WHERE path = '/'")
node2.query("SELECT count() FROM system.zookeeper WHERE path = '/'")
kThreadsNumber = 16
kIterations = 100
threads = []
for _ in range(kThreadsNumber):
threads.append(threading.Thread(target=(lambda:
[node1.query("SELECT count() FROM system.zookeeper WHERE path = '/'") for _ in range(kIterations)])))
threads_number = 16
iterations = 100
threads = []
for thread in threads:
thread.start()
# just checking for race conditions
for _ in range(threads_number):
threads.append(threading.Thread(target=(lambda:
[node1.query("SELECT count() FROM system.zookeeper WHERE path = '/'") for _ in range(iterations)])))
for thread in threads:
thread.join()
for thread in threads:
thread.start()
for thread in threads:
thread.join()

View File

@ -45,11 +45,13 @@ CREATE TABLE [] TABLE CREATE
CREATE VIEW [] VIEW CREATE
CREATE DICTIONARY [] DICTIONARY CREATE
CREATE TEMPORARY TABLE [] GLOBAL CREATE
CREATE FUNCTION [] DATABASE CREATE
CREATE [] \N ALL
DROP DATABASE [] DATABASE DROP
DROP TABLE [] TABLE DROP
DROP VIEW [] VIEW DROP
DROP DICTIONARY [] DICTIONARY DROP
DROP FUNCTION [] DATABASE DROP
DROP [] \N ALL
TRUNCATE ['TRUNCATE TABLE'] TABLE ALL
OPTIMIZE ['OPTIMIZE TABLE'] TABLE ALL

View File

@ -2,7 +2,7 @@ drop table if exists data_01730;
-- does not use 127.1 due to prefer_localhost_replica
select * from remote('127.{2..11}', view(select * from numbers(1e6))) group by number order by number limit 20 settings distributed_group_by_no_merge=0, max_memory_usage='100Mi'; -- { serverError 241 }
select * from remote('127.{2..11}', view(select * from numbers(1e6))) group by number order by number limit 20 settings distributed_group_by_no_merge=0, max_memory_usage='100Mi'; -- { serverError MEMORY_LIMIT_EXCEEDED }
-- no memory limit error, because with distributed_group_by_no_merge=2 remote servers will do ORDER BY and will cut to the LIMIT
select * from remote('127.{2..11}', view(select * from numbers(1e6))) group by number order by number limit 20 settings distributed_group_by_no_merge=2, max_memory_usage='100Mi';
@ -10,11 +10,12 @@ select * from remote('127.{2..11}', view(select * from numbers(1e6))) group by n
-- and the query with GROUP BY on remote servers will first do GROUP BY and then send the block,
-- so the initiator will first receive all blocks from remotes and only after start merging,
-- and will hit the memory limit.
select * from remote('127.{2..11}', view(select * from numbers(1e6))) group by number order by number limit 1e6 settings distributed_group_by_no_merge=2, max_memory_usage='100Mi'; -- { serverError 241 }
select * from remote('127.{2..11}', view(select * from numbers(1e6))) group by number order by number limit 1e6 settings distributed_group_by_no_merge=2, max_memory_usage='100Mi'; -- { serverError MEMORY_LIMIT_EXCEEDED }
-- with optimize_aggregation_in_order=1 remote servers will produce blocks more frequently,
-- since they don't need to wait until the aggregation will be finished,
-- and so the query will not hit the memory limit error.
create table data_01730 engine=MergeTree() order by key as select number key from numbers(1e6);
select * from remote('127.{2..11}', currentDatabase(), data_01730) group by key order by key limit 1e6 settings distributed_group_by_no_merge=2, max_memory_usage='100Mi', optimize_aggregation_in_order=1 format Null;
select * from remote('127.{2..11}', currentDatabase(), data_01730) group by key order by key limit 1e6 settings distributed_group_by_no_merge=2, max_memory_usage='40Mi', optimize_aggregation_in_order=1 format Null; -- { serverError MEMORY_LIMIT_EXCEEDED }
select * from remote('127.{2..11}', currentDatabase(), data_01730) group by key order by key limit 1e6 settings distributed_group_by_no_merge=2, max_memory_usage='50Mi', optimize_aggregation_in_order=1 format Null;
drop table data_01730;

View File

@ -0,0 +1,2 @@
24
1

View File

@ -0,0 +1,13 @@
CREATE FUNCTION 01856_test_function_0 AS (a, b, c) -> a * b * c;
SELECT 01856_test_function_0(2, 3, 4);
SELECT isConstant(01856_test_function_0(1, 2, 3));
DROP FUNCTION 01856_test_function_0;
CREATE FUNCTION 01856_test_function_1 AS (a, b) -> a || b || c; --{serverError 47}
CREATE FUNCTION 01856_test_function_1 AS (a, b) -> 01856_test_function_1(a, b) + 01856_test_function_1(a, b); --{serverError 600}
CREATE FUNCTION cast AS a -> a + 1; --{serverError 598}
CREATE FUNCTION sum AS (a, b) -> a + b; --{serverError 598}
CREATE FUNCTION 01856_test_function_2 AS (a, b) -> a + b;
CREATE FUNCTION 01856_test_function_2 AS (a) -> a || '!!!'; --{serverError 598}
DROP FUNCTION 01856_test_function_2;
DROP FUNCTION unknown_function; -- {serverError 46}
DROP FUNCTION CAST; -- {serverError 599}

View File

@ -484,6 +484,8 @@
"01804_dictionary_decimal256_type",
"01850_dist_INSERT_preserve_error", // uses cluster with different static databases shard_0/shard_1
"01821_table_comment",
"01856_create_function",
"01857_create_function_and_check_jit_compiled",
"01824_prefer_global_in_and_join",
"01870_modulo_partition_key",
"01870_buffer_flush", // creates database

View File

@ -77,6 +77,7 @@ Results for 2x AMD EPYC 7F72 3.2 Ghz (Total 96 Cores, IBM Cloud's Bare Metal Ser
Results for 2x AMD EPYC 7742 (128 physical cores, 1 TB DDR4-3200 RAM) from <b>Yedige Davletgaliyev</b> and <b>Nikita Zhavoronkov</b> of blockchair.com.<br/>
Results for ASUS A15 (Ryzen laptop) are from <b>Kimmo Linna</b>.<br/>
Results for MacBook Air M1 are from <b>Denis Glazachev</b>.<br/>
Results for Xeon Gold 6140 are from <b>Shiv Iyer</b> (ChistaDATA Labs).<br/>
</p>
</div>
</div>

View File

@ -0,0 +1,56 @@
[
{
"system": "Xeon Gold 6140",
"system_full": "Xeon Gold 6140, 1 socket, 32 threads, 64 GB RAM, vda",
"cpu_vendor": "Intel",
"cpu_model": "Xeon Gold 6140",
"time": "2021-08-24 00:00:00",
"kind": "server",
"result":
[
[0.002, 0.002, 0.001],
[0.017, 0.008, 0.008],
[0.029, 0.019, 0.018],
[0.043, 0.026, 0.025],
[0.101, 0.087, 0.084],
[0.213, 0.190, 0.187],
[0.017, 0.012, 0.012],
[0.036, 0.009, 0.009],
[0.264, 0.240, 0.245],
[0.313, 0.269, 0.270],
[0.139, 0.120, 0.123],
[0.149, 0.129, 0.134],
[0.411, 0.342, 0.342],
[0.490, 0.448, 0.445],
[0.433, 0.411, 0.403],
[0.459, 0.446, 0.445],
[1.223, 1.143, 1.133],
[0.723, 0.675, 0.685],
[2.121, 2.095, 2.049],
[0.058, 0.049, 0.030],
[0.560, 0.429, 0.430],
[0.653, 0.499, 0.501],
[1.379, 1.171, 1.152],
[1.153, 0.564, 0.562],
[0.200, 0.155, 0.152],
[0.150, 0.129, 0.127],
[0.192, 0.151, 0.152],
[0.583, 0.449, 0.459],
[0.752, 0.666, 0.656],
[1.073, 1.072, 1.073],
[0.373, 0.330, 0.336],
[0.525, 0.441, 0.441],
[3.795, 2.731, 2.908],
[1.918, 1.756, 1.787],
[1.828, 1.732, 1.735],
[0.725, 0.697, 0.711],
[0.188, 0.171, 0.179],
[0.072, 0.067, 0.065],
[0.077, 0.062, 0.062],
[0.435, 0.415, 0.391],
[0.027, 0.022, 0.020],
[0.023, 0.020, 0.014],
[0.014, 0.008, 0.003]
]
}
]