このトピックでは、ユーザー定義関数 (UDF) を記述および使用する方法について説明します。
背景情報
StarRocks 2.2.0 以降では、Java で記述された UDF がサポートされています。
StarRocks 3.0 以降では、グローバル UDF がサポートされています。CREATE、SHOW、DROP などの SQL 文に GLOBAL キーワードを追加するだけで、SQL 文がグローバルに有効になります。各データベースに対して SQL 文を 1 つずつ実行する必要はありません。ビジネスシナリオに基づいて UDF を構成し、StarRocks の関数機能を拡張できます。
StarRocks は、以下のタイプの UDF をサポートしています。
ユーザー定義スカラー関数 (スカラー UDF)
ユーザー定義集計関数 (UDAF)
ユーザー定義ウィンドウ関数 (UDWF)
ユーザー定義テーブル値関数 (UDTF)
前提条件
StarRocks の Java UDF 機能を使用する前に、以下の要件が満たされていることを確認する必要があります。
関連する Java プロジェクトを作成および記述するために、Apache Maven をインストールします。
サーバーに Java 開発キット (JDK) 1.8 をインストールします。
UDF 機能を有効にします。EMR Serverless StarRocks インスタンスの詳細ページの [インスタンス構成] タブの FE セクションで、
enable_udfパラメーターをTRUEに設定し、インスタンスを再起動して構成を有効にします。
データ型のマッピング
SQL 型 | Java 型 |
BOOLEAN | java.lang.Boolean |
TINYINT | java.lang.Byte |
SMALLINT | java.lang.Short |
INT | java.lang.Integer |
BIGINT | java.lang.Long |
FLOAT | java.lang.Float |
DOUBLE | java.lang.Double |
STRING/VARCHAR | java.lang.String |
UDF の開発と使用
Maven プロジェクトを作成し、対応する機能を Java で記述する必要があります。
手順 1: Maven プロジェクトを作成する
以下の基本ディレクトリ構造で Maven プロジェクトを作成します。
project
|--pom.xml
|--src
| |--main
| | |--java
| | |--resources
| |--test
|--target手順 2: 依存関係を追加する
pom.xml ファイルに以下の依存関係を追加します。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>udf</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.10</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
</project>手順 3: UDF を開発する
Java で UDF を開発する必要があります。
スカラー UDF を開発する
スカラー UDF を使用して、単一のデータ行に対して操作を実行し、単一の結果行を生成できます。スカラー UDF を使用してデータをクエリする場合、各データ行は最終的に結果セットに行ごとに表示されます。典型的なスカラー UDF には、UPPER、LOWER、ROUND、ABS などがあります。
次の例は、JSON データを抽出する方法を示しています。たとえば、JSON データのフィールドの値は、JSON オブジェクトではなく JSON 文字列である場合があります。この場合、JSON 文字列を抽出する場合、SQL 文で関数 GET_JSON_STRING のネスト呼び出し、つまり GET_JSON_STRING(GET_JSON_STRING('{"key":"{\\"k0\\":\\"v0\\"}"}', "$.key"), "$.k0") が必要になります。
SQL 文を簡略化するために、MY_UDF_JSON_GET('{"key":"{\\"k0\\":\\"v0\\"}"}', "$.key.k0") など、JSON 文字列を直接抽出する UDF を開発できます。
package com.starrocks.udf.sample;
import com.alibaba.fastjson.JSONPath;
public class UDFJsonGet {
public final String evaluate(String jsonObj, String key) {
if (obj == null || key == null) return null; // オブジェクトまたはキーが null の場合は null を返す
try {
// JSONPath ライブラリは、フィールドの値が JSON 文字列であっても完全に展開できます。
return JSONPath.read(jsonObj, key).toString();
} catch (Exception e) {
return null; // 例外が発生した場合は null を返す
}
}
}ユーザー定義クラスは、以下のメソッドを実装する必要があります。
メソッドのリクエストパラメーターとレスポンスパラメーターのデータ型は、手順 6 の CREATE FUNCTION 文で宣言されているものと同じである必要があり、メソッドのパラメーターと CREATE FUNCTION 文で宣言されているパラメーター間のデータ型マッピングは、「データ型のマッピング」に準拠している必要があります。
メソッド | 説明 |
TYPE1 evaluate(TYPE2, ...) |
|
UDAF を開発する
UDAF を使用して、複数のデータ行に対して操作を実行し、単一の結果行を生成できます。典型的な UDAF には、SUM、COUNT、MAX、MIN などがあります。これらの関数は、各 GROUP BY グループの複数のデータ行を集計し、単一の結果行を生成するために使用できます。
次の例では、関数 MY_SUM_INT を使用します。戻り値のデータ型が BIGINT である組み込み関数 SUM とは異なり、関数 MY_SUM_INT の入力パラメーターとレスポンスパラメーターのデータ型は INT です。
package com.starrocks.udf.sample;
public class SumInt {
public static class State {
int counter = 0; // カウンター変数
public int serializeLength() { return 4; } // シリアル化後の長さ
}
public State create() {
return new State(); // State オブジェクトを作成
}
public void destroy(State state) {
// State オブジェクトを破棄
}
public final void update(State state, Integer val) {
if (val != null) {
state.counter+= val; // 値をカウンターに追加
}
}
public void serialize(State state, java.nio.ByteBuffer buff) {
buff.putInt(state.counter); // カウンターをシリアル化
}
public void merge(State state, java.nio.ByteBuffer buffer) {
int val = buffer.getInt(); // バッファーから値を取得
state.counter += val; // 値をカウンターに追加
}
public Integer finalize(State state) {
return state.counter; // 最終結果を返す
}
}ユーザー定義クラスは、以下のメソッドを実装する必要があります。
メソッドの入力パラメーターとレスポンスパラメーターのデータ型は、手順 6 の CREATE FUNCTION 文で宣言されているものと同じである必要があり、メソッドのパラメーターと CREATE FUNCTION 文で宣言されているパラメーター間のデータ型マッピングは、「データ型のマッピング」に準拠している必要があります。
メソッド | 説明 |
State create() | State を作成します。 |
void destroy(State) | State を破棄します。 |
void update(State, ...) | State を更新します。最初のパラメーターは State で、残りのパラメーターは関数によって宣言された入力パラメーターです。単一の入力パラメーターまたは複数のパラメーターを指定できます。 |
void serialize(State, ByteBuffer) | State をシリアル化します。 |
void merge(State, ByteBuffer) | State をマージおよび逆シリアル化します。 |
TYPE finalize(State) | State を使用して関数の最終結果を取得します。 |
UDAF を開発する場合は、バッファークラス java.nio.ByteBuffer を使用して中間結果を保存および表現し、ローカル変数 serializeLength を使用して中間結果のシリアル化された長さを指定する必要があります。
クラスとローカル変数 | 説明 |
java.nio.ByteBuffer() | 中間結果を保存するために使用されるバッファークラス。中間結果は、異なる実行ノード間で転送されるときにシリアル化および逆シリアル化されるため、serializeLength を使用して中間結果のシリアル化された長さを指定する必要があります。 |
serializeLength() | シリアル化後の中間結果の長さ。単位: バイト。serializeLength のデータ型は INT に固定されています。例では、 |
シリアル化の実行時に java.nio.ByteBuffer に関連する以下の項目に注意してください。
ByteBuffer の remaining() メソッドを使用して State を逆シリアル化することはできません。
ByteBuffer で clear() メソッドを呼び出すことはできません。
serializeLengthの値は、実際に書き込まれるデータの長さと一致している必要があります。そうでない場合、シリアル化および逆シリアル化中に正しくない結果が得られる可能性があります。
UDWF を開発する
UDWF は、ユーザー定義ウィンドウ関数の略語です。一般的な UDAF とは異なり、UDWF は一連の行 (ウィンドウ) の計算を実行し、各行の結果を返すために使用できます。ほとんどの場合、UDWF には、データ行を複数のグループに分割するために使用できる OVER 句が含まれています。UDWF は、各データ行が存在するグループ (ウィンドウ) に基づいて計算を実行し、各行の結果を返します。
次の例では、関数 MY_WINDOW_SUM_INT を使用します。戻り値のデータ型が BIGINT である組み込み関数 SUM とは異なり、関数 MY_WINDOW_SUM_INT の入力パラメーターとレスポンスパラメーターのデータ型は INT にすることができます。
package com.starrocks.udf.sample;
public class WindowSumInt {
public static class State {
int counter = 0; // カウンター変数
public int serializeLength() { return 4; } // シリアル化後の長さ
@Override
public String toString() {
return "State{" +
"counter=" + counter +
'}';
}
}
public State create() {
return new State(); // State オブジェクトを作成
}
public void destroy(State state) {
// State オブジェクトを破棄
}
public void update(State state, Integer val) {
if (val != null) {
state.counter+=val; // 値をカウンターに追加
}
}
public void serialize(State state, java.nio.ByteBuffer buff) {
buff.putInt(state.counter); // カウンターをシリアル化
}
public void merge(State state, java.nio.ByteBuffer buffer) {
int val = buffer.getInt(); // バッファーから値を取得
state.counter += val; // 値をカウンターに追加
}
public Integer finalize(State state) {
return state.counter; // 最終結果を返す
}
public void reset(State state) {
state.counter = 0; // カウンターをリセット
}
public void windowUpdate(State state,
int peer_group_start, int peer_group_end,
int frame_start, int frame_end,
Integer[] inputs) {
for (int i = (int)frame_start; i < (int)frame_end; ++i) {
state.counter += inputs[i]; // ウィンドウ内の入力データを追加
}
}
}ユーザー定義クラスは、UDAF に必要なメソッドと windowUpdate() メソッドを実装する必要があります。UDWF は特別な UDAF です。
メソッドのリクエストパラメーターとレスポンスパラメーターのデータ型は、手順 6 の CREATE FUNCTION 文で宣言されているものと同じである必要があり、メソッドのパラメーターと CREATE FUNCTION 文で宣言されているパラメーター間のデータ型マッピングは、「データ型のマッピング」に準拠している必要があります。
実装する必要があるその他のメソッド
メソッド | 説明 |
| ウィンドウデータを更新します。UDWF の詳細については、「Window_function」をご参照ください。データ行を入力すると、対応するウィンドウ情報が取得され、中間結果が更新されます。
|
UDTF を開発する
UDTF は、データ行を読み取り、テーブルと見なすことができる複数の値を生成するために使用できます。UDTF は、行から列への変換を実装するためによく使用されます。
UDTF は、単一の列で複数の行を返すことのみをサポートしています。
次の例では、関数 MY_UDF_SPLIT を使用します。関数 MY_UDF_SPLIT は、スペースを区切り文字として使用して文字列を分割することをサポートしています。入力パラメーターとレスポンスパラメーターのデータ型は STRING です。
package com.starrocks.udf.sample;
public class UDFSplit{
public String[] process(String in) { // 入力文字列を処理するメソッド
if (in == null) return null; // 入力が null の場合は null を返す
return in.split(" "); // スペースで分割して配列を返す
}
}ユーザー定義クラスは、以下のメソッドを実装する必要があります。
メソッドのリクエストパラメーターとレスポンスパラメーターのデータ型は、手順 6 の CREATE FUNCTION 文で宣言されているものと同じである必要があり、メソッドのパラメーターと CREATE FUNCTION 文で宣言されているパラメーター間のデータ型マッピングは、「データ型のマッピング」に準拠している必要があります。
メソッド | 説明 |
TYPE[] process() |
|
手順 4: Java プロジェクトをパッケージ化する
次のコマンドを実行して、Java プロジェクトをパッケージ化します。
mvn packagetarget ディレクトリに、udf-1.0-SNAPSHOT.jar と udf-1.0-SNAPSHOT-jar-with-dependencies.jar の 2 つのファイルが生成されます。
手順 5: プロジェクトをアップロードする
ファイル udf-1.0-SNAPSHOT-jar-with-dependencies.jar を Object Storage Service (OSS) にアップロードし、JAR パッケージにパブリック読み取り権限を付与します。詳細については、「シンプルアップロード」および「バケット ACL」をご参照ください。
手順 6 では、フロントエンド (FE) ノードが UDF の JAR パッケージを検証し、検証値を計算します。バックエンド (BE) ノードは、UDF の JAR パッケージをダウンロードして実行します。
手順 6: StarRocks で UDF を作成する
StarRocks は、データベースレベルとグローバルレベルの両方の名前空間の UDF を提供します。
UDF の可視性に関する特別な要件がない場合は、グローバル UDF を作成できます。グローバル UDF を参照する場合、カタログとデータベースをプレフィックスとして使用する必要なく、関数名を直接呼び出すことができるため、アクセスがより便利になります。
UDF の可視性に関する特別な要件がある場合、または異なるデータベースに同じ名前の UDF を作成する場合は、データベースに UDF を作成できます。この場合、セッションが UDF が属するデータベース内にある場合は、関数名を直接呼び出すことができます。セッションが別のカタログまたはデータベース内にある場合は、
catalog.database.functionなど、カタログとデータベースをプレフィックスとして含める必要があります。
グローバル UDF を作成するには、システムレベルの CREATE GLOBAL FUNCTION 文を実行する権限が必要です。データベースレベルの UDF を作成するには、データベースレベルの CREATE FUNCTION 文を実行する権限が必要です。UDF を使用するには、UDF に対する USAGE 権限が必要です。必要な権限を付与する方法については、「GRANT」をご参照ください。
JAR パッケージをアップロードした後、ビジネス要件に基づいて StarRocks で対応する UDF を作成する必要があります。グローバル UDF を作成するには、SQL 文に GLOBAL キーワードを含めるだけで済みます。
構文
CREATE [GLOBAL][AGGREGATE | TABLE] FUNCTION function_name(arg_type [, ...])
RETURNS return_type
[PROPERTIES ("key" = "value" [, ...]) ]パラメーター
パラメーター | 必須 | 説明 |
GLOBAL | いいえ | グローバル UDF を作成する場合は、このキーワードを指定する必要があります。このパラメーターは、StarRocks 3.0 以降でサポートされています。 |
AGGREGATE | いいえ | UDAF または UDWF を作成する場合は、このキーワードを指定する必要があります。 |
TABLE | いいえ | UDTF を作成する場合は、このキーワードを指定する必要があります。 |
function_name | はい | 関数名。 |
arg_type | はい | 関数のパラメーターのデータ型。サポートされているデータ型の詳細については、「データ型のマッピング」をご参照ください。 |
return_type | はい | 関数の戻り値のデータ型。サポートされているデータ型の詳細については、「データ型のマッピング」をご参照ください。 |
properties | はい | 関数のプロパティ。作成するさまざまなタイプの UDF のプロパティを構成する必要があります。詳細と例については、以下のセクションをご参照ください。 |
スカラー UDF を作成する
次のコマンドを実行して、StarRocks にスカラー UDF を作成します。
CREATE [GLOBAL] FUNCTION MY_UDF_JSON_GET(string, string)
RETURNS string
PROPERTIES (
"symbol" = "com.starrocks.udf.sample.UDFJsonGet",
"type" = "StarrocksJar",
"file" = "http://<YourBucketName>.oss-cn-xxxx-internal.aliyuncs.com/<YourPath>/udf-1.0-SNAPSHOT-jar-with-dependencies.jar"
);パラメーター | 説明 |
symbol | UDF が属するプロジェクトのクラス名。クラス名は、 |
type | UDF のタイプ。値を |
file | UDF が属する JAR パッケージの HTTP パス。値を OSS の内部エンドポイントに対応する HTTP URL に設定します。形式は |
UDAF を作成する
次のコマンドを実行して、StarRocks に UDAF を作成します。
CREATE [GLOBAL] AGGREGATE FUNCTION MY_SUM_INT(INT)
RETURNS INT
PROPERTIES
(
"symbol" = "com.starrocks.udf.sample.SumInt",
"type" = "StarrocksJar",
"file" = "http://<YourBucketName>.oss-cn-xxxx-internal.aliyuncs.com/<YourPath>/udf-1.0-SNAPSHOT-jar-with-dependencies.jar"
);PROPERTIES のパラメーターの説明は、「スカラー UDF を作成する」のプロパティの説明と同じです。
UDWF を作成する
次のコマンドを実行して、手順 3 で使用した UDWF を StarRocks に作成します。
CREATE [GLOBAL] AGGREGATE FUNCTION MY_WINDOW_SUM_INT(Int)
RETURNS Int
PROPERTIES
(
"analytic" = "true",
"symbol" = "com.starrocks.udf.sample.WindowSumInt",
"type" = "StarrocksJar",
"file" = "http://<YourBucketName>.oss-cn-xxxx-internal.aliyuncs.com/<YourPath>/udf-1.0-SNAPSHOT-jar-with-dependencies.jar"
);analytic: 作成される関数がウィンドウ関数であるかどうかを指定します。値を true に設定します。残りのパラメーターの説明は、「スカラー UDF を作成する」で説明されているものと同じです。
UDTF を作成する
次のコマンドを実行して、手順 3 で使用した UDTF を StarRocks に作成します。
CREATE [GLOBAL] TABLE FUNCTION MY_UDF_SPLIT(string)
RETURNS string
PROPERTIES
(
"symbol" = "com.starrocks.udf.sample.UDFSplit",
"type" = "StarrocksJar",
"file" = "http://<YourBucketName>.oss-cn-xxxx-internal.aliyuncs.com/<YourPath>/udf-1.0-SNAPSHOT-jar-with-dependencies.jar"
);PROPERTIES のパラメーターの説明は、「スカラー UDF を作成する」のプロパティの説明と同じです。
手順 7: UDF を使用する
UDF を作成した後、開発した UDF をテストおよび使用できます。
スカラー UDF を使用する
次のコマンドを実行して、手順 6 で作成したスカラー UDF を使用します。
SELECT MY_UDF_JSON_GET('{"key":"{\\"in\\":2}"}', '$.key.in');UDAF を使用する
次のコマンドを実行して、手順 6 で作成した UDAF を使用します。
SELECT MY_SUM_INT(col1);UDWF を使用する
次のコマンドを実行して、手順 6 で作成した UDWF を使用します。
SELECT MY_WINDOW_SUM_INT(intcol)
OVER (PARTITION BY intcol2
ORDER BY intcol3
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
FROM test_basic;UDTF を使用する
次のコマンドを実行して、手順 6 で作成した UDTF を使用します。
-- 列 a、b、c1 を含むテーブル t1 が存在すると仮定します。
SELECT t1.a,t1.b,t1.c1 FROM t1;
> output:
1,2.1,"hello world"
2,2.2,"hello UDTF."
-- MY_UDF_SPLIT() 関数を使用します。
SELECT t1.a,t1.b, MY_UDF_SPLIT FROM t1, MY_UDF_SPLIT(t1.c1);
> output:
1,2.1,"hello"
1,2.1,"world"
2,2.2,"hello"
2,2.2,"UDTF."最初の
MY_UDF_SPLITは、関数MY_UDF_SPLITの呼び出し後に生成される列エイリアスです。AS t2(f1)メソッドを使用して、UDTF によって返されるテーブルのテーブルエイリアスと列エイリアスを指定することはできません。
UDF 情報を表示する
次のコマンドを実行して、UDF 情報を表示します。
SHOW [GLOBAL] FUNCTIONS;UDF を削除する
次のコマンドを実行して、指定した UDF を削除します。
DROP [GLOBAL] FUNCTION <function_name>(arg_type [, ...]);FAQ
Q: UDF を開発するときに静的変数を使用できますか? 異なる UDF の静的変数は互いに影響しますか?
A: UDF を開発するときに静的変数を使用できます。異なる UDF の静的変数は、UDF のクラス名が同じであっても互いに分離されており、互いに影響しません。