---
metadata:
  - name: generator
    content: Diplodoc Platform v5.50.6
alternate:
  - https://ytsaurus.tech/docs/en/yql/udf/cpp.md
  - https://ytsaurus.tech/docs/ru/yql/udf/cpp.md
---
> **Documentation Index:** Fetch the complete configuration index at https://ytsaurus.tech/docs/ru/llms.txt

# Разработка UDF на C++

## Введение
На данный момент в YQL поддерживаются пользовательские функции (UDF) на C++, Python. Ниже речь пойдет только о C++, про [Python](https://ytsaurus.tech/docs/ru/yql/udf/python.md) есть отдельная статья.

На сервисе YQL уже установлен набор базовых функций — рекомендуем ознакомиться со [списком](https://ytsaurus.tech/docs/ru/yql/udf/list/index.md).

## Терминология

* **Вызываемое значение** <span style="color: gray;">callable</span> — сущность с фиксированной, строго типизированной сигнатурой, которая при вызове способна вернуть результат по своим аргументам.
* **Пользовательская функция** <span style="color: gray;">(function, user defined function, UDF)</span> — фабрика по созданию вызываемых значений.
* **Модуль** <span style="color: gray;">(module)</span> — набор тесно связанных функций с общим префиксом; все функции модуля распространяются вместе в одной разделяемой библиотеке (`.so`). Если модуль специфичен для какого-либо проекта, то крайне желательно отразить это в его имени в виде префикса.
* **Регистратор** <span style="color: gray;">(registrator)</span> — интерфейс для внесения модулей и функций в общий реестр.
* **Группа модулей** <span style="color: gray;">(group)</span> — группа модулей, принадлежащих структурному подразделению или крупному проекту; служит лишь для логического разделения исходного кода (см. следующий раздел), из YQL/SQL не видна.
* **Конфигурационные параметры** — опциональный механизм для настройки пользовательской функции (фабрики вызываемых значений). Позволяют изменить поведение вызываемых значений без перекомпиляции:
    * `Type Config`, `User Type` — по ним функция должна определить и объявить сигнатуру вызываемого значения; используется при построении графа вычислений. Должны быть известны в момент компиляции, поэтому `Type Config` обязательно должен быть атомом, а `User Type` — некоторым типом, например полученным с помощью операции TypeOf.
    * `Run Config` — по нему функция должна вернуть вызываемое значение с объявленной ранее сигнатурой. Тип значения Run Config указывается в коде UDF при объявлении сигнатуры вызываемого значения. Отличается от обычного аргумента тем, что он передается при инициализации, а не при каждом вызове.


## Примеры
Примеры готовых модулей можно посмотреть здесь: [https://github.com/ytsaurus/ytsaurus/tree/main/yql/essentials/udfs](https://github.com/ytsaurus/ytsaurus/tree/main/yql/essentials/udfs).

Совсем простые примеры см. в поддиректории [examples](https://github.com/ytsaurus/ytsaurus/tree/main/yql/essentials/udfs/examples), приближенные к реальности — в остальных, например [common](https://github.com/ytsaurus/ytsaurus/tree/main/yql/essentials/udfs/common).

Перед изучением следующего раздела рекомендуем открыть один или несколько примеров, так как в разделе даны только пояснения к коду, но полные листинги не приводятся.


## Интерфейсы (С++)

### TUnboxedValue (udf\_value.h)

Класс-обертка вокруг типизированных данных. Предоставляет набор конструкторов для создания сложных типов данных из простых типов данных и набор хелперов для работы со сложными типами данных.

Числа, интервалы, даты, включая даты с временной зоной и короткие строки до 14 байт хранятся по значению, метод `IsEmbedded()` для них вернет `true`. Строки длиннее 14 байт хранятся по указателю.

### TUnboxedValuePod(udf\_value.h)

Является базовым классом для TUnboxedValue. Не
производит автоматический подсчет ссылок на свое значение, в отличие от своего наследника, который осуществляет это в конструкторе и деструкторе - ключевым моментом здесь является отсутсвие конструкторов и деструкторов у класса TUnboxedValuePod. Реализует всю логику работы с
типизированными данными.

### IValueBuilder (udf\_value\_builder.h)

Метод Run должен возвращать TUnboxedValue. Для числовых типов можно воспользоваться конструктором типа, например `return TUnboxedValue<ui32>(123);`. Для создания TUnboxedValue с более сложным содержимым в метод Run передается IValueBuilder. На нем можно вызвать один из методов `New***` для получения интерфейса `I***ValueBuilder`, где *** — одно из String/Struct/Dict/пр. С его помощью можно добавить специфичных для каждого типа элементов и в конце вызвать `Build` для получения заполненного TUnboxedValue.
Есть вариант создать Struct/Tuple с преаллокацией для хранения содержимого, для этого надо вызвать соответствующий метод `New***(const TType* type, TUnboxedValue*& items)` и заполнить элементы items на месте.


См. [yql/essentials/udfs/examples/structs](https://github.com/ytsaurus/ytsaurus/tree/main/yql/essentials/udfs/examples/structs) для примера со структурами.

### TBoxedValue (udf\_value.h)
Класс типичного UDF наследуется от TBoxedValue и помимо конструктора переопределяет метод Run, в котором должна размещаться основная логика по обработке одного конкретного вызова. В аргументы Run передается интерфейс IValueBuilder, о котором ниже, а также сами переданные аргументы в виде массива TUnboxedValue.

В конструкторе можно сохранить какое-то состояние в приватные переменные класса, таким образом конкретный объект нашего класса-наследника TBoxedValue превращается в вызываемое значение с потенициально уникальным поведением.

Для простых UDF (без использования YQL-структур, C++ шаблонов, наследования и т. п.), можно воспользоваться макросом `SIMPLE_UDF(udfName, signature)` (находится в `udf_helpers.h`). `signature` задается в виде `returnType(arg1, arg2, ..., argN)`.

Пример использования:
``` cpp
SIMPLE_UDF(TAbs, ui64(i64)) {
    i64 input = args[0].Get<i64>();
    ui64 result = static_cast<ui64>(input >= 0 ? input : -input);
    return TUnboxedValue(result);
}
```
Функция будет зарегистрирована под именем `Abs`, аргумент типа `i64`, результат — `ui64`.

Пример, когда успех не гарантируется:
``` cpp
SIMPLE_UDF(TParseDuration, TOptional<ui64>(char*)) {
    TDuration result;
    auto input = args[0].AsStringRef();
    bool success = TDuration::TryParse(input, result);
    return success ? TUnboxedValue::Optional(result.Seconds()) : TUnboxedValue();
}
```
Под `Optional (type)` ([тип, допускающий значение NULL](https://ytsaurus.tech/docs/ru/yql/types/optional.md)) подразумевается возможность отсутствия значения указанного типа, например в случае какой-то ошибки. Из SQL такого типа наличие в результате  можно проверить с помощью конструкции `IS [NOT] NULL`.

### IUdfModule (udf\_registrator.h)

Для типовых модулей можно воспользоваться макросом `SIMPLE_MODULE(moduleName, udfs...)` (находится в `udf_helpers.h`):

* Первым аргументом — имя класса-модуля, при регистрации префикс `T` и суффикс `Module` будут отброшены.
* Далее — список из N входящих в него UDF, при регистрации которых будет отброшен только префикс `T`.

Таким образом, в примере `SIMPLE_MODULE(TFooModule, TBar, TBaz)` после регистрации (о которой ниже) из YQL будет доступен модуль `Foo` с двумя UDF — `Bar` и `Baz`.

#### Реализация модуля вручную

Класс типичного модуля наследуется от IUdfModule и должен реализовать два метода:

* `GetAllFunctionNames` — в переданном `IFunctionNamesSink` нужно вызвать метод `Add(name)` для каждой из UDF, входящей в модуль.
* `BuildFunctionTypeInfo` — по переданному имени UDF и Type Config нужно на переданном `IFunctionTypeInfoBuilder` вызвать:
  * `Args()->Add<ui32>()[->Add(foo)->Add...].Done()`:
    * для указания аргументов и их типов, для чисел и строк тип аргумента можно указать шаблоном;
    * сложный тип вроде Struct/Dict/etc можно описать с помощью одноименного метода `IFunctionTypeInfoBuilder`, затем передав его в аргумент `Add`, пример:
``` cpp
ui32 a = 0;
ui32 b = 0;
auto foo = builder.Struct()->AddField<char*>("A", &a).AddField<ui32>("B", &b).Build();
builder.Args()->Add(foo);
```
  * При описании типов наподобие `builder.List()->...` следует максимально внимательно копировать чужие примеры, так как часто вызов цепочки методов билдера в неправильной последовательности или с неправильными аргументами нормально компилируется, но в рантайме падает в корку с нетривиальным бектрейсом. Пример такой цепочки:

``` cpp
// udf returns list of lists of char*
auto retType = buider.List()->Item(builder.List()->Item<char*>().Build()).Build(); // Correct
auto retType = buider.List()->Item<char*>().Build(); // Wrong type, coredump with backtrace in casting to string deep in validating
auto retType = buider.List()->Build(); // No type at all, coredump with attempt to dereference nullptr deep in type machinery
builder.Returns(retType);
```
  * Также в аргументах передается битовая маска `ui32 flags`, в которой на момент написания может присутствовать только `(cpp)TFlags::TypesOnly`, при наличии которого не обязательно вызвать `builder.Implementation(new TMyUdf);` для UDF, соответствующей переданному имени.

### IRegistrator (udf\_registrator.h)

Для упрощенной регистрации модуля в системе рекомендуется использовать макрос:
``` cpp
REGISTER_MODULES(
    TFooModule,
    TBarModule<true>,
    TBarModule<false>
)
```
Имя модуля в этом случае берется из метода `Name()`, который должен вернуть `TStringRef` по значению, макрос `SIMPLE_MODULE` создает его автоматически.

#### Альтернативный вариант для продвинутых пользователей

Нужно объявить функцию `Register` следующего вида и в переданном интерфейсе зарегистрировать модуль(-и), а также экспортировать версию ABI:
``` cpp
extern "C" YQL_UDF_API void Register(NKikimr::NUdf::IRegistrator& registrator, ui32 flags)
{
     registrator->AddModule("Foo", new NDetail::TFooModule(flags));
}

extern "C" YQL_UDF_API ui32 AbiVersion()
{
    return CurrentAbiVersion();
}
```
Список флагов перечислен в `IUdfModule::TFlags`. На момент написания реализован только `TypesOnly`, который используется как указание на то, что реальных вычислений пока производиться не будет, так как ещё идет построение графа вычислений, и на данном этапе нужны только сигнатуры функций.

### Обработка исключений/ошибок

Чтобы возвращать пользователю фатальные исключения, нужно использовать следующую конструкцию:

``` cpp
try {
   ...
} catch (const std::exception& e) {
    UdfTerminate(e.what());
}
```

Вместо `e.what()` можно написать человекочитаемое описание ошибки, если это возможно.

{% note warning "Внимание" %}

Завершать выполнение программы целиком с помощью `abort`, `exit`, `Y_ABORT_UNLESS`, `Y_ABORT` и подобных механизмов из кода UDF крайне не рекомендуется, так как это ограничит область её применения только теми окружениями, где под каждый расчет создается отдельный процесс. Реализация `UdfTerminate` знает о текущем окружении и вызовет ожидаемый им механизм обработки ошибок.

{% endnote %}


