本文为您介绍如何通过代码嵌入式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'指定一个虚拟文件名。