前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【Apache Doris】自定义函数之C++ UDF详解

【Apache Doris】自定义函数之C++ UDF详解

作者头像
一臻数据
发布2024-12-24 15:19:44
发布2024-12-24 15:19:44
7900
代码可运行
举报
文章被收录于专栏:一臻数据一臻数据
运行总次数:0
代码可运行

导读 本文主要分享 Apache Doris 1.2版本之前如何构建 C++ UDF。


一、背景信息

【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 的能力,并解决用户分析需求。

二、环境信息

1.硬件信息

  • CPU:48C
  • 内存:256G

2.软件信息

  • 系统:CentOS
  • Apache Doris版本:0.15

三、IDE准备

C++编程可以选择Visual Studio、CLion或Code::Blocks等IDE。

本文选择轻量级的Code::Blocks演示,环境准备好后来个传统美学:

四、C++ UDF 开发流程

1.源码准备

以官方源码为例。

代码语言:javascript
代码运行次数:0
复制
git clone https://github.com/apache/
incubator-doris/tree/branch-0.15/contrib/udf/src/udf_sample

2.文件上传

根据官文将文件放置对应的路径下,注意修改CMakeLists.txt文件,以官文内容为主。

代码语言:javascript
代码运行次数:0
复制
├── thirdparty
│ │── include
│ │ └── udf.h
│ └── lib
│   └── libDorisUdf.a
└── udf_samples
  ├── CMakeLists.txt
  ├── uda_sample.cpp
  ├── uda_sample.h
  ├── udf_sample.cpp
  └── udf_sample.h

3.文件编译

代码语言:javascript
代码运行次数:0
复制
#进入build文件夹
cd /opt/doris/udf/udf_samples/build

#生成Makefile
cmake ../

#生成对应动态库
make

#编译结果
├── thirdparty
├── udf_samples
  └── build
    └── src
      └── udf_samples
        ├── libudasample.so
        └── libudfsample.so

4.服务搭建

由于<1.2版本的doris client需要http服务获取so动态库,故需搭建nginx或其它http服务(过程省略)。

代码语言:javascript
代码运行次数:0
复制
#安装部署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.函数使用

创建 UDF 函数:

代码语言:javascript
代码运行次数:0
复制
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" );

使用UDF函数:

五、自定义 TIME_TO_SEC 函数

TIME_TO_SEC:函数实现传入一个时间参数,将其时间部分转换成秒的UDF。

1.源码开发 & 实现一

(1) 主函数

代码语言:javascript
代码运行次数:0
复制
//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) UDF头文件

代码语言:javascript
代码运行次数: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);

}

(3) UDF源文件

代码语言:javascript
代码运行次数:0
复制
#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) {}
}

(4) 小结

不建议使用,doris对其中的regex相关函数并不友好,会直接导致be core:

2.源码开发 & 实现二

(1) 主函数

代码语言:javascript
代码运行次数:0
复制
//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) UDF头文件

代码语言:javascript
代码运行次数: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);

}

(3) UDF源文件

代码语言:javascript
代码运行次数:0
复制
#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) {}
}

(4) 小结

基本完全使用字符串的API实现,简单高效并且兼容性较好,最终选定实现二。

3.编译结果

4.函数使用

创建 UDF 函数:

代码语言:javascript
代码运行次数:0
复制
CREATE FUNCTION 
TIME_TO_SEC(String) 
RETURNS INT PROPERTIES ( 
"symbol" = "_ZN9doris_udf999,
"object_file" = "http://nginx_ip:8088/libtime_to_sec.so" );

原先不兼容TIME_TO_SEC的Tableau固化SQL,现在可以正常运行。

六、常见问题

1.ROS问题

make时出现ROS问题;

注意:需要在CMakeFiles.txt头部加一条SET(CMAKE_CXX_FLAGS “-std=c++0x”)命令解决。

2.路径问题

将CMakeFiles.txt的相对路径都调整为绝对路径或新增路径变量。

七、总结

自定义 C++ UDF 的使用与普通的函数方式一致,唯一的区别在于,内置函数的作用域是全局的,而 UDF 的作用域是 DB 内部;

1.2后的新版本不建议使用原生C++ UDF,因为兼容性较差、GLIBC一升级就没法用了;建议使用JAVA UDF。

最后,愿各位看官们在2024新的一年里:身体健康、阖家欢乐、心想事成!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-01-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一臻数据 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档