小 T 导读:虽然 TDengine 已经提供了非常多的常用计算函数,但是在具体实践中,企业的开发团队往往会因为自己特殊的业务需求,需要特有的计算函数,这时候,支持自定义函数功能就特别重要了。本文将介绍 TDengine 3.0 支持的 UDF 机制。
在使用 TDengine 这款时序数据库(Time Series Database, TSDB)的时候,我们经常会用到各种内置函数,通过在数据库中完成很多计算,可以大大简化数据库应用层的开发工作。
TDengine 提供了大量的内置函数,可以分为几个大类:
- 单行函数:单行函数为查询结果中的每一行返回一个结果行
- 数学函数:如 ABS、SIN、COS、LOG、POW 等
- 字符串函数:如 CHAR_LENGTH、CONCAT、LOWER、SUBSTR、UPPER 等
- 转换函数:如 CAST、TO_JSON、TO_UNIXTIMESTAMP 等
- 时间和日期函数:NOW、TIMEDIFF、TIMEZONE、TODAY 等
- 聚合函数:聚合函数为查询结果集的每一个分组返回单个结果行
- 如 AVG、COUNT、STDDEV、SUM 等
- 选择函数
- 时序数据特有函数
- 系统信息函数
虽然 TDengine 已经提供了这么多常用的计算函数,但是在具体实践中,企业的开发团队往往会因为自己特殊的业务需求,需要特有的计算函数,这时候,支持自定义函数功能就特别重要了。
本文将具体介绍如何在 TDengine 中定义并使用自定义函数。
利用 UDF(User Defined Function) 功能,TDengine 可以插入用户编写的处理代码并在查询中使用它们,这样就能很方便地解决特殊应用场景中的使用需求。 UDF 通常以数据表中的一列数据做为输入,同时支持以嵌套子查询的结果作为输入。
TDengine 支持通过 C/C++ 语言来定义 UDF。TDengine 3.0 优化了相关机制,所以本文描述的特性适用于 3.0 及以上版本。
基本概念
用户可以通过 UDF 实现两类函数:标量函数和聚合函数。标量函数对每行数据输出一个值,如求绝对值 abs,正弦函数 sin,字符串拼接函数 concat 等。聚合函数对多行数据进行输出一个值,如求平均数 avg,最大值 max 等。
实现 UDF 时,需要实现规定的接口函数
- 标量函数需要实现标量接口函数 scalarfn
- 聚合函数需要实现聚合接口函数 aggfn_start、aggfn、aggfn_finish
- 如果需要初始化,实现 udf_init;如果需要清理工作,实现 udf_destroy
接口函数的名称是 UDF 名称,或者是 UDF 名称和特定后缀(_start, _finish, _init, _destroy)的连接。列表中的scalarfn、aggfn、udf需要替换成udf函数名。
标量接口函数
int32_t scalarfn(SUdfDataBlock* inputDataBlock, SUdfColumn *resultColumn)
其中 scalarFn 是函数名的占位符。这个函数对数据块进行标量计算,通过设置resultColumn结构体中的变量设置值。
参数的具体含义是:
- inputDataBlock: 输入的数据块
- resultColumn: 输出列
聚合接口函数
int32_t aggfn_start(SUdfInterBuf *interBuf)
int32_t aggfn(SUdfDataBlock* inputBlock, SUdfInterBuf *interBuf, SUdfInterBuf *newInterBuf)
int32_t aggfn_finish(SUdfInterBuf* interBuf, SUdfInterBuf *result)
其中 aggfn 是函数名的占位符。首先调用aggfn_start生成结果buffer,然后相关的数据会被分为多个行数据块,对每个数据块调用 aggfn 用数据块更新中间结果,最后再调用 aggfn_finish 从中间结果产生最终结果,最终结果只能含 0 或 1 条结果数据。
参数的具体含义是:
- interBuf:中间结果 buffer。
- inputBlock:输入的数据块。
- newInterBuf:新的中间结果buffer。
- result:最终结果。
UDF 初始化和销毁
int32_t udf_init()
int32_t udf_destroy()
其中 udf 是函数名的占位符。udf_init 完成初始化工作。 udf_destroy 完成清理工作。如果没有初始化工作,无需定义udf_init函数。
如果没有清理工作,无需定义udf_destroy函数。 篇幅所限,相关数据结构的定义可以参考 UDF 文档。
编译 UDF
用户定义函数的 C 语言源代码无法直接被 TDengine 系统所使用,而是需要先编译为 动态链接库,之后才能载入 TDengine 系统。
假设我们编写了自定义函数,保存在 add_one.c 文件中,在 Linux 上可以这样编译:
gcc -g -O0 -fPIC -shared add_one.c -o add_one.so
创建 UDF
用户可以通过 SQL 指令在系统中加载客户端所在主机上的 UDF 函数库。一旦创建成功,则当前 TDengine 集群的所有用户都可以在 SQL 指令中使用这些函数。UDF 存储在系统的 MNode 节点上,因此即使重启 TDengine 系统,已经创建的 UDF 也仍然可用。
在创建 UDF 时,需要区分标量函数和聚合函数。
- 创建标量函数
CREATE FUNCTION function_name AS library_path OUTPUTTYPE output_type;
例如,如下语句可以把 libbitand.so 创建为系统中可用的 UDF:
CREATE FUNCTION bit_and AS "/home/taos/udf_example/libbitand.so" OUTPUTTYPE INT;
- 创建聚合函数:
CREATE AGGREGATE FUNCTION function_name AS library_path OUTPUTTYPE output_type [ BUFSIZE buffer_size ];
例如,如下语句可以把 libl2norm.so 创建为系统中可用的 UDF:
CREATE AGGREGATE FUNCTION l2norm AS "/home/taos/udf_example/libl2norm.so" OUTPUTTYPE DOUBLE bufsize 8;
如果不再需要,可以通过 DROP 指令删除所创建的 UDF。
DROP FUNCTION function_name;
使用 UDF
在 SQL 指令中,可以直接以在系统中创建 UDF 时赋予的函数名来调用用户定义函数。例如:
SELECT X(c1,c2) FROM table/stable;
表示对名为 c1、c2 的数据列调用名为 X 的用户定义函数。SQL 指令中用户定义函数可以配合 WHERE 等查询特性来使用。
欢迎下载试用 TDengine 3.0,并尝试编写一个自定义函数。
欢迎添加小T微信:tdengine,加入物联网技术讨论群,第一时间了解 TDengine 官方信息,与关注前沿技术的同学们共同探讨新技术、新玩法。