> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-mintlify-8a08bda2.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> WebAssembly 用户自定义函数文档

# WebAssembly 用户自定义函数

export const ExperimentalBadge = () => {
  return <div className="experimentalBadge">
            <div className="experimentalIcon">
            <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path strokeWidth="1.25" d="M5.5 2H10.5" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" />
                <path strokeWidth="1.25" d="M9.50015 2V6.19625L13.4283 12.7425C13.4738 12.8183 13.4985 12.9049 13.4996 12.9934C13.5008 13.0818 13.4785 13.169 13.435 13.246C13.3914 13.323 13.3283 13.3871 13.2519 13.4317C13.1755 13.4764 13.0886 13.4999 13.0002 13.5H3.00015C2.91164 13.5 2.8247 13.4766 2.74822 13.432C2.67174 13.3874 2.60847 13.3233 2.56487 13.2463C2.52126 13.1693 2.49889 13.082 2.50004 12.9935C2.50119 12.905 2.52582 12.8184 2.5714 12.7425L6.50015 6.19625V2" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" />
                <path strokeWidth="1.25" d="M4.47656 9.56754C5.30344 9.41254 6.47656 9.47942 7.99969 10.25C10.0153 11.2707 11.4216 11.0569 12.2184 10.7282" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" />
            </svg>
        </div>
            Experimental feature. <u><a href="/docs/beta-and-experimental-features#experimental-features">Learn more.</a></u>
        </div>;
};

export const CloudNotSupportedBadge = () => {
  return <div className="cloudNotSupportedBadge">
            <div className="cloudNotSupportedIcon">
            <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path strokeWidth="1.5" d="M6.33366 12.6666L12.3739 12.6667C13.6593 12.6667 14.7073 11.6187 14.7073 10.3334C14.7073 9.04804 13.6593 8.00003 12.3739 8.00003C12.3739 8.00003 12.3337 7.66659 12.0003 7.33325M10.667 5.33322C8.00033 2.33325 4.45395 4.78537 4.14195 6.68203C2.55728 6.7627 1.29395 8.06203 1.29395 9.6667C1.29395 11.3234 2.66699 12.6666 4.00033 12.6666" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" />
                <path strokeWidth="1.5" d="M2.66699 14L12.0003 4.66663" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" />
            </svg>

        </div>
            Not supported in ClickHouse Cloud
        </div>;
};

ClickHouse 支持创建以 WebAssembly 编写的用户自定义函数 (UDF) 。这样，你就可以将 Rust、C、C++ 等语言编写的自定义逻辑编译为 WebAssembly 模块并执行。

<div id="overview">
  ## 概述
</div>

WebAssembly 模块是已编译的二进制文件，其中包含一个或多个可由 ClickHouse 调用的函数。
可以将模块看作一个只需加载一次、却能重复多次使用的库或共享对象。

包含 UDF 的 WebAssembly 模块可以使用任何能编译为 WebAssembly 的语言编写，例如 Rust、C 或 C++。

编译为 WebAssembly 的代码 (“guest”代码) 和执行它的 ClickHouse (“host”) 运行在沙箱环境中，只能访问专用的内存空间。

guest 代码会导出 ClickHouse 可调用的函数——这些函数既包括实现自定义逻辑的函数 (用于定义 UDF) ，也包括 ClickHouse 与 WebAssembly 代码之间进行内存管理和数据交换所需的辅助函数。

你的代码应编译为“freestanding” WebAssembly (也称为 `wasm32-unknown-unknown`) ，不依赖任何操作系统或标准库。此外，仅支持默认的 32 位 WebAssembly 目标 (不支持 `wasm64` 扩展) 。
该模块必须遵循一种受支持的通信协议 (ABI) 才能与 ClickHouse 交互。

编译完成后，需要将模块的二进制代码插入 `system.webassembly_modules` 表中，以将其加载到 ClickHouse。
之后，你可以使用 `CREATE FUNCTION ... LANGUAGE WASM` 语句创建引用该模块导出函数的 UDF。

<div id="prerequisites">
  ## 前置条件
</div>

在 ClickHouse 配置中启用 WebAssembly 支持：

```xml theme={null}
<clickhouse>
    <allow_experimental_webassembly_udf>true</allow_experimental_webassembly_udf>
    <webassembly_udf_engine>wasmtime</webassembly_udf_engine>
</clickhouse>
```

可用的引擎实现：

