Hive UDF 函数的编写

编写Apache Hive用户自定义函数(UDF)有两个不同的接口,一个非常简单,另一个...就相对复杂点。
如果你的函数读和返回都是基础数据类型(Hadoop&Hive基本writable类型,
如Text,IntWritable,LongWriable,DoubleWritable等等),
那么简单的API(org.apache.hadoop.hive.ql.exec.UDF)可以胜任。
但是,如果你想写一个UDF用来操作内嵌数据结构,如Map,List和Set,
那么你要去熟悉org.apache.hadoop.hive.ql.udf.generic.GenericUDF这个API
简单API: org.apache.hadoop.hive.ql.exec.UDF
复杂API: org.apache.hadoop.hive.ql.udf.generic.GenericUDF
接下来我将通过一个示例为上述两个API建立UDF,我将为接下来的示例提供代码与测试

如果你想浏览代码:fork it on Github:https://github.com/rathboma/hive-extension-examples

简单API
用简单UDF API来构建一个UDF只涉及到编写一个类继承实现一个方法(evaluate),以下是示例:
class SimpleUDFExample extends UDF {  
      public Text evaluate(Text input) {  
            return new Text("Hello " + input.toString());  
      }  
} 

好的,在Hive控制台测试一把,也可以在hive中直接测试这个UDF,特别是当你不完全肯定该函数是否能够正确处理问题的时候

%> hive  
hive> ADD JAR target/hive-extensions-1.0-SNAPSHOT-jar-with-dependencies.jar;  
hive> CREATE TEMPORARY FUNCTION helloworld as 'com.matthewrathbone.example.SimpleUDFExample';  
hive> select helloworld(name) from people limit 1000;
事实上,上述UDF有一个bug,不会去检查null参数,null在一个大的数据集当中是很常见的,所以要适当严谨点。作为回应,这边在函数中加了一个null检查。
class SimpleUDFExample extends UDF {  
      public Text evaluate(Text input) {  
            if(input == null) return null;  
            return new Text("Hello " + input.toString());  
      }  
} 
然后加了一个测试去验证它
@Test  
public void testUDFNullCheck() {  
      SimpleUDFExample example = new SimpleUDFExample();  
      Assert.assertNull(example.evaluate(null));  
}
用mvn test跑一下测试,来保证所有用例通过。


复杂的API
org.apache.hadoop.hive.ql.udf.generic.GenericUDF API提供了一种方法去处理那些不是可写类型的对象,例如:struct,map和array类型。这个API需要你亲自去为函数的参数去管理对象存储格式(object inspectors),验证接收的参数的数量与类型。一个object inspector为内在的数据类型提供一个一致性接口,以至不同实现的对象可以在hive中以一致的方式去访问(例如,只要你能提供一个对应的object inspector,你可以实现一个如Map的复合对象)。
这个API要求你去实现以下方法: 
// 这个类似于简单API的evaluat方法,它可以读取输入数据和返回结果  
abstract Object evaluate(GenericUDF.DeferredObject[] arguments);  
  
// 该方法无关紧要,我们可以返回任何东西,但应当是描述该方法的字符串  
abstract String getDisplayString(String[] children);  
  
// 只调用一次,在任何evaluate()调用之前,你可以接收到一个可以表示函数输入参数类型的object inspectors数组  
// 这是你用来验证该函数是否接收正确的参数类型和参数个数的地方  
abstract ObjectInspector initialize(ObjectInspector[] arguments); 

可能要通过一个示例才能去了解这个接口,所以接下来往下看。
示例
我将通过建立一个UDF函数:containsString,来加深对该API了解,该函数接收两个参数:
一个String的列表(list)         一个String
根据该list中是否包含所提供的string来返回true或者false,如下:

containsString(List("a", "b", "c"), "b"); // true   
containsString(List("a", "b", "c"), "d"); // false
不同于UDF接口,这个GenericUDF接口需要更啰嗦点
class ComplexUDFExample extends GenericUDF {  
  
	ListObjectInspector listOI;  
	StringObjectInspector elementOI;  
	
	@Override  
	public String getDisplayString(String[] arg0) {  
			return "arrayContainsExample()"; // this should probably be better  
	}  
	
