本文为您介绍如何通过代码嵌入式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)