* `wasmtime` (默认，推荐) — 基于 [WasmTime](https://github.com/bytecodealliance/wasmtime)
* `wasmedge` — 基于 [WasmEdge](https://github.com/WasmEdge/WasmEdge)

<div id="quick-start">
  ## 快速入门
</div>

本示例通过实现 [Collatz conjecture](https://en.wikipedia.org/wiki/Collatz_conjecture) 计算器，演示创建 WebAssembly UDF 的完整流程。

我们将使用 WebAssembly Text 格式 (WAT) 编写代码。它是 WebAssembly 的一种人类可读表示形式，因此在这一阶段无需使用任何编程语言。
ClickHouse 要求模块采用二进制格式，因此我们将使用转换工具将 WAT 转换为 WASM。
要执行此转换，可以使用 [WebAssembly Binary Toolkit (WABT)](https://github.com/WebAssembly/wabt) 中的 `wat2wasm`，或 [wasm-tools](https://github.com/bytecodealliance/wasm-tools) 中的 `parse` 命令。

```bash theme={null}
cat << 'EOF' | wasm-tools parse | clickhouse client -q "INSERT INTO system.webassembly_modules (name, code) SELECT 'collatz', code FROM input('code String') FORMAT RawBlob"
(module
  (func $next (param $n i32) (result i32)
    local.get $n i32.const 1 i32.and
    (if (result i32)
      (then local.get $n i32.const 3 i32.mul i32.const 1 i32.add)
      (else local.get $n i32.const 2 i32.div_u)))
  (func $steps (export "steps") (param $n i32) (result i32)
    (local $count i32)
    local.get $n i32.const 1 i32.lt_u
    (if (then i32.const 0 return))
    (block $done (loop $loop
      local.get $n i32.const 1 i32.eq br_if $done
      local.get $n call $next local.set $n
      local.get $count i32.const 1 i32.add local.set $count
      br $loop))
    local.get $count)
)
EOF
```

在上面的代码片段中，我们使用 `FORMAT RawBlob` 将二进制 WASM 代码直接通过管道传给 ClickHouse 客户端，并将其插入到 `system.webassembly_modules` 表中。

然后，我们定义一个引用该模块导出的 `steps` 函数的 UDF：

```sql theme={null}
CREATE FUNCTION collatz_steps LANGUAGE WASM ARGUMENTS (n UInt32) RETURNS UInt32 FROM 'collatz' :: 'steps';
```

请注意，我们在 `::` 后指定的是模块中的函数名，因为它与 UDF 的名称不同。

现在我们可以在查询中使用 `collatz_steps` 函数：

```sql theme={null}
SELECT groupArray(collatz_steps(number :: UInt32))
FROM numbers(1, 100)
FORMAT TSV
```

`number` 列被显式转换为 `UInt32`，因为 WebAssembly 函数要求类型必须与 `CREATE FUNCTION` 语句中指定的签名精确匹配。

最终结果中，我们得到了 1 到 100 各个数字对应的 Collatz 步骤序列，这与序列 [OEIS 中的 A006577](https://oeis.org/A006577) 相对应。

```text theme={null}
[0,1,7,2,5,8,16,3,19,6,14,9,9,17,17,4,12,20,20,7,7,15,15,10,23,10,111,18,18,18,106,5,26,13,13,21,21,21,34,8,109,8,29,16,16,16,104,11,24,24,24,11,11,112,112,19,32,19,32,19,19,107,107,6,27,27,27,14,14,14,102,22,115,22,14,22,22,35,35,9,22,110,110,9,9,30,30,17,30,17,92,17,17,105,105,12,118,25,25,25]
```

<div id="manage-wasm-modules-via-system-table">
  ## 通过系统表管理 WASM 模块
</div>

WebAssembly 模块存储在 `system.webassembly_modules` 表中，其结构如下：

* **列**
  * `name` String — 模块名称。不能为空，且只能包含词字符。
  * `code` String — 原始 WASM 二进制代码。仅可写，读取时返回空字符串。
  * `hash` UInt256 — 模块二进制文件的 SHA256 (如果文件已存在于磁盘上但尚未加载，则为零) 。

模块管理通过对该表执行标准 SQL 操作进行：

<div id="insert-a-module">
  ### 插入模块
</div>

```sql theme={null}
INSERT INTO system.webassembly_modules (name, code)
SELECT 'my_module', base64Decode('AGFzbQEAAAA...');
```

可选择提供完整性哈希：

```sql theme={null}
INSERT INTO system.webassembly_modules (name, code, hash)
SELECT 'my_module', base64Decode('...'), reinterpretAsUInt256(unhex('369f...c57d'));
```

如果提供的哈希值与模块代码计算出的 SHA256 不一致，则插入会失败。在从 S3 或 HTTP 等外部来源加载模块时，这会很有用。

<div id="list-modules">
  ### 列出模块
</div>

```sql theme={null}
SELECT name, lower(hex(reinterpretAsFixedString(hash))) AS sha256 FROM system.webassembly_modules

   ┌─name────┬─sha256───────────────────────────────────────────────────────────┐
1. │ collatz │ a084a10b7b5cb07db198bc93bf1f3c1f8cb8ef279df7a4f6b66b1cdd55d79c48 │
   └─────────┴──────────────────────────────────────────────────────────────────┘
```

<div id="delete-a-module">
  ### 删除模块
</div>

通过执行 `DELETE FROM system.webassembly_modules WHERE name = '...'` 语句删除模块。
该谓词必须是精确匹配的 `name = 'literal'`，或用于删除所有名称匹配该模式的模块的 `name LIKE 'pattern'`；不接受其他形态。

```sql theme={null}
DELETE FROM system.webassembly_modules WHERE name = 'collatz';

-- 批量删除所有名称以 `tmp_` 开头的模块（字面下划线需转义为 `\_`）：
DELETE FROM system.webassembly_modules WHERE name LIKE 'tmp\_%';
```

如果现有 UDFs 中有任何一个引用了某个匹配模块，删除操作将失败，因此必须先删除这些 UDFs。

<div id="create-a-webassembly-udf">
  ## 创建 WebAssembly UDF
</div>

**语法**:

```sql theme={null}
CREATE [OR REPLACE] FUNCTION function_name
LANGUAGE WASM
FROM 'module_name' [:: 'source_function_name']
ARGUMENTS ( [name type[, ...]] | [type[, ...]] )
RETURNS return_type
[ABI ROW_DIRECT | ABI BUFFERED_V1]
[DETERMINISTIC]
[SHA256_HASH 'hex']
[SETTINGS key = value[, ...]];
```

**参数**：

* `function_name`：ClickHouse 中的函数名称。可能与模块中导出的函数名不同。
* `FROM 'module_name' :: 'source_function_name'`：已加载的 WASM 模块名称，以及要使用的 WASM 模块中的函数名称 (默认为 function\_name)
* `ARGUMENTS`：参数名称和类型列表 (名称为可选项，用于支持命名字段的序列化格式)
* `ABI`：应用程序二进制接口版本
  * `ROW_DIRECT`：直接类型映射，按行处理
  * `BUFFERED_V1`：基于块的处理，带序列化
* `DETERMINISTIC`：将该函数声明为确定性的——对于相同输入始终返回相同输出。指定后，当所有参数都是常量时，ClickHouse 可能会对调用进行常量折叠：函数会在查询分析阶段求值一次，结果会复用于每一行。
* `SHA256_HASH`：用于校验的预期模块哈希 (若省略则自动填充) ，可用于确保不同副本上加载的是正确的 WASM 模块。
* `SETTINGS`：函数级设置
  * `serialization_format` String —— ABI 所需的序列化格式。默认值：`MsgPack`。

<div id="abis-versions">
  ## ABI 版本
</div>

要与 ClickHouse 交互，WebAssembly 模块必须遵循受支持的 ABI (应用程序二进制接口) 之一。

* `ROW_DIRECT`：直接类型映射 (仅支持基本类型 `Int32`、`UInt32`、`Int64`、`UInt64`、`Float32`、`Float64`)
* `BUFFERED_V1`：通过序列化处理复杂类型

<div id="abi-row_direct">
  ### ABI ROW\_DIRECT
</div>

按行直接调用导出的 WASM 函数。

* 参数和返回值类型均为数值类型：`Int32/UInt32/Int64/UInt64/Float32/Float64/Int128/UInt128`。
* 此 ABI 不支持字符串。
* 签名必须与 WASM 导出一致 (`i32/i64/f32/f64/v128`) 。
* 模块无需导出任何辅助函数。

例如，对于签名如下的函数：

```
(func (param i32 i64 f32) (result f64) ...)
```

可按如下方式创建：

```sql theme={null}
CREATE FUNCTION my_func ARGUMENTS (Int32, UInt64, Float32) RETURNS Float64 ...
```

WebAssembly 不区分有符号参数和无符号参数，而是通过不同的指令来解释这些值。因此，参数的大小必须严格一致，而是否有符号则由函数内部的操作决定。

<div id="abi-buffered_v1">
  ### ABI BUFFERED\_V1
</div>

<Note>
  此 ABI 仍处于 Experimental 阶段，未来版本中可能会发生变化。
</Note>

通过 WASM 内存中的 (反) 序列化一次处理整个块。支持任意参数和返回类型。

序列化后的数据会被复制到 WASM 内存中，并将指向缓冲区的指针 (缓冲区由数据指针和数据大小组成) 以及输入中的行数一并传递给 UDF 函数。因此，WASM 侧的用户自定义函数始终接收两个 `i32` 参数，并返回单个 `i32` 值。
guest 侧代码处理数据后，会返回一个指向结果缓冲区的指针，其中包含序列化后的结果数据。

guest 侧代码必须提供两个函数，用于创建和销毁这些缓冲区。

```
(module
  ;; 分配指定大小的新缓冲区
  ;; 返回：Buffer 结构的句柄（非直接数据指针！），包含指向数据的指针及数据大小
  (func (export "clickhouse_create_buffer")
    (param $size i32)    ;; 要分配的数据大小
    (result i32))        ;; 返回具有足够空间的缓冲区句柄

  ;; 通过句柄释放缓冲区
  (func (export "clickhouse_destroy_buffer")
    (param $handle i32)  ;; 要释放的缓冲区句柄
    (result))            ;; 无返回值

    ;; 用户自定义函数
    (func (export "user_defined_function1")
      (param $input_buffer_handle i32)  ;; 输入缓冲区句柄
      (param $n i32)                    ;; 输入的行数
      (result i32))                     ;; 返回输出缓冲区句柄
)
```

C 定义示例：

```c theme={null}
typedef struct {
    uint8_t * data;
    uint32_t size;
} ClickhouseBuffer;

ClickhouseBuffer * clickhouse_create_buffer(uint32_t size) { /* ... */ }

void clickhouse_destroy_buffer(ClickhouseBuffer * data) { /* ... */ }

/// 示例用户自定义函数
ClickhouseBuffer * user_defined_function1(ClickhouseBuffer * span, uint32_t n) { /* ... */ }
ClickhouseBuffer * user_defined_function2(ClickhouseBuffer * span, uint32_t n) { /* ... */ }
```

<div id="note-for-developing-udfs-in-rust">
  ### 关于用 Rust 开发 UDF 的说明
</div>

对于 Rust 程序，我们提供了一个辅助 crate [clickhouse-wasm-udf](https://crates.io/crates/clickhouse-wasm-udf)，可简化为 ClickHouse 开发 WebAssembly UDF 的过程。该 crate 提供了内存管理功能，因此你无需手动实现 `clickhouse_create_buffer` 和 `clickhouse_destroy_buffer` 函数，只需将该 crate 添加为依赖即可。此外，还提供了宏 `#[clickhouse_wasm_udf]`，可将普通的 Rust 函数封装为所需的 ABI 格式。

借助这个 crate，你可以像这样编写 UDF：

```rust theme={null}

use clickhouse_wasm_udf_bindgen::clickhouse_udf;

#[clickhouse_udf]
pub fn some_udf(data: String) -> HashMap<String, String> {
    // 在此处填写您的实现
}

```

宏会生成一个包装函数，用于接收并返回缓冲区结构，并使用 `serde` 自动处理序列化和反序列化。

<div id="host-api-available-to-modules">
  ## 模块可用的宿主 API
</div>

模块可导入并使用以下宿主函数：

* `clickhouse_server_version() -> i64` — 以整数形式返回 ClickHouse server 版本 (例如，v25.11.1.1 对应 25011001) 。
* `clickhouse_throw(ptr: i32, size: i32)` — 使用给定的消息抛出错误。接受一个指向存放错误消息字符串的内存位置的指针，以及该字符串的大小。
* `clickhouse_log(ptr: i32, size: i32)` — 将消息写入 ClickHouse server 的文本日志。
* `clickhouse_random(ptr: i32, size: i32)` — 用随机字节填充内存。

<div id="settings">
  ## 设置
</div>

以下查询级设置用于控制 WebAssembly UDF 的执行：

* `webassembly_udf_max_fuel` — 每个 WebAssembly UDF 实例每次执行的 fuel 上限。每条 WebAssembly 指令都会消耗一定数量的 fuel。设为 0 表示不限制。

* `webassembly_udf_max_memory` — 每个 WebAssembly UDF 实例的内存上限 (以字节为单位) 。

* `webassembly_udf_max_input_block_size` — 单个块中传递给 WebAssembly UDF 的最大行数。设为 0 表示一次处理所有行。

* `webassembly_udf_max_instances` — 每个函数可并行运行的 WebAssembly UDF 实例最大数量。

示例用法：

```sql theme={null}
SET webassembly_udf_max_fuel = 200000;
SELECT my_wasm_udf(column) FROM table;
```

<div id="see-also">
  ## 另请参阅
</div>

* [ClickHouse UDF 概述](/zh/reference/functions/regular-functions/udf)
