関数¶
プラグインの実装¶
関数フレームワークは、SQL 関数を実装するために使用されます。Presto には、多数の組み込み関数が含まれています。新しい関数を実装するには、getFunctions()
から 1 つ以上の関数を返すプラグインを作成できます。
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()
メソッドには、このチュートリアルで以下に実装する関数のすべてのクラスが含まれています。
コードベースの完全な例については、Presto ソースのルートにある、機械学習関数用の presto-ml
モジュール、または Teradata 互換関数用の presto-teradata-functions
モジュールのいずれかを参照してください。
スカラー関数の実装¶
関数フレームワークは、名前、説明、戻り値の型、パラメーターの型など、関数に関する関連情報を示すためにアノテーションを使用します。以下は、is_null
を実装するサンプル関数です。
public class ExampleNullFunction
{
@ScalarFunction("is_null", calledOnNullInput = 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
引数を取り、引数が NULL
であるかどうかを示す BOOLEAN
を返します。関数の引数が Slice
型であることに注意してください。VARCHAR
はネイティブコンテナ型として String
ではなく、本質的に byte[]
のラッパーである Slice
を使用します。
@SqlType
:@SqlType
アノテーションは、戻り値の型と引数の型を宣言するために使用されます。Java コードの戻り値の型と引数が、対応するアノテーションのネイティブコンテナ型と一致する必要があることに注意してください。@SqlNullable
:@SqlNullable
アノテーションは、引数がNULL
になる可能性があることを示します。このアノテーションがない場合、フレームワークは、いずれかの引数がNULL
の場合、すべての関数がNULL
を返すと想定します。BigintType
のように、プリミティブなネイティブコンテナ型を持つType
を使用する場合は、@SqlNullable
を使用するときにネイティブコンテナ型のオブジェクトラッパーを使用します。引数が null でない場合にNULL
を返すことができる場合は、メソッドに@SqlNullable
のアノテーションを付ける必要があります。
パラメトリック スカラー関数¶
型パラメーターを持つスカラー関数には、追加の複雑さがあります。前の例を任意の型で動作させるには、次のものが必要です。
@ScalarFunction(name = "is_null", calledOnNullInput = true)
@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
型のパラメーターにアノテーションを付けるためにも使用できます。実行時に、エンジンはこのパラメーターに具体的な型をバインドします。オプションで、@TypeParameter
にboundedBy
型クラスを提供することにより、型パラメーターを特定の型の派生型に制限できます。@OperatorDependency
は、指定された型パラメーターを操作するための追加の関数が必要であることを宣言するために使用できます。たとえば、次の関数は、等しい関数が定義されている型にのみバインドします。
@ScalarFunction(name = "is_equal_or_null", calledOnNullInput = true)
@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
になります(関数は呼び出されません)。
Codegen スカラー関数の実装¶
スカラー関数はバイトコードで実装することもでき、@TypeParameter
に従って関数を特殊化および最適化できます。
@CodegenScalarFunction
:@CodegenScalarFunction
アノテーションは、バイトコードで実装されるスカラー関数を宣言するために使用されます。@SqlType
アノテーションは、戻り値の型を宣言するために使用されます。これは、@SqlType
アノテーションも持つパラメーターとしてType
を取ります。戻り値の型は、codegen 関数メソッドであるMethodHandle
です。
public class CodegenArrayLengthFunction
{
@CodegenScalarFunction("array_length", calledOnNullInput = true)
@SqlType(StandardTypes.INTEGER)
@TypeParameter("K")
public static MethodHandle arrayLength(@SqlType("array(K)") Type arr)
{
CallSiteBinder binder = new CallSiteBinder();
ClassDefinition classDefinition = new ClassDefinition(a(Access.PUBLIC, FINAL), makeClassName("ArrayLength"), type(Object.class));
classDefinition.declareDefaultConstructor(a(PRIVATE));
Parameter inputBlock = arg("inputBlock", Block.class);
MethodDefinition method = classDefinition.declareMethod(a(Access.PUBLIC, STATIC), "array_length", type(Block.class), ImmutableList.of(inputBlock));
BytecodeBlock body = method.getBody();
body.append(inputBlock.invoke("getPositionCount", int.class).ret());
Class<?> clazz = defineClass(classDefinition, Object.class, binder.getBindings(), CodegenArrayLengthFunction.class.getClassLoader());
return new methodHandle(clazz, "array_length", Block.class), Optional.of();
}
}
集計関数の実装¶
集計関数はスカラー関数と同様のフレームワークを使用しますが、少し複雑です。
AccumulatorState
:すべてのアグリゲーション関数は、入力行を状態オブジェクトに累積します。このオブジェクトは
AccumulatorState
を実装する必要があります。単純なアグリゲーションの場合、必要なゲッターとセッターを持つ新しいインターフェースにAccumulatorState
を拡張するだけで、フレームワークがすべての実装とシリアライザーを生成します。より複雑な状態オブジェクトが必要な場合は、AccumulatorStateFactory
とAccumulatorStateSerializer
を実装し、AccumulatorStateMetadata
アノテーションを介して提供する必要があります。
以下のコードは、DOUBLE
列の平均を計算するアグリゲーション関数 avg_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
カウントの2つの部分があります。LongAndDoubleState
は、AccumulatorState
を拡張するインターフェースです。
public interface LongAndDoubleState
extends AccumulatorState
{
long getLong();
void setLong(long value);
double getDouble();
void setDouble(double value);
}
上記のように、単純な AccumulatorState
オブジェクトの場合、ゲッターとセッターを持つインターフェースを定義するだけで十分であり、フレームワークが実装を生成します。
アグリゲーション関数の記述に関連するさまざまなアノテーションの詳細な説明を以下に示します。
@InputFunction
:@InputFunction
アノテーションは、入力行を受け取り、AccumulatorState
に保存する関数を宣言します。スカラ関数と同様に、引数に@SqlType
をアノテーションする必要があります。上記のスカラの例ではSlice
がVARCHAR
を保持するために使用されるのとは異なり、入力の引数にはプリミティブなdouble
型が使用されることに注意してください。この例では、入力関数は、実行中の行数(setLong()
を介して)と、実行中の合計(setDouble()
を介して)を追跡するだけです。@CombineFunction
:@CombineFunction
アノテーションは、2つの状態オブジェクトを結合するために使用される関数を宣言します。この関数は、すべての部分的なアグリゲーション状態をマージするために使用されます。2つの状態オブジェクトを取り、結果を最初のオブジェクトにマージします(上記の例では、単にそれらを加算するだけです)。@OutputFunction
:@OutputFunction
は、アグリゲーションの計算時に最後に呼び出される関数です。最終的な状態オブジェクト(すべての部分状態をマージした結果)を取得し、結果をBlockBuilder
に書き込みます。シリアライズはどこで行われるのか、そして
GroupedAccumulatorState
とは何ですか?@InputFunction
は通常、@CombineFunction
とは異なるワーカーで実行されるため、状態オブジェクトはアグリゲーションフレームワークによってシリアライズされ、これらのワーカー間で転送されます。GroupedAccumulatorState
は、GROUP BY
アグリゲーションを実行するときに使用され、AccumulatorStateFactory
を指定しない場合は、実装が自動的に生成されます。
高度なユースケース¶
生のブロック入力¶
スカラ関数とアグリゲーション関数のアノテーションの両方で、ネイティブ型を操作するメソッドを定義できます。Javaでは、これらのネイティブ型は boolean
、Slice
、および long
です。パラメーター化された実装またはパラメトリック型の場合、標準のJava型は入力データを表現できないため使用できません。
任意の型を受け入れることができるメソッドハンドルを定義するには、@BlockPosition
を @BlockIndex
パラメーターと組み合わせて使用します。@SqlNullable
アノテーションと同様に、ブロック位置が NULL
の場合にのみ関数を呼び出すことを示すには、@NullablePosition
アノテーションを使用します。
これは、スカラ関数とアグリゲーション関数の実装の両方で機能します。
@ScalarFunction("example")
public static Block exampleFunction(
@BlockPosition @NullablePosition @SqlType("array(int)") Block block,
@BlockIndex int index) { /* ...implementation */ }
@BlockPosition
を使用したジェネリック型の適用¶
@BlockPosition
構文を使用する関数シグネチャは、関数が @TypeParameter
アノテーションで定義されている場合、ジェネリック型を操作できます。@BlockPosition
引数を追加の @SqlType("T")
アノテーションで拡張して、ジェネリック型に対応する引数を受け入れることを示します。これは、スカラ関数とアグリゲーション関数の実装の両方で機能します。
@ScalarFunction("example")
@TypeParameter("T")
public static Block exampleFunction(
@BlockPosition @SqlType("T") Block block,
@BlockIndex int index) { /* ...implementation */ }
@TypeParameter
を使用したジェネリック型の取得¶
実装が型固有のロジックを実行できるようにするには、関数の引数リストの先頭に @TypeParameter
アノテーションを追加します。関数のシグネチャの最初の引数として、Type
に型付けされ、@TypeParameter
でアノテーションされた引数を追加して、Type
にアクセスできるようにします。これは、スカラ関数とアグリゲーション関数の両方で機能します。
@ScalarFunction("example")
@TypeParameter("T")
public static Block exampleFunction(
@TypeParameter("T") Type type,
@BlockPosition @SqlType("T") Block block,
@BlockIndex int index) { /* ...implementation */ }