09-udf.md 9.2 KB
Newer Older
1 2 3 4 5 6 7 8
---
sidebar_label: 用户定义函数
title: UDF(用户定义函数)
description: "支持用户编码的聚合函数和标量函数,在查询中嵌入并使用用户定义函数,拓展查询的能力和功能。"
---

在有些应用场景中,应用逻辑需要的查询无法直接使用系统内置的函数来表示。利用 UDF 功能,TDengine 可以插入用户编写的处理代码并在查询中使用它们,就能够很方便地解决特殊应用场景中的使用需求。 UDF 通常以数据表中的一列数据做为输入,同时支持以嵌套子查询的结果作为输入。

S
shenglian zhou 已提交
9
TDengine 支持通过 C/C++ 语言进行 UDF 定义。接下来结合示例讲解 UDF 的使用方法。
10

S
shenglian zhou 已提交
11 12 13
用户可以通过 UDF 实现两类函数: 标量函数和聚合函数。标量函数对每行数据返回一个值,如求绝对值 abs,正弦函数 sin,字符串拼接函数 concat 等。聚合函数对多行数据进行返回一个值,如求平均数 avg,最大值 max 等。

实现udf时,需要实现规定的接口函数。接口函数的名称是 udf 名称,或者是 udf 名称和特定后缀(_start, _finish, _init, _destroy)的连接后。以下列表中的scalarfn,aggfn, udf需要替换成udf函数名。
S
shenglian zhou 已提交
14 15 16
- 标量函数需要实现标量接口函数 scalarfn,
- 聚合函数需要实现聚合接口函数 aggfn_start , aggfn , aggfn_finish。
- 无论标量函数还是聚合函数,如果需要初始化,实现 udf_init;如果需要清理工作,实现udf_destory。
17

S
shenglian zhou 已提交
18
## 实现标量函数
S
shenglian zhou 已提交
19
标量函数实现模板如下
S
shenglian zhou 已提交
20 21 22 23
```c
#include "taos.h"
#include "taoserror.h"
#include "taosudf.h"
24

S
shenglian zhou 已提交
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
// initialization function. if no initialization, we can skip definition of it. The initialization function shall be concatenation of the udf name and _init suffix
// @return error number defined in taoserror.h
int32_t scalarfn_init() {
    // initialization.
    return TSDB_CODE_SUCCESS;
}

// scalar function main computation function
// @param inputDataBlock, input data block composed of multiple columns with each column defined by SUdfColumn
// @param resultColumn, output column
// @return error number defined in taoserror.h
int32_t scalarfn(SUdfDataBlock* inputDataBlock, SUdfColumn* resultColumn) {
    // read data from inputDataBlock and process, then output to resultColumn.
    return TSDB_CODE_SUCCESS;
}

// cleanup function. if no cleanup related processing, we can skip definition of it. The destroy function shall be concatenation of the udf name and _destroy suffix.
// @return error number defined in taoserror.h
int32_t scalarfn_destroy() {
    // clean up
    return TSDB_CODE_SUCCESS;
}
```
S
shenglian zhou 已提交
48 49
scalarfn 为函数名的占位符,需要替换成函数名,如bit_and。

S
shenglian zhou 已提交
50
## 实现聚合函数
S
shenglian zhou 已提交
51 52

