Java UDF

SinceVersion 1.2.0

Java UDF 为用户提供UDF编写的Java接口,以方便用户使用Java语言进行自定义函数的执行。相比于 Native 的 UDF 实现,Java UDF 有如下优势和限制:

  1. 优势
  • 兼容性:使用Java UDF可以兼容不同的Doris版本,所以在进行Doris版本升级时,Java UDF不需要进行额外的迁移操作。与此同时,Java UDF同样遵循了和Hive/Spark等引擎同样的编程规范,使得用户可以直接将Hive/Spark的UDF jar包迁移至Doris使用。
  • 安全:Java UDF 执行失败或崩溃仅会导致JVM报错,而不会导致 Doris 进程崩溃。
  • 灵活:Java UDF 中用户通过把第三方依赖打进用户jar包,而不需要额外处理引入的三方库。
  1. 使用限制
  • 性能:相比于 Native UDF,Java UDF会带来额外的JNI开销,不过通过批式执行的方式,我们已经尽可能的将JNI开销降到最低。
  • 向量化引擎:Java UDF当前只支持向量化引擎。

类型对应关系

TypeUDF Argument Type
BoolBoolean
TinyIntByte
SmallIntShort
IntInteger
BigIntLong
LargeIntBigInteger
FloatFloat
DoubleDouble
DateLocalDate
DatetimeLocalDateTime
StringString
DecimalBigDecimal

编写 UDF 函数

本小节主要介绍如何开发一个 Java UDF。在 samples/doris-demo/java-udf-demo/ 下提供了示例,可供参考,查看点击这里

使用Java代码编写UDF,UDF的主入口必须为 evaluate 函数。这一点与Hive等其他引擎保持一致。在本示例中,我们编写了 AddOne UDF来完成对整型输入进行加一的操作。 值得一提的是,本例不只是Doris支持的Java UDF,同时还是Hive支持的UDF,也就是说,对于用户来讲,Hive UDF是可以直接迁移至Doris的。

创建 UDF

  1. CREATE FUNCTION
  2. name ([,...])
  3. [RETURNS] rettype
  4. PROPERTIES (["key"="value"][,...])

说明:

  1. PROPERTIES中symbol表示的是包含UDF类的类名,这个参数是必须设定的。
  2. PROPERTIES中file表示的包含用户UDF的jar包,这个参数是必须设定的。
  3. PROPERTIES中type表示的 UDF 调用类型,默认为 Native,使用 Java UDF时传 JAVA_UDF。
  4. PROPERTIES中always_nullable表示的 UDF 返回结果中是否有可能出现NULL值,是可选参数,默认值为true。
  5. name: 一个function是要归属于某个DB的,name的形式为dbName.funcName。当dbName没有明确指定的时候,就是使用当前session所在的db作为dbName

示例:

  1. CREATE FUNCTION java_udf_add_one(int) RETURNS int PROPERTIES (
  2. "file"="file:///path/to/java-udf-demo-jar-with-dependencies.jar",
  3. "symbol"="org.apache.doris.udf.AddOne",
  4. "always_nullable"="true",
  5. "type"="JAVA_UDF"
  6. );
  • “file”=”http://IP:port/udf-code.jar“, 当在多机环境时,也可以使用http的方式下载jar包
  • “always_nullable”可选属性, 如果在计算中对出现的NULL值有特殊处理,确定结果中不会返回NULL,可以设为false,这样在整个查询计算过程中性能可能更好些。
  • 如果你是本地路径方式,这里数据库驱动依赖的jar包,FE、BE节点都要放置

编写 UDAF 函数

在使用Java代码编写UDAF时,有一些必须实现的函数(标记required)和一个内部类State,下面将以一个具体的实例来说明 下面的SimpleDemo将实现一个类似的sum的简单函数,输入参数INT,输出参数是INT

  1. package org.apache.doris.udf.demo;
  2. import org.apache.hadoop.hive.ql.exec.UDAF;
  3. import java.io.DataInputStream;
  4. import java.io.DataOutputStream;
  5. import java.io.IOException;
  6. public class SimpleDemo extends UDAF {
  7. //Need an inner class to store data
  8. /*required*/
  9. public static class State {
  10. /*some variables if you need */
  11. public int sum = 0;
  12. }
  13. /*required*/
  14. public State create() {
  15. /* here could do some init work if needed */
  16. return new State();
  17. }
  18. /*required*/
  19. public void destroy(State state) {
  20. /* here could do some destroy work if needed */
  21. }
  22. /*required*/
  23. //first argument is State, then other types your input
  24. public void add(State state, Integer val) {
  25. /* here doing update work when input data*/
  26. if (val != null) {
  27. state.sum += val;
  28. }
  29. }
  30. /*required*/
  31. public void serialize(State state, DataOutputStream out) {
  32. /* serialize some data into buffer */
  33. try {
  34. out.writeInt(state.sum);
  35. } catch ( IOException e ) {
  36. throw new RuntimeException (e);
  37. }
  38. }
  39. /*required*/
  40. public void deserialize(State state, DataInputStream in) {
  41. /* deserialize get data from buffer before you put */
  42. int val = 0;
  43. try {
  44. val = in.readInt();
  45. } catch ( IOException e ) {
  46. throw new RuntimeException (e);
  47. }
  48. state.sum = val;
  49. }
  50. /*required*/
  51. public void merge(State state, State rhs) {
  52. /* merge data from state */
  53. state.sum += rhs.sum;
  54. }
  55. /*required*/
  56. //return Type you defined
  57. public Integer getValue(State state) {
  58. /* return finally result */
  59. return state.sum;
  60. }
  61. }
  1. CREATE AGGREGATE FUNCTION simple_sum(INT) RETURNS INT PROPERTIES (
  2. "file"="file:///pathTo/java-udaf.jar",
  3. "symbol"="org.apache.doris.udf.demo.SimpleDemo",
  4. "always_nullable"="true",
  5. "type"="JAVA_UDF"
  6. );
  • 实现的jar包可以放在本地也可以存放在远程服务端通过http下载,但必须让每个BE节点都能获取到jar包; 否则将会返回错误状态信息”Couldn’t open file ……”.

目前还暂不支持UDTF

使用 UDF

用户使用 UDF 必须拥有对应数据库的 SELECT 权限。

UDF 的使用与普通的函数方式一致,唯一的区别在于,内置函数的作用域是全局的,而 UDF 的作用域是 DB内部。当链接 session 位于数据内部时,直接使用 UDF 名字会在当前DB内部查找对应的 UDF。否则用户需要显示的指定 UDF 的数据库名字,例如 dbName.funcName

删除 UDF

当你不再需要 UDF 函数时,你可以通过下述命令来删除一个 UDF 函数, 可以参考 DROP FUNCTION

示例

samples/doris-demo/java-udf-demo/ 目录中提供了具体示例。具体使用方法见每个目录下的README.md,查看点击这里

使用须知

  1. 不支持复杂数据类型(HLL,Bitmap)。
  2. 当前允许用户自己指定JVM最大堆大小,配置项是jvm_max_heap_size。
  3. char类型的udf在create function时需要使用String类型。
  4. 由于jvm加载同名类的问题,不要同时使用多个同名类作为udf实现,如果想更新某个同名类的udf,需要重启be重新加载classpath。