导读 本文主要分享 Apache Doris 1.2版本之前如何构建 C++ UDF。
全文目录:
背景信息
环境信息
IDE准备
C++ UDF 开发流程
自定义TIME_TO_SEC函数
常见问题
总结
【Apache Doris】自定义函数之JAVA UDF详解 发布后,有小伙伴表示由于各种因素还无法升级至 >= 1.2.0 的Doris版本,可否来个 1.2.0 之前的 C++ UDF 详解?安排!
C++自定义UDF函数主要适用于1.2.0版本之前,用户需要的分析能力 Doris 并不具备的场景,例如Tableau通过固化SQL直连Doris查询时出现部分函数不兼容问题。用户可以自行根据自己的需求,实现自定义的函数,并且通过 UDF 框架注册到 Doris 中,来扩展 Doris 的能力,并解决用户分析需求。
CPU:48C
内存:256G
系统:CentOS
Apache Doris版本:0.15
C++编程可以选择Visual Studio、CLion或Code::Blocks等IDE。
1.源码准备
以官方源码为例。
git clone https://github.com/apache/
incubator-doris/tree/branch-0.15/contrib/udf/src/udf_sample
2.文件上传
├── thirdparty
│ │── include
│ │ └── udf.h
│ └── lib
│ └── libDorisUdf.a
└── udf_samples
├── CMakeLists.txt
├── uda_sample.cpp
├── uda_sample.h
├── udf_sample.cpp
└── udf_sample.h
3.文件编译
#进入build文件夹
cd /opt/doris/udf/udf_samples/build
#生成Makefile
cmake ../
#生成对应动态库
make
#编译结果
├── thirdparty
├── udf_samples
└── build
└── src
└── udf_samples
├── libudasample.so
└── libudfsample.so
4.服务搭建
#安装部署nginx步骤省略
server {
listen 8088;
server_name localhost;
location /udf {
alias /opt/doris/udf;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
5.函数使用
CREATE FUNCTION
MyADD00(INT,INT)
RETURNS INT PROPERTIES (
"symbol" = "_ZN9doris_udf666",
"object_file" = "http://nginx_ip:8088/
udf/udf_samples/build/src/udf_samples/libudfsample.so" );
TIME_TO_SEC:函数实现传入一个时间参数,将其时间部分转换成秒的UDF。
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;
}
#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);
}
#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.源码开发 & 实现二
//time_to_sec 的语法格式
// TIME_TO_SEC(time)
//语法格式说明
//time:传入时间,如果传入了日期部分,也不会管,只将时间部分转换成秒
//重点:是指将传入的时间转换成距离当天00:00:00的秒数,00:00:00为基数,等于 0 秒
#include <iostream>
#include <string>
#include <regex>
usingnamespacestd;
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)
returnNULL;
// 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;
return0;
}
#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);
}
#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) {}
}
3.编译结果
4.函数使用
CREATE FUNCTION
TIME_TO_SEC(String)
RETURNS INT PROPERTIES (
"symbol" = "_ZN9doris_udf999,
"object_file" = "http://nginx_ip:8088/libtime_to_sec.so" );
1.ROS问题
自定义 C++ UDF 的使用与普通的函数方式一致,唯一的区别在于,内置函数的作用域是全局的,而 UDF 的作用域是 DB 内部;
1.2后的新版本不建议使用原生C++ UDF,因为兼容性较差、GLIBC一升级就没法用了;建议使用JAVA UDF。
最后,愿各位看官们在2024新的一年里:身体健康、阖家欢乐、心想事成!
往期推荐