聚合函数的实现模板如下
S
shenglian zhou 已提交
53 54 55 56 57 58 59 60 61 62 63 64 65
```c
#include "taos.h"
#include "taoserror.h"
#include "taosudf.h"

// Initialization function. if no initialization, we can skip definition of it. The initialization function shall be concatenation of the udf name and _init suffix
// @return error number defined in taoserror.h
int32_t aggfn_init() {
    // initialization.
    return TSDB_CODE_SUCCESS;
}

// aggregate start function. The intermediate value or the state(@interBuf) is initialized in this function. The function name shall be concatenation of udf name and _start suffix
S
shenglian zhou 已提交
66
// @param interbuf intermediate value to intialize
S
shenglian zhou 已提交
67 68
// @return error number defined in taoserror.h
int32_t aggfn_start(SUdfInterBuf* interBuf) {
S
shenglian zhou 已提交
69
    // initialize intermediate value in interBuf
S
shenglian zhou 已提交
70 71 72 73
    return TSDB_CODE_SUCESS;
}

// aggregate reduce function. This function aggregate old state(@interbuf) and one data bock(inputBlock) and output a new state(@newInterBuf).
S
shenglian zhou 已提交
74 75 76 77
// @param inputBlock input data block
// @param interBuf old state
// @param newInterBuf new state
// @return error number defined in taoserror.h
S
shenglian zhou 已提交
78 79 80 81
int32_t aggfn(SUdfDataBlock* inputBlock, SUdfInterBuf *interBuf, SUdfInterBuf *newInterBuf) {
    // read from inputBlock and interBuf and output to newInterBuf
    return TSDB_CODE_SUCCESS;
}
82

S
shenglian zhou 已提交
83
// aggregate function finish function. This function transforms the intermediate value(@interBuf) into the final output(@result). The function name must be concatenation of aggfn and _finish suffix.
S
shenglian zhou 已提交
84 85
// @interBuf : intermediate value
// @result: final result
S
shenglian zhou 已提交
86 87
// @return error number defined in taoserror.h
int32_t int32_t aggfn_finish(SUdfInterBuf* interBuf, SUdfInterBuf *result) {
S
shenglian zhou 已提交
88
    // read data from inputDataBlock and process, then output to result
S
shenglian zhou 已提交
89 90 91 92 93 94 95 96 97 98
    return TSDB_CODE_SUCCESS;
}

// cleanup function. if no cleanup related processing, we can skip definition of it. The destroy function shall be concatenation of the udf name and _destroy suffix.
// @return error number defined in taoserror.h
int32_t aggfn_destroy() {
    // clean up
    return TSDB_CODE_SUCCESS;
}
```
S
shenglian zhou 已提交
99
aggfn为函数名的占位符,需要修改为自己的函数名,如l2norm。
S
shenglian zhou 已提交
100 101 102

## 接口函数定义

S
shenglian zhou 已提交
103
接口函数的名称是 udf 名称,或者是 udf 名称和特定后缀(_start, _finish, _init, _destroy)的连接。以下描述中函数名称中的 scalarfn,aggfn, udf 需要替换成udf函数名。
S
shenglian zhou 已提交
104

S
shenglian zhou 已提交
105
接口函数返回值表示是否成功,如果错误返回错误代码,错误代码见taoserror.h。参数类型见数据结构定义。
S
shenglian zhou 已提交
106

S
shenglian zhou 已提交
107
### 标量接口函数
108

S
shenglian zhou 已提交
109
 `int32_t scalarfn(SUdfDataBlock* inputDataBlock, SUdfColumn *resultColumn)` 
110
 
111 112
 其中 udf 是函数名的占位符,以上述模板实现的函数对行数据块进行标量计算。

S
shenglian zhou 已提交
113
- 其中各参数的具体含义是:
114 115
  - inputDataBlock: 输入的数据块
  - resultColumn: 输出列 
116

S
shenglian zhou 已提交
117
### 聚合接口函数
118

S
shenglian zhou 已提交
119
`int32_t aggfn_start(SUdfInterBuf *interBuf)`
120

S
shenglian zhou 已提交
121
`int32_t aggfn(SUdfDataBlock* inputBlock, SUdfInterBuf *interBuf, SUdfInterBuf *newInterBuf)`
122

S
shenglian zhou 已提交
123 124
`int32_t aggfn_finish(SUdfInterBuf* interBuf, SUdfInterBuf *result)`
其中 aggfn 是函数名的占位符。其中各参数的具体含义是:
125

126 127 128 129
  - interBuf:中间结果 buffer。
  - inputBlock:输入的数据块。
  - newInterBuf:新的中间结果buffer。
  - result:最终结果。
130 131


S
shenglian zhou 已提交
132
其计算过程为:首先调用aggfn_start生成结果buffer,然后相关的数据会被分为多个行数据块,对每个行数据块调用 aggfn 用数据块更新中间结果,最后再调用 aggfn_finish 从中间结果产生最终结果,最终结果只能含 0 或 1 条结果数据。
133

134 135
### UDF 初始化和销毁
`int32_t udf_init()`
136

137
`int32_t udf_destroy()`
138

