编写 Apache Hive 用户自定义函数(UDF)有两个不同的接口:一个是非常简单的 UDF,上一篇已经介绍过了;还有一个是 GenericUDF,相对复杂点。两个 API 的区别是:如果函数的参数和返回都是基础数据类型,那么简单 API(UDF)可以胜任;但是,如果你想写一个函数用来操作内嵌数据结构,如 Map、List 和 Set,此时就需要去熟悉复杂 API(GenericUDF)。

0x00 自定义 GenericUDF 开发

编写 GenericUDF 需要两个步骤:

  • 继承 org.apache.hadoop.hive.ql.udf.generic.GenericUDF 类;
  • 重写 initializeevaluategetDisplayString 三个方法;

每个方法有着不同的作用,参考如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 这个方法只调用一次,并且在evaluate()方法之前调用。
// 该方法接受的参数是一个ObjectInspectors数组,表示函数输入参数类型。
// 1.检查接受正确的参数个数;
// 2.检查接受正确的参数类型;
// 3.定义返回值被序列化的一致类型。
abstract ObjectInspector initialize(ObjectInspector[] arguments);

// 这个方法类似UDF的evaluate()方法。它处理真实的参数,并返回最终结果。
abstract Object evaluate(DeferredObject[] arguments);

// 这个方法用于当实现的GenericUDF出错的时候,打印出提示信息。
// 而提示信息就是你实现该方法最后返回的字符串。
abstract String getDisplayString(String[] children);

其中有个 ObjectInspector 需要解释下,简单来说是帮助使用者访问需要序列化或者反序列化的对象,为数据类型提供一致性的访问接口。有关 ObjectInspector 更深入地理解,请参考 Hive-ObjectInspector

0x01 官方 ArrayGenericUDF 示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.data.hive;

import java.util.ArrayList;

import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDFUtils;
import org.apache.hadoop.hive.ql.exec.Description;
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.exec.UDFArgumentTypeException;
import org.apache.hadoop.hive.ql.exec.UDFArgumentLengthException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorConverters;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorConverters.Converter;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;

@Description(
name = "array",
value = "_FUNC_(n0, n1...) - Creates an array with the given elements ")
public class ArrayGenericUDF extends GenericUDF {

private transient Converter[] converters;
private transient ArrayList<Object> ret = new ArrayList<Object>();

@Override
public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {

if (arguments.length < 1) {
throw new UDFArgumentLengthException("array() takes at least one parameter.");
}

GenericUDFUtils.ReturnObjectInspectorResolver returnOIResolver = new GenericUDFUtils.ReturnObjectInspectorResolver(true);

for (int i = 0; i < arguments.length; i++) {
if (!returnOIResolver.update(arguments[i])) {
throw new UDFArgumentTypeException(i, "Argument type \""
+ arguments[i].getTypeName()
+ "\" is different from preceding arguments. "
+ "Previous type was \"" + arguments[i - 1].getTypeName() + "\"");
}
}

converters = new Converter[arguments.length];

ObjectInspector returnOI =
returnOIResolver.get(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
for (int i = 0; i < arguments.length; i++) {
converters[i] = ObjectInspectorConverters.getConverter(arguments[i], returnOI);
}

return ObjectInspectorFactory.getStandardListObjectInspector(returnOI);
}

@Override
public Object evaluate(DeferredObject[] arguments) throws HiveException {
ret.clear();
for (int i = 0; i < arguments.length; i++) {
ret.add(converters[i].convert(arguments[i].get()));
}
return ret;
}

@Override
public String getDisplayString(String[] children) {
return getStandardDisplayString("array", children, ",");
}
}

0x02 代码走读

方法的调用关系如下:

  1. ArrayGenericUDF 用默认的构造器来初始化;

  2. initialize() 被调用,传入函数参数的 ObjectInspector[] 数组;
    2.1. 检查传入的参数个数与每个参数的数据类型是正确的;
    2.2. 保存 converters (ObjectInspector) 用以供 evaluate() 使用;
    2.3. 返回 ListObjectInspector,让 Hive 能够读取该函数的返回结果;

  3. 对于查询中的每一行,evaluate() 方法都会被调用,并传入该行的指定列;
    3.1. 利用 initialize() 方法中存储的 converters (ObjectInspector) 来抽取出正确的值;
    3.2. 执行处理逻辑然后用 initialize() 返回的 ListObjectInspector 来序列化返回值;

参考文献

SerDe
Hadoop Hive UDF Tutorial - Extending Hive with Custom Functions
Hive-ObjectInspector