本文為您介紹如何編寫和使用UDF。
背景資訊
自2.2.0版本起,StarRocks支援使用Java語言編寫使用者定義函數(User Defined Function,簡稱UDF)。
自3.0版本起,StarRocks支援Global UDF,您只需要在相關的SQL語句(CREATE/SHOW/DROP)中加上GLOBAL關鍵字,該語句即可全域生效,無需逐個為每個資料庫執行此語句。您可以根據業務情境開發自訂函數,擴充StarRocks的函數能力。
目前StarRocks支援的UDF包括:
使用者自訂純量涵式(Scalar UDF)
使用者自訂彙總函式(User Defined Aggregation Function,UDAF)
使用者自訂視窗函數(User Defined Window Function,UDWF)
使用者自訂表格格函數(User Defined Table Function,UDTF)
前提條件
使用StarRocks的Java UDF功能前,您需要:
安裝Apache Maven以建立並編寫相關Java專案。
在伺服器上安裝JDK 1.8。
開啟UDF功能。在執行個體配置頁面,設定FE配置項
enable_udf為TRUE,並重啟執行個體使配置項生效。
類型映射關係
SQL TYPE | Java TYPE |
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語言編寫相應功能。
步驟一:建立Maven專案
建立Maven專案,專案的基本目錄結構如下。
project
|--pom.xml
|--src
| |--main
| | |--java
| | |--resources
| |--test
|--target步驟二:添加依賴
在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>步驟三:開發UDF
您需要使用Java語言開發相應UDF。
開發Scalar UDF
Scalar UDF,即使用者自訂純量涵式,可以對單行資料進行操作,輸出單行結果。當您在查詢時使用Scalar 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語句,您可以開發一個UDF,直接提取JSON字串,例如:MY_UDF_JSON_GET('{"key":"{\\"k0\\":\\"v0\\"}"}', "$.key.k0")。
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;
try {
// JSONPath庫可以全部展開,即使某個欄位的值是JSON格式的字串
return JSONPath.read(jsonObj, key).toString();
} catch (Exception e) {
return null;
}
}
}使用者自訂類必須實現如下方法。
方法中請求參數和返回參數的資料類型,需要和步驟六中的CREATE FUNCTION語句中聲明的相同,且兩者的類型映射關係需要符合類型映射關係。
方法 | 含義 |
TYPE1 evaluate(TYPE2, ...) |
|
開發UDAF
UDAF,即使用者自訂的彙總函式,對多行資料進行操作,輸出單行結果。典型的彙總函式包括SUM、COUNT、MAX、MIN,這些函數對於每個GROUP BY分組中多行資料進行彙總後,只輸出一行結果。
以下樣本以MY_SUM_INT函數為例進行說明。與內建函數SUM(傳回值為BIGINT類型)區別在於,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();
}
public void destroy(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;
}
}使用者自訂類必須實現如下方法。
方法中傳入參數和返回參數的資料類型,需要和步驟六中的CREATE FUNCTION語句中聲明的相同,且兩者的類型映射關係需要符合類型映射關係。
方法 | 含義 |
State create() | 建立State。 |
void destroy(State) | 銷毀State。 |
void update(State, ...) | 更新State。其中第一個參數是State,其餘的參數是函式宣告的輸入參數,可以為1個或多個。 |
void serialize(State, ByteBuffer) | 序列化State。 |
void merge(State, ByteBuffer) | 合并State和還原序列化State。 |
TYPE finalize(State) | 通過State擷取函數的最終結果。 |
並且,開發UDAF函數時,您需要使用緩衝區類java.nio.ByteBuffer和局部變數serializeLength,用於儲存和表示中間結果,指定中間結果的序列化長度。
類和局部變數 | 說明 |
java.nio.ByteBuffer() | 緩衝區類,用於儲存中間結果。由於中間結果在不同執行節點間傳輸時,會進行序列化和還原序列化,因此還需要使用serializeLength指定中間結果序列化後的長度。 |
serializeLength() | 中間結果序列化後的長度,單位為Byte。serializeLength的資料類型固定為INT。例如,樣本中 |
java.nio.ByteBuffer序列化相關事項:
不支援依賴ByteBuffer的remaining()方法來還原序列化State。
不支援對ByteBuffer調用clear()方法。
serializeLength需要與實際寫入資料的長度保持一致,否則序列化和還原序列化過程中會造成結果錯誤。
開發UDWF
UDWF,即使用者自訂視窗函數。跟普通彙總函式不同的是,視窗函數針對一組行(一個視窗)計算值,並為每行返回一個結果。一般情況下,視窗函數包含OVER子句,將資料行拆分成多個分組,視窗函數基於每一行資料所在的組(一個視窗)進行計算,並為每行返回一個結果。
以下樣本以MY_WINDOW_SUM_INT函數為例進行說明。與內建函數SUM(傳回型別為BIGINT)區別在於,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();
}
public void destroy(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()方法。
方法中請求參數和返回參數的資料類型,需要和步驟六中的CREATE FUNCTION語句中聲明的相同,且兩者的類型映射關係需要符合類型映射關係。
需要額外實現的方法
方法 | 含義 |
| 更新視窗資料。視窗函數的詳細說明,請參見Window_function。輸入每一行資料,都會擷取到對應視窗資訊來更新中間結果。
|
開發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;
return in.split(" ");
}
}使用者自訂類必須實現如下方法。
方法中請求參數和返回參數的資料類型,需要和步驟六中的CREATE FUNCTION語句中聲明的相同,且兩者的類型映射關係需要符合類型映射關係。
方法 | 含義 |
TYPE[] process() |
|
步驟四:打包Java專案
通過以下命令打包Java專案。
mvn packagetarget目錄下會產生兩個檔案:udf-1.0-SNAPSHOT.jar和udf-1.0-SNAPSHOT-jar-with-dependencies.jar。
步驟五:上傳專案
將檔案udf-1.0-SNAPSHOT-jar-with-dependencies.jar上傳到OSS上,並開放JAR包的公用讀取許可權。詳情請參見簡單上傳、設定Bucket ACL。
步驟六中,FE會對UDF所在JAR包進行校正並計算校正值,BE會下載UDF所在JAR包並執行。
步驟六:在StarRocks中建立UDF
StarRocks內提供了兩種Namespace的UDF:一種是Database級Namespace,一種是Global級Namespace。
如果您沒有特殊的UDF可見度隔離需求,您可以直接選擇建立Global UDF。在引用Global UDF時,直接調用Function Name即可,無需任何Catalog和Database作為首碼,訪問更加便捷。
如果您有特殊的UDF可見度隔離需求,或者需要在不同Database下建立同名UDF,那麼你可以選擇在Database內建立UDF。此時,如果您的會話在某個Database內,您可以直接調用Function Name即可;如果您的會話在其他Catalog和Database下,那麼您需要帶上Catalog和Database首碼,例如:
catalog.database.function。
建立Global UDF需要有System級的CREATE GLOBAL FUNCTION許可權;建立資料庫層級的UDF需要有資料庫級的CREATE FUNCTION許可權;使用UDF需要有對應UDF的USAGE許可權。關於如何賦權,參見GRANT。
JAR包上傳完成後,您需要在StarRocks中,按需建立相應的UDF。如果建立Global UDF,只需要在SQL語句中帶上GLOBAL關鍵字即可。
文法
CREATE [GLOBAL][AGGREGATE | TABLE] FUNCTION function_name(arg_type [, ...])
RETURNS return_type
[PROPERTIES ("key" = "value" [, ...]) ]參數說明
參數 | 必選 | 說明 |
GLOBAL | 否 | 如需建立全域UDF,需指定該關鍵字。從3.0版本開始支援。 |
AGGREGATE | 否 | 如要建立UDAF和UDWF,需指定該關鍵字。 |
TABLE | 否 | 如要建立UDTF,需指定該關鍵字。 |
function_name | 是 | 函數名,可以包含資料庫名稱,比如, |
arg_type | 是 | 函數的參數類型。具體支援的資料類型,請參見類型映射關係。 |
return_type | 是 | 函數的傳回值類型。具體支援的資料類型,請參見類型映射關係。 |
properties | 是 | 函數相關屬性。建立不同類型的UDF需配置不同的屬性,詳情和樣本請參考以下樣本。 |
建立Scalar UDF
執行如下命令,在StarRocks中建立之前樣本中的Scalar 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包含對應內網Endpoint的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裡的參數說明與建立Scalar UDF相同。
建立UDWF
執行如下命令,在StarRocks中建立先前樣本中的UDWF。
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。其他參數說明與建立Scalar UDF相同。
建立UDTF
執行如下命令,在StarRocks中建立先前樣本中的UDTF。
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裡的參數說明與建立Scalar UDF相同。
步驟七:使用UDF
建立完成後,您可以測試使用您開發的UDF。
使用Scalar UDF
執行如下命令,使用步驟六建立的Scalar UDF函數。
SELECT MY_UDF_JSON_GET('{"key":"{\\"in\\":2}"}', '$.key.in');使用UDAF
執行如下命令,使用步驟六建立的UDAF函數。
SELECT MY_SUM_INT(col1);使用UDWF
執行如下命令,使用步驟六建立的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
執行如下命令,使用先前樣本中的UDTF。
-- 假設存在表 t1,其列 a、b、c1 資訊如下。
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)的方式指定表格函數返回表的表別名和列別名。
查看UDF資訊
運行以下命令查看UDF資訊。
SHOW [GLOBAL] FUNCTIONS;刪除UDF
運行以下命令刪除指定的UDF。
DROP [GLOBAL] FUNCTION <function_name>(arg_type [, ...]);FAQ
Q:開發UDF時是否可以使用靜態變數?不同UDF間的靜態變數間否會互相影響?
A:支援在開發UDF時使用靜態變數,且不同UDF間(即使類同名),靜態變數是互相隔離的,不會互相影響。