S
shenglian zhou 已提交
139
其中 udf 是函数名的占位符,可以替换成自己的函数名。udf_init 完成初始化工作。 udf_destroy 完成清理工作。如果没有初始化工作,无需定义udf_init函数。如果没有清理工作,无需定义udf_destroy函数。
140 141


S
shenglian zhou 已提交
142
## UDF 数据结构
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
```c
typedef struct SUdfColumnMeta {
  int16_t type;
  int32_t bytes;
  uint8_t precision;
  uint8_t scale;
} SUdfColumnMeta;

typedef struct SUdfColumnData {
  int32_t numOfRows;
  int32_t rowsAlloc;
  union {
    struct {
      int32_t nullBitmapLen;
      char   *nullBitmap;
      int32_t dataLen;
      char   *data;
    } fixLenCol;

    struct {
      int32_t varOffsetsLen;
      int32_t   *varOffsets;
      int32_t payloadLen;
      char   *payload;
      int32_t payloadAllocLen;
    } varLenCol;
  };
} SUdfColumnData;

typedef struct SUdfColumn {
  SUdfColumnMeta colMeta;
  bool           hasNull;
  SUdfColumnData colData;
} SUdfColumn;

typedef struct SUdfDataBlock {
  int32_t numOfRows;
  int32_t numOfCols;
  SUdfColumn **udfCols;
} SUdfDataBlock;

typedef struct SUdfInterBuf {
  int32_t bufLen;
  char* buf;
  int8_t numOfResult; //zero or one
} SUdfInterBuf;
```
S
shenglian zhou 已提交
190 191 192 193 194
数据结构说明如下:

- SUdfDataBlock 数据块包含行数 numOfRows 和列数 numCols。udfCols[i] (0 <= i <= numCols-1)表示每一列数据,类型为SUdfColumn*
- SUdfColumn 包含列的数据类型定义 colMeta 和列的数据colData。
- SUdfColumnMeta 成员定义同 taos.h 数据类型定义。
S
shenglian zhou 已提交
195
- SUdfColumnData 数据可以变长,varLenCol定义变长数据,fixLenCol定义定长数据。 
S
shenglian zhou 已提交
196
- SUdfInterBuf 定义中间结构buffer,以及buffer中结果个数 numOfResult
197 198 199

为了更好的操作以上数据结构,提供了一些便利函数,定义在 taosudf.h。

200 201 202 203 204 205 206 207 208 209 210 211 212 213
## 编译 UDF

用户定义函数的 C 语言源代码无法直接被 TDengine 系统使用,而是需要先编译为 动态链接库,之后才能载入 TDengine 系统。

例如,按照上一章节描述的规则准备好了用户定义函数的源代码 add_one.c,以 Linux 为例可以执行如下指令编译得到动态链接库文件:

```bash
gcc -g -O0 -fPIC -shared add_one.c -o add_one.so
```

这样就准备好了动态链接库 add_one.so 文件,可以供后文创建 UDF 时使用了。为了保证可靠的系统运行,编译器 GCC 推荐使用 7.5 及以上版本。

## 示例代码

S
shenglian zhou 已提交
214
### 标量函数示例 [bit_and](https://github.com/taosdata/TDengine/blob/develop/tests/script/sh/bit_and.c)
215

S
shenglian zhou 已提交
216 217
bit_add 实现多列的按位与功能。如果只有一列,返回这一列。bit_add 忽略空值。

218
<details>
S
shenglian zhou 已提交
219
<summary>bit_and.c</summary>
220 221

```c
S
shenglian zhou 已提交
222
{{#include tests/script/sh/bit_and.c}}
223 224 225 226
```

</details>

S
shenglian zhou 已提交
227
### 聚合函数示例 [l2norm](https://github.com/taosdata/TDengine/blob/develop/tests/script/sh/l2norm.c)
228

S
shenglian zhou 已提交
229
l2norm 实现了输入列的所有数据的二阶范数,即对每个数据先平方,再累加求和,最后开方。
S
shenglian zhou 已提交
230

231
<details>
S
shenglian zhou 已提交
232
<summary>l2norm.c</summary>
233 234

```c
S
shenglian zhou 已提交
235
{{#include tests/script/sh/l2norm.c}}
236 237 238
```

</details>