全部產品
Search
文件中心

MaxCompute:UDF(嵌入式)

更新時間:Jun 19, 2024

本文為您介紹如何通過代碼嵌入式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不同版本的開發和使用請參考: