一、环境信息
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至此结束,查阅过程中若遇到问题欢迎留言交流