Функции#
Реализация плагина#
Фреймворк функций используется для реализации функций SQL. Трино включает в себя
количество встроенных функций. Для реализации новых функций вы можете
написать плагин, который возвращает одну или несколько функций изgetFunctions():
public class ExampleFunctionsPlugin
implements Plugin
{
@Override
public Set<Class<?>> getFunctions()
{
return ImmutableSet.<Class<?>>builder()
.add(ExampleNullFunction.class)
.add(IsNullFunction.class)
.add(IsEqualOrNullFunction.class)
.add(ExampleStringFunction.class)
.add(ExampleAverageFunction.class)
.build();
}
}
Обратите внимание, что класс ImmutableSet — это служебный класс из Guava.
Метод getFunctions() содержит все классы функций,
которые мы реализуем ниже в этом руководстве.
Полный пример в кодовой базе см. в модуле trino-ml для
функций машинного обучения или в модуле trino-teradata-functions для
функций, совместимых с Teradata, оба находятся в каталоге plugin
исходного кода Trino.
Реализация скалярной функции#
Функциональная структура использует аннотации для указания соответствующей информации.
о функциях, включая имя, описание, тип возвращаемого значения и параметр
типы. Ниже приведен пример функции, которая реализуетis_null:
public class ExampleNullFunction
{
@ScalarFunction("is_null", deterministic = true)
@Description("Returns TRUE if the argument is NULL")
@SqlType(StandardTypes.BOOLEAN)
public static boolean isNull(
@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice string)
{
return (string == null);
}
}
Функция is_null принимает один аргумент VARCHAR и возвращает
BOOLEAN, указывающий, был ли аргумент NULL. Обратите внимание, что аргумент
функции имеет тип Slice. VARCHAR использует Slice, что по сути
является оберткой над byte[], а не String, как собственным контейнерным типом.
Аргумент deterministic указывает, что функция не имеет побочных эффектов и
при последующих вызовах с теми же аргументами возвращает точно те же
значения.
В Trino детерминированные функции не зависят от какого-либо изменяющегося состояния.
и не изменяют никакого состояния. Флаг deterministic является необязательным
и по умолчанию равен true.
Например, функция shuffle() является недетерминированной, поскольку использует
случайные значения. С другой стороны, now() является детерминированной,
поскольку последующие вызовы в рамках одного запроса возвращают одну и ту же
метку времени.
Для любой функции с недетерминированным поведением требуется установить
deterministic = false,
чтобы избежать неожиданных результатов.
@SqlType:Аннотация
@SqlTypeиспользуется для объявления типа возвращаемого значения и типов аргументов. Обратите внимание, что тип возвращаемого значения и аргументы Java-кода должны соответствовать собственным контейнерным типам соответствующих аннотаций.@SqlNullable:Аннотация
@SqlNullableуказывает, что аргумент может бытьNULL. Без этой аннотации framework предполагает, что все функции возвращаютNULL, если любой из их аргументов равенNULL. При работе сType, у которого собственный контейнерный тип примитивный (например,BigintType), при использовании@SqlNullableприменяйте объектную обертку контейнерного типа. Метод должен быть помечен@SqlNullable, если он может вернутьNULL, когда аргументы не равны null.
Параметрические скалярные функции#
Скалярные функции, имеющие параметры типа, имеют некоторую дополнительную сложность. Чтобы наш предыдущий пример работал с любым типом, нам нужно следующее:
@ScalarFunction(name = "is_null")
@Description("Returns TRUE if the argument is NULL")
public final class IsNullFunction
{
@TypeParameter("T")
@SqlType(StandardTypes.BOOLEAN)
public static boolean isNullSlice(@SqlNullable @SqlType("T") Slice value)
{
return (value == null);
}
@TypeParameter("T")
@SqlType(StandardTypes.BOOLEAN)
public static boolean isNullLong(@SqlNullable @SqlType("T") Long value)
{
return (value == null);
}
@TypeParameter("T")
@SqlType(StandardTypes.BOOLEAN)
public static boolean isNullDouble(@SqlNullable @SqlType("T") Double value)
{
return (value == null);
}
// ...and so on for each native container type
}
@TypeParameter:Аннотация
@TypeParameterиспользуется для объявления параметра типа, который можно использовать в аргументах аннотации@SqlTypeили в типе возвращаемого значения функции. Ее также можно использовать для аннотирования параметра типаType. Во время выполнения движок привяжет конкретный тип к этому параметру.@OperatorDependencyможет использоваться, чтобы объявить необходимость дополнительной функции для работы с данным параметром типа. Например, следующая функция привяжется только к типам, для которых определена функция равенства:
@ScalarFunction(name = "is_equal_or_null")
@Description("Returns TRUE if arguments are equal or both NULL")
public final class IsEqualOrNullFunction
{
@TypeParameter("T")
@SqlType(StandardTypes.BOOLEAN)
public static boolean isEqualOrNullSlice(
@OperatorDependency(
operator = OperatorType.EQUAL,
returnType = StandardTypes.BOOLEAN,
argumentTypes = {"T", "T"}) MethodHandle equals,
@SqlNullable @SqlType("T") Slice value1,
@SqlNullable @SqlType("T") Slice value2)
{
if (value1 == null && value2 == null) {
return true;
}
if (value1 == null || value2 == null) {
return false;
}
return (boolean) equals.invokeExact(value1, value2);
}
// ...and so on for each native container type
}
Еще один пример скалярной функции#
Функция lowercaser принимает один аргумент VARCHAR и возвращает
VARCHAR, то есть аргумент, преобразованный в нижний регистр:
public class ExampleStringFunction
{
@ScalarFunction("lowercaser")
@Description("Converts the string to alternating case")
@SqlType(StandardTypes.VARCHAR)
public static Slice lowercaser(@SqlType(StandardTypes.VARCHAR) Slice slice)
{
String argument = slice.toStringUtf8();
return Slices.utf8Slice(argument.toLowerCase());
}
}
Обратите внимание, что для большинства распространенных строковых функций, включая преобразование строки в
строчные буквы, библиотека Slice также предоставляет реализации, которые работают напрямую
на основеbyte[], которые имеют гораздо лучшую производительность. Эта функция
не имеет@SqlNullableаннотации, означающие, что если аргументNULL,
результат будет автоматическиNULL(функция не будет вызвана).
Реализация функции агрегирования#
Функции агрегации используют структуру, аналогичную скалярным функциям, но немного сложнее.
AccumulatorState:Все функции агрегирования накапливают входные строки в объект состояния; этот объект должен реализовать
AccumulatorState. Для простых агрегаций просто продлеватьAccumulatorStateв новый интерфейс с геттерами и сеттерами вы хотите, и фреймворк сгенерирует все реализации и сериализаторы для вас. Если вам нужен более сложный объект состояния, вам понадобится реализоватьAccumulatorStateFactoryиAccumulatorStateSerializerи предоставить их черезAccumulatorStateMetadataаннотация.
Следующий код реализует функцию агрегирования.avg_doubleкоторый вычисляет
среднее значениеDOUBLEстолбец:
@AggregationFunction("avg_double")
public class AverageAggregation
{
@InputFunction
public static void input(
LongAndDoubleState state,
@SqlType(StandardTypes.DOUBLE) double value)
{
state.setLong(state.getLong() + 1);
state.setDouble(state.getDouble() + value);
}
@CombineFunction
public static void combine(
LongAndDoubleState state,
LongAndDoubleState otherState)
{
state.setLong(state.getLong() + otherState.getLong());
state.setDouble(state.getDouble() + otherState.getDouble());
}
@OutputFunction(StandardTypes.DOUBLE)
public static void output(LongAndDoubleState state, BlockBuilder out)
{
long count = state.getLong();
if (count == 0) {
out.appendNull();
}
else {
double value = state.getDouble();
DOUBLE.writeDouble(out, value / count);
}
}
}
Среднее значение состоит из двух частей: суммыDOUBLEв каждой строке столбца
иLONGподсчет количества просмотренных строк.LongAndDoubleStateэто интерфейс
который простираетсяAccumulatorState:
public interface LongAndDoubleState
extends AccumulatorState
{
long getLong();
void setLong(long value);
double getDouble();
void setDouble(double value);
}
Как говорилось выше, для простогоAccumulatorStateобъекты, достаточно
просто определите интерфейс с помощью геттеров и сеттеров, а также структуру
сгенерирует для вас реализацию.
Углубленный взгляд на различные аннотации, имеющие отношение к написанию агрегата. функция следующая:
@InputFunction:Аннотация
@InputFunctionобъявляет функцию, принимающую входные строки и сохраняющую их вAccumulatorState. Как и в скалярных функциях, аргументы должны быть аннотированы@SqlType. Обратите внимание, что, в отличие от скалярного примера выше, где дляVARCHARиспользуетсяSlice, здесь для входного аргумента используется примитивный типdouble. В этом примере входная функция просто отслеживает текущее число строк (черезsetLong()) и текущую сумму (черезsetDouble()).@CombineFunction:Аннотация
@CombineFunctionобъявляет функцию, используемую для объединения двух объектов состояния. Эта функция используется для объединения всех состояний частичной агрегации. Она принимает два объекта состояния и объединяет результаты в первый (в примере выше — просто складывает их).@OutputFunction:@OutputFunction— последняя функция, вызываемая при вычислении агрегации. Она принимает итоговый объект состояния (результат объединения всех частичных состояний) и записывает результат вBlockBuilder.Где происходит сериализация, и что такое
GroupedAccumulatorState?
@InputFunctionобычно запускается на другом worker-узле, чем@CombineFunction, поэтому объекты состояния сериализуются и передаются между этими worker-узлами framework-ом агрегации.GroupedAccumulatorStateиспользуется при выполнении агрегацииGROUP BY, и реализация будет автоматически сгенерирована, если вы не укажетеAccumulatorStateFactory
Устаревшая функция#
Аннотация @Deprecated должна использоваться для любой функции, которую
больше не следует использовать. Эта аннотация заставляет Trino генерировать
предупреждение всякий раз, когда SQL-операторы используют устаревшую функцию.
Когда функция устарела, @Description нужно заменить примечанием об
устаревании и функции-замене:
public class ExampleDeprecatedFunction
{
@Deprecated
@ScalarFunction("bad_function")
@Description("(DEPRECATED) Use good_function() instead")
@SqlType(StandardTypes.BOOLEAN)
public static boolean bad_function()
{
return false;
}
}