	@Override  
	public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {  
		if (arguments.length != 2) {  
			throw new UDFArgumentLengthException("arrayContainsExample only takes 2 arguments: List<T>, T");  
		}  
		
		// 1. 检查是否接收到正确的参数类型  
		ObjectInspector a = arguments[0];  
		ObjectInspector b = arguments[1];  
		if (!(a instanceof ListObjectInspector) || !(b instanceof StringObjectInspector)) {  
			throw new UDFArgumentException("first argument must be a list / array, second argument must be a string");  
		}  
		this.listOI = (ListObjectInspector) a;  
		this.elementOI = (StringObjectInspector) b;  
		
		// 2. 检查list是否包含的元素都是string  
		if(!(listOI.getListElementObjectInspector() instanceof StringObjectInspector)) {  
			throw new UDFArgumentException("first argument must be a list of strings");  
		}  
		
		// 返回类型是boolean,所以我们提供了正确的object inspector  
		return PrimitiveObjectInspectorFactory.javaBooleanObjectInspector;  
	}  
		
	@Override  
	public Object evaluate(DeferredObject[] arguments) throws HiveException {  
			
		// 利用object inspectors从传递的对象中得到list与string  
		List<String> list = (List<String>) this.listOI.getList(arguments[0].get());  
		String arg = elementOI.getPrimitiveJavaObject(arguments[1].get());  
		
		// 检查空值  
		if (list == null || arg == null) {  
			return null;  
		}  
		
		// 判断是否list中包含目标值  
		for(String s: list) {  
			if (arg.equals(s)) return new Boolean(true);  
		}  
		return new Boolean(false);  
	}  
    
}   
代码走读
函数的调用模块如下:
1、该UDF用默认的构造器来初始化
2、udf.initialize() 被调用,传人udf参数的object instructors数组,(ListObjectInstructor, StringObjectInstructor)
1) 检查传人的参数有两个与该参数的数据类型是正确的(见上面)
2) 我们保存object instructors用以供evaluate()使用(listOI, elementOI)
3) 返回 object inspector,让Hive能够读取该函数的返回结果(BooleanObjectInspector)
3、对于查询中的每一行,evaluate方法都会被调用,传入该行的指定的列(如:evaluate(List(“a”,“b”,“c”),“c”))。
1) 我们利用initialize方法中存储的object instructors来抽取出正确的值。

2) 在这处理逻辑然后用initialize返回的object inspector来序列化返回来的值(list.contains(elemement) ? true : false)。

测试
测试该函数比较复杂的部分是初始化,一旦调用顺序明确了,我们就知道怎么去构建该对象测试流程,非常简单 
public class ComplexUDFExampleTest {  
    
  @Test  
  public void testComplexUDFReturnsCorrectValues() throws HiveException {  
      
    // 建立需要的模型  
    ComplexUDFExample example = new ComplexUDFExample();  
    ObjectInspector stringOI = PrimitiveObjectInspectorFactory.javaStringObjectInspector;  
    ObjectInspector listOI = ObjectInspectorFactory.getStandardListObjectInspector(stringOI);  
    JavaBooleanObjectInspector resultInspector = 
			(JavaBooleanObjectInspector) example.initialize(new ObjectInspector[]{listOI, stringOI});  
      
    // create the actual UDF arguments  
    List<String> list = new ArrayList<String>();  
    list.add("a");  
    list.add("b");  
    list.add("c");  
      
    // 测试结果  
      
    // 存在的值  
    Object result = example
			.evaluate(new DeferredObject[]{new DeferredJavaObject(list), new DeferredJavaObject("a")});  
    Assert.assertEquals(true, resultInspector.get(result));  
      
    // 不存在的值  
    Object result2 = example
			.evaluate(new DeferredObject[]{new DeferredJavaObject(list), new DeferredJavaObject("d")});
    Assert.assertEquals(false, resultInspector.get(result2));  
      
    // 为null的参数  
    Object result3 = example
			.evaluate(new DeferredObject[]{new DeferredJavaObject(null), new DeferredJavaObject(null)});  
    Assert.assertNull(result3);  
  }  
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值