Apache Doris 原生C++ UDF之Coding(2)
一、环境信息
1.1 硬件信息
- CPU :4C
- CPU型号:x64(AVX2)
- 内存 :10GB
- 硬盘 :66GB SSD
1.2 软件信息
- Linux版本 :CentOS-7
- Apahce Doris版本 :0.15-release
- CodeBlocks版本:20.03mingw
二、自定义TIME_TO_SEC函数
实现传入一个时间参数,将其时间部分转换成秒的UDF。
2.1 源码开发 & 实现一
2.1.1 测试主函数
//time_to_sec 的语法格式
// TIME_TO_SEC(time)
//语法格式说明
//time:传入时间,如果传入了日期部分,也不会管,只将时间部分转换成秒
//重点:是指将传入的时间转换成距离当天00:00:00的秒数,00:00:00为基数,等于 0 秒
#include <iostream>
#include <string>
#include <regex>
using namespace std;
int time_to_sec(string text)
{
// clear other str
regex r("^((?![0-9]{2}:[0-9]{2}:[0-9]{2}).)*");
string time = regex_replace(text, r, "");
cout << time << endl;
// handle abnormal
if(time.length() != 8)
return NULL;
// get hh mm ss
int HH = atoi(time.substr(0,2).c_str());
int MM = atoi(time.substr(3,2).c_str());
int SS = atoi(time.substr(6,2).c_str());
// return sum sec
return HH*3600 + MM*60 + SS;
}
int main()
{
cout<<time_to_sec("1987-01-01 00:39:38")<<endl;
return 0;
}
2.1.2 UDF头文件
C++
#pragma once
#include "udf.h"
#include <bits/stdc++.h>
namespace doris_udf {
IntVal TIME_TO_SEC(FunctionContext* context, const StringVal& time);
/// --- Prepare / Close Functions ---
/// ---------------------------------
/// The UDF can optionally include a prepare function. The prepare function is called
/// before any calls to the UDF to evaluate values.
void AddUdfPrepare(FunctionContext* context, FunctionContext::FunctionStateScope scope);
/// The UDF can also optionally include a close function. The close function is called
/// after all calls to the UDF have completed.
void AddUdfClose(FunctionContext* context, FunctionContext::FunctionStateScope scope);
}
2.1.3 UDF源文件
C++
#include "time_to_sec.h"
namespace doris_udf {
IntVal TIME_TO_SEC(FunctionContext* context, const StringVal& time) {
// handle null
if (time.is_null) {
return IntVal::null();
}
// clear other str
using namespace std;
const string timestr((char *)time.ptr);
const regex r("^((?![0-9]{2}:[0-9]{2}:[0-9]{2}).)*");
const string replace_str = "";
string hms_time = regex_replace(timestr, r, replace_str);
// handle str abnormal
if(hms_time.length() != 8) {
return IntVal::null();
}
// get hh mm ss
int HH = atoi(hms_time.substr(0,2).c_str());
int MM = atoi(hms_time.substr(3,2).c_str());
int SS = atoi(hms_time.substr(6,2).c_str());
// return sum sec
return HH*3600 + MM*60 + SS;
}
/// --- Prepare / Close Functions ---
/// ---------------------------------
void AddUdfPrepare(FunctionContext* context, FunctionContext::FunctionStateScope scope) {
}
void AddUdfClose(FunctionContext* context, FunctionContext::FunctionStateScope scope) {
}
}
2.1.4 实现方式一小结
不建议使用,doris对其中的regex相关函数并不友好,会直接导致be所有节点crash。
2.2 源码开发 & 实现二
2.2.1 测试主函数
C++
//time_to_sec 的语法格式
// TIME_TO_SEC(time)
//语法格式说明
//time:传入时间,如果传入了日期部分,也不会管,只将时间部分转换成秒
//重点:是指将传入的时间转换成距离当天00:00:00的秒数,00:00:00为基数,等于 0 秒
#include <iostream>
#include <string>
#include <regex>
using namespace std;
int time_to_sec(string text)
{
// clear other str
string segSign = ":";
string::size_type pos1 = text.find(segSign);
if(pos1 == string::npos)
cout << "没找到!" << endl;
else
cout << "找到了!下标:" << pos1<<endl;
string time = text.substr(pos1-2,8);
cout << time << endl;
// handle abnormal
if(time.length() != 8)
return NULL;
// get hh mm ss
int HH = atoi(time.substr(0,2).c_str());
int MM = atoi(time.substr(3,2).c_str());
int SS = atoi(time.substr(6,2).c_str());
// return sum sec
return HH*3600 + MM*60 + SS;
}
int main()
{
cout<<time_to_sec("1987-01-01 00:39:38")<<endl;
return 0;
}
2.2.2 UDF头文件
C++
#pragma once
#include "udf.h"
#include <bits/stdc++.h>
namespace doris_udf {
IntVal TIME_TO_SEC(FunctionContext* context, const StringVal& time);
/// --- Prepare / Close Functions ---
/// ---------------------------------
/// The UDF can optionally include a prepare function. The prepare function is called
/// before any calls to the UDF to evaluate values.
void AddUdfPrepare(FunctionContext* context, FunctionContext::FunctionStateScope scope);
/// The UDF can also optionally include a close function. The close function is called
/// after all calls to the UDF have completed.
void AddUdfClose(FunctionContext* context, FunctionContext::FunctionStateScope scope);
}
2.2.3 UDF源文件
C++
#include "time_to_sec.h"
namespace doris_udf {
IntVal TIME_TO_SEC(FunctionContext* context, const StringVal& time) {
// handle null
if (time.is_null) {
return IntVal::null();
}
// clear other str
using namespace std;
string timestr((char *)time.ptr);
string segSign = ":";
string::size_type pos = timestr.find(segSign);
string hms_time;
if(pos == string::npos)
return IntVal::null();
else
hms_time = timestr.substr(pos-2,8);
// handle str abnormal
if(hms_time.length() != 8) {
return IntVal::null();
}
// get hh mm ss
int HH = atoi(hms_time.substr(0,2).c_str());
int MM = atoi(hms_time.substr(3,2).c_str());
int SS = atoi(hms_time.substr(6,2).c_str());
// return sum sec
IntVal result;
result.val = HH*3600 + MM*60 + SS;
return {
result.val};
}
/// --- Prepare / Close Functions ---
/// ---------------------------------
void AddUdfPrepare(FunctionContext* context, FunctionContext::FunctionStateScope scope) {
}
void AddUdfClose(FunctionContext* context, FunctionContext::FunctionStateScope scope) {
}
}
2.2.4 实现方式二小结
基本完全使用字符串的API实现,简单高效并且兼容性较好,最终选定实现二。
三、编译结果
四、函数使用
4.1 创建 UDF 函数
CREATE FUNCTION
TIME_TO_SEC(String)
RETURNS INT PROPERTIES (
"symbol" = "_ZN9doris_udf11TIME_TO_SECEPNS_15FunctionContextERKNS_9StringValE",
"object_file" = "http://10.192.119.68:8088/udf/udf_samples/build/src/udf_samples/libtime_to_sec.so" );
4.2 使用UDF 函数
原先不兼容TIME_TO_SEC的Tableau固化SQL,现在可以正常运行。
五、总结
- 自定义C++ UDF 的使用与普通的函数方式一致,唯一的区别在于,内置函数的作用域是全局的,而 UDF 的作用域是 DB 内部
- 1.2后的新版本不建议使用原生C++ UDF,因为兼容性较差、GLIBC一升级就没法用了;建议使用JAVA UDF
Apache Doris 自定义C++ UDF的Coding至此结束,查阅过程中若遇到问题欢迎留言交流