本文為您介紹如何通過代碼嵌入式UDF(Embedded UDF)將Java或Python代碼嵌入SQL指令碼。
背景資訊
您可以通過MaxCompute的代碼嵌入式UDF解決以下代碼實現過程繁瑣,且不方便閱讀和維護的問題:
建立UDF並完成代碼開發後,您還需要完成代碼編譯(Java)、建立資源和建立函數操作,過程比較繁瑣。
SQL指令碼中如果包含UDF,您無法直接查看實現邏輯,或無法擷取到JAR包的源碼,維護不方便。
通過UDT使用Java庫函數時,您需要編寫長代碼將Java代碼轉換為運算式,閱讀和維護不方便。另外還會存在Java代碼無法寫為運算式的問題,命令樣本如下。
Foo f = new Foo(); f.execute(); f.getResult();
功能介紹
代碼嵌入式UDF支援將Java或Python代碼嵌入SQL指令碼。Janino-compiler編譯器會識別並提取嵌入的代碼,完成代碼編譯(Java)、動態產生資源和建立臨時函數操作。
代碼嵌入式UDF允許您將SQL指令碼和第三方代碼放入同一個源碼檔案,減少使用UDT或UDF的操作步驟,方便日常開發。
使用限制
嵌入式Java代碼使用Janino-compiler編譯器進行編譯,且支援的Java文法只是標準Java JDK的一個子集。嵌入式Java代碼使用限制包含但不限於以下內容:
不支援Lambda運算式。
不支援Catch多種Exception類型。例如
catch(Exception1 | Exception2 e)
。不支援自動推導泛型。例如
Map map = new HashMap<>();
。型別參數的推導會被忽略,必須顯示Cast。例如
(String) myMap.get(key)
。Assert會強制開啟,不受JVM的-ea參數控制。
不支援Java 8以上(不包含Java 8)版本的語言功能。
UDT引用嵌入式代碼
範例程式碼如下。您需要通過指令碼模式提交執行,詳情請參見SQL指令碼模式。
SELECT
s,
com.mypackage.Foo.extractNumber(s)
FROM VALUES ('abc123def'),('apple') AS t(s);
#CODE ('lang'='JAVA')
package com.mypackage;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Foo {
final static Pattern compile = Pattern.compile(".*?([0-9]+).*");
public static String extractNumber(String input) {
final Matcher m = compile.matcher(input);
if (m.find()) {
return m.group(1);
}
return null;
}
}
#END CODE;
#CODE
和#END CODE
:表示嵌入式代碼的開始和結束位置。位於指令碼末尾的嵌入式代碼塊範圍為整個指令碼。'lang'='JAVA'
:表示嵌入式代碼為Java代碼。還支援PYTHON
。在SQL指令碼裡可以使用UDT文法直接調用
Foo.extractNumber
。
Java代碼嵌入式UDF
範例程式碼如下。您需要通過指令碼模式提交執行,詳情請參見SQL指令碼模式。
CREATE TEMPORARY FUNCTION foo AS 'com.mypackage.Reverse' USING
#CODE ('lang'='JAVA')
package com.mypackage;
import com.aliyun.odps.udf.UDF;
public class Reverse extends UDF {
public String evaluate(String input) {
if (input == null) return null;
StringBuilder ret = new StringBuilder();
for (int i = input.toCharArray().length - 1; i >= 0; i--) {
ret.append(input.toCharArray()[i]);
}
return ret.toString();
}
}
#END CODE;
SELECT foo('abdc');
嵌入式代碼塊可以置於
USING
後或指令碼末尾,置於USING
後的代碼塊範圍僅為CREATE TEMPORARY FUNCTION
語句。CREATE TEMPORARY FUNCTION
建立的函數為臨時函數,僅在本次執行生效,不會存入MaxCompute的Meta系統。如需建立永久函數並存入MaxCompute的Meta系統,請參見CREATE SQL FUNCTION。
Java代碼嵌入式UDTF
範例程式碼如下。您需要通過指令碼模式提交執行,詳情請參見SQL指令碼模式。
CREATE TEMPORARY FUNCTION foo AS 'com.mypackage.Reverse' USING
#CODE ('lang'='JAVA', 'filename'='embedded.jar')
package com.mypackage;
import com.aliyun.odps.udf.UDTF;
import com.aliyun.odps.udf.UDFException;
import com.aliyun.odps.udf.annotation.Resolve;
@Resolve({"string->string,string"})
public class Reverse extends UDTF {
@Override
public void process(Object[] objects) throws UDFException {
String str = (String) objects[0];
String[] split = str.split(",");
forward(split[0], split[1]);
}
}
#END CODE;
SELECT foo('ab,dc') AS (a,b);
由於@Resolve
傳回值要求為string[]
,但Janino-compiler編譯器無法將"string->string,string"
識別為string[]
,@Resolve
註解的參數需要加大括弧({}
),為嵌入式代碼特有內容。用普通方式建立Java UDTF時可省略大括弧({}
)。
Python代碼嵌入式UDF
範例程式碼如下。您需要通過指令碼模式提交執行,詳情請參見SQL指令碼模式。
CREATE TEMPORARY FUNCTION foo AS 'embedded.UDFTest' USING
#CODE ('lang'='PYTHON', 'filename'='embedded')
from odps.udf import annotate
@annotate("bigint->bigint")
class UDFTest(object):
def evaluate(self, a):
return a * a
#END CODE;
SELECT foo(4);
Python代碼的縮排需要符合Python語言規範。
由於註冊Python UDF時
AS
後的類名需要包含Python源碼的檔案名稱,您可以通過'filename'='embedded'
指定一個虛擬檔案名稱。Python不同版本的開發和使用請參考:
Python2: UDF開發(Python2)
Python3: UDF開發(Python3)