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

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

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

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

S
shenglian zhou 已提交
13 14
实现 UDF 时,需要实现规定的接口函数
- 标量函数需要实现标量接口函数 scalarfn 。
S
shenglian zhou 已提交
15
- 聚合函数需要实现聚合接口函数 aggfn_start , aggfn , aggfn_finish。
S
shenglian zhou 已提交
16
- 如果需要初始化,实现 udf_init;如果需要清理工作,实现udf_destroy。
S
shenglian zhou 已提交
17 18

接口函数的名称是 UDF 名称,或者是 UDF 名称和特定后缀(_start, _finish, _init, _destroy)的连接。列表中的scalarfn,aggfn, udf需要替换成udf函数名。
19

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

S
shenglian zhou 已提交
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
// 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 已提交
50 51
scalarfn 为函数名的占位符,需要替换成函数名,如bit_and。

S
shenglian zhou 已提交
52
## 实现聚合函数
S
shenglian zhou 已提交
53 54

聚合函数的实现模板如下
S
shenglian zhou 已提交
55 56 57 58 59 60 61 62 63 64 65 66 67
```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 已提交
68
// @param interbuf intermediate value to intialize
S
shenglian zhou 已提交
69 70
// @return error number defined in taoserror.h
int32_t aggfn_start(SUdfInterBuf* interBuf) {
S
shenglian zhou 已提交
71
    // initialize intermediate value in interBuf
S
shenglian zhou 已提交
72 73 74 75
    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 已提交
76 77 78 79
// @param inputBlock input data block
// @param interBuf old state
// @param newInterBuf new state
// @return error number defined in taoserror.h
S
shenglian zhou 已提交
80 81 82 83
int32_t aggfn(SUdfDataBlock* inputBlock, SUdfInterBuf *interBuf, SUdfInterBuf *newInterBuf) {
    // read from inputBlock and interBuf and output to newInterBuf
    return TSDB_CODE_SUCCESS;
}
84

S
shenglian zhou 已提交
85
// 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 已提交
86 87
// @interBuf : intermediate value
// @result: final result
S
shenglian zhou 已提交
88 89
// @return error number defined in taoserror.h
int32_t int32_t aggfn_finish(SUdfInterBuf* interBuf, SUdfInterBuf *result) {
S
shenglian zhou 已提交
90
    // read data from inputDataBlock and process, then output to result
S
shenglian zhou 已提交
91 92 93 94 95 96 97 98 99 100
    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 已提交
101
aggfn为函数名的占位符,需要修改为自己的函数名,如l2norm。
S
shenglian zhou 已提交
102 103 104

## 接口函数定义

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

陶建辉(Jeff)'s avatar
陶建辉(Jeff) 已提交
107
接口函数返回值表示是否成功。如果返回值是 TSDB_CODE_SUCCESS,表示操作成功,否则返回的是错误代码。错误代码定义在 taoserror.h,和 taos.h 中的API共享错误码的定义。例如, TSDB_CODE_UDF_INVALID_INPUT 表示输入无效输入。TSDB_CODE_OUT_OF_MEMORY 表示内存不足。
S
shenglian zhou 已提交
108 109

接口函数参数类型见数据结构定义。
S
shenglian zhou 已提交
110

S
shenglian zhou 已提交
111
### 标量接口函数
112

S
shenglian zhou 已提交
113
 `int32_t scalarfn(SUdfDataBlock* inputDataBlock, SUdfColumn *resultColumn)` 
114
 
S
shenglian zhou 已提交
115
 其中 scalarFn 是函数名的占位符。这个函数对数据块进行标量计算,通过设置resultColumn结构体中的变量设置值
116

S
shenglian zhou 已提交
117
参数的具体含义是:
118
  - inputDataBlock: 输入的数据块
S
shenglian zhou 已提交
119
  - resultColumn: 输出列。输出列 
120

S
shenglian zhou 已提交
121
### 聚合接口函数
122

S
shenglian zhou 已提交
123
`int32_t aggfn_start(SUdfInterBuf *interBuf)`
124

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

S
shenglian zhou 已提交
127
`int32_t aggfn_finish(SUdfInterBuf* interBuf, SUdfInterBuf *result)`
128

S
shenglian zhou 已提交
129 130 131
其中 aggfn 是函数名的占位符。首先调用aggfn_start生成结果buffer,然后相关的数据会被分为多个行数据块,对每个数据块调用 aggfn 用数据块更新中间结果,最后再调用 aggfn_finish 从中间结果产生最终结果,最终结果只能含 0 或 1 条结果数据。

参数的具体含义是:
132 133 134 135
  - interBuf:中间结果 buffer。
  - inputBlock:输入的数据块。
  - newInterBuf:新的中间结果buffer。
  - result:最终结果。
136 137


138 139
### UDF 初始化和销毁
`int32_t udf_init()`
140

141
`int32_t udf_destroy()`
142

S
shenglian zhou 已提交
143
其中 udf 是函数名的占位符。udf_init 完成初始化工作。 udf_destroy 完成清理工作。如果没有初始化工作,无需定义udf_init函数。如果没有清理工作,无需定义udf_destroy函数。
144 145


S
shenglian zhou 已提交
146
## UDF 数据结构
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 190 191 192 193
```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 已提交
194 195 196
数据结构说明如下:

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

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

204 205 206 207 208 209 210 211 212 213 214 215
## 编译 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 已提交
216 217 218
## 管理和使用UDF
关于如何管理和使用UDF,参见[UDF使用说明](../12-taos-sql/26-udf.md)

219 220
## 示例代码

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

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

225
<details>
S
shenglian zhou 已提交
226
<summary>bit_and.c</summary>
227 228

```c
S
shenglian zhou 已提交
229
{{#include tests/script/sh/bit_and.c}}
230 231 232 233
```

</details>

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

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

238
<details>
S
shenglian zhou 已提交
239
<summary>l2norm.c</summary>
240 241

```c
S
shenglian zhou 已提交
242
{{#include tests/script/sh/l2norm.c}}
243 244 245
```

</details>