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

> WebAssembly 用户自定义函数文档

# WebAssembly 用户自定义函数

export const ExperimentalBadge = () => {
  return <a href="https://clickhouse.com/docs/reference/settings/beta-and-experimental-features#experimental-features" 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 功能
        </a>;
};

export const CloudNotSupportedBadge = () => {
  return <a href="https://clickhouse.com/docs/products/cloud/guides/cloud-compatibility#list-of-unsupported-features" 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>
            ClickHouse Cloud 不支持此功能
        </a>;
};

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

<CloudNotSupportedBadge />

<ExperimentalBadge />

<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="distribute-a-module-across-a-cluster">
  ### 在 cluster 中分发模块
</div>

`system.webassembly_modules` 是按实例划分的表——`INSERT` 只会写入处理该 connection 的副本。`INSERT` statement 没有 `ON CLUSTER` 这种形式，因此后续执行 `CREATE FUNCTION ... ON CLUSTER` 时，会在没有该模块的副本上失败：

```text theme={null}
Code: 674. DB::Exception: WebAssembly module 'collatz' not found:
while adding user defined function `collatz_steps`. (RESOURCE_NOT_FOUND)
```

要将 `insert` 操作发送到每个节点，请写入 `cluster` 表函数，而不是本地的 `system.webassembly_modules` 表：

```bash theme={null}
cat collatz.wasm | clickhouse client -q "
  INSERT INTO FUNCTION cluster('default', 'system', 'webassembly_modules') (name, code)
  SELECT 'collatz', code FROM input('code String') FORMAT RawBlob"
```

<Note>
  这种方式依赖底层分布式写入路径访问每个分片中的所有副本，而这只有在集群配置为 `internal_replication=false` 时才会发生。启用 `internal_replication=true` 时 (对于使用 `ReplicatedMergeTree` 自行完成复制的集群，这是默认设置) ，insert 只会发送到每个分片中一个健康的副本，而 `system.webassembly_modules` 不会通过这条路径进行复制，因此某些副本仍然会缺少该模块。在这种配置下，你需要分别向每个副本执行 insert，例如遍历 `system.clusters`，并通过每台主机的 `remote(...)` 写入，或者将二进制文件复制到每台主机上的 `user_scripts/wasm/` 目录中。

  你可以通过 `SELECT cluster, shard_num, internal_replication FROM system.clusters` 查看集群的 `internal_replication`。
</Note>

在扇出式 insert 之后，该模块会出现在每个副本上，并且 `CREATE FUNCTION ... ON CLUSTER` 将成功执行：

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

你可以使用 `clusterAllReplicas` 验证该模块是否已在所有节点上加载：

```sql theme={null}
SELECT hostName(), name FROM clusterAllReplicas('default', system.webassembly_modules) WHERE name = 'collatz';
```

向 `system.webassembly_modules` 执行插入时，对于相同的 `(name, hash)` 组合是幂等的，因此重新运行分发式插入是安全的，也是在某个副本被替换后修复状态的合理方式。请注意，新加入的服务器不会自动补收已有模块——你必须针对更新后的集群重新运行插入，或者将该 binary 放到新 host 上的 `user_scripts/wasm/` 目录中。

<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 | ABI ASSEMBLYSCRIPT]
[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`：基于块的处理，带序列化
  * `ASSEMBLYSCRIPT`：适用于由 [AssemblyScript](https://www.assemblyscript.org) 编译器生成的模块的按行处理。数值类型会映射到 AssemblyScript 基本类型；ClickHouse `String` 会映射到 AssemblyScript `string`。
* `DETERMINISTIC`：将该函数声明为确定性的——对于相同输入始终返回相同输出。指定后，当所有参数都是常量时，ClickHouse 可能会对调用进行常量折叠：函数会在查询分析阶段求值一次，结果会复用于每一行。
* `SHA256_HASH`：用于校验的预期模块哈希 (若省略则自动填充) ，可用于确保不同副本上加载的是正确的 WASM 模块。
* `SETTINGS`：函数级设置
  * `serialization_format` String —— 用于序列化传递给模块的参数块并解析返回结果的格式。仅由 `ABI BUFFERED_V1` 使用。支持的值：`MsgPack`、`JSONEachRow`、`CSV`、`TSV`、`TSVRaw`、`RowBinary` 和 `Buffers`。默认值：`MsgPack`。像 `Buffers` 这样基于块的格式必须返回单列，且该列的类型必须与声明的函数签名匹配。
  * `webassembly_udf_enable_fuel` Bool —— 为该函数启用有限 fuel 预算。默认值：`true`。当为 `false` 时，查询级设置 `webassembly_udf_max_fuel` 会被该函数忽略。使用 `wasmtime` engine 时，禁用 fuel 限制可能会提升性能。但是，对于不受信任或有缺陷的 guest 代码，这可能会增加失控执行的风险。

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

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

* `ROW_DIRECT`：直接类型映射 (仅支持基本类型 `Int32`、`UInt32`、`Int64`、`UInt64`、`Float32`、`Float64`)
* `BUFFERED_V1`：通过序列化处理复杂类型
* `ASSEMBLYSCRIPT`：与 [AssemblyScript](https://www.assemblyscript.org) 模块按行互操作；支持数值类型和 `String`。

<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 仍处于实验阶段，未来的发行版中可能会有所变更。
</Note>

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

数据通过 WASM 内存中的缓冲区交换。缓冲区是一个 8 字节的结构，包含一个指向数据的指针和数据大小 (两个小端序 `u32` 值) 。缓冲区通过句柄传递——即指向该结构的指针，而非指向数据本身的指针。guest 代码必须导出两个函数，用于创建和销毁这些缓冲区。

对于每个输入块，ClickHouse 会：

1. 使用函数的 `serialization_format` (默认为 `MsgPack`) 序列化参数列。基于行的格式会逐行写入值，参数值的顺序与其在 `ARGUMENTS` 中声明的顺序一致；具有命名字段的格式使用参数名称，因此使用此类格式时，请在 `ARGUMENTS` 中声明参数名称。
2. 调用模块导出的 `clickhouse_create_buffer`，并将序列化数据复制到返回的缓冲区所指向的内存中。
3. 使用两个 `i32` 参数调用用户自定义函数：输入缓冲区句柄 (如果函数没有参数，则为 `0`) 和行数。该函数返回一个 `i32`——由 guest 代码自行分配的结果缓冲区句柄；返回 `0` 会导致查询报错失败。
4. 读取结果缓冲区：其中必须恰好包含一列，且行数必须与输入完全相同，并以相同格式序列化。对于具有命名字段的格式，例如 `JSONEachRow`，结果列必须命名为 `result`。
5. 对输入缓冲区 (如有) 和结果缓冲区的句柄调用 `clickhouse_destroy_buffer`。guest 代码不得自行释放结果缓冲区，也不得将输入句柄作为结果返回——否则该缓冲区会被销毁两次。

函数会对每个输入块调用一次：大型查询会由查询管道拆分为多个块 (每次调用的最大行数还可通过 `webassembly_udf_max_input_block_size` 设置加以限制) ，因此每次调用传入的行数是该块的大小，而不是整个查询的大小。

<Note>
  缓冲区中的确切字节由所选格式定义，guest 代码必须自行解码和编码。对于 `String` 值，尤其需要注意：

  * `MsgPack`：ClickHouse 使用 `bin` 类型家族 (`0xc4`/`0xc5`/`0xc6`) 写入 `String` 值，而非 `str` 家族。解析结果时，两种家族均可接受。
  * `RowBinary`：每个 `String` 前面都会附加以无符号 varint ([LEB128](https://en.wikipedia.org/wiki/LEB128)) 编码的字节长度，而不是定长整数。
  * `JSONEachRow`：每个结果行必须是包含 `result` 键的对象，例如 `{"result":"value"}`。
</Note>

```
(module
  ;; Allocate a new buffer of specified size
  ;; Returns: handle to Buffer structure (not direct data pointer!) with pointer to data and size
  (func (export "clickhouse_create_buffer")
    (param $size i32)    ;; Size of data to allocate
    (result i32))        ;; Returns buffer handle with enough space

  ;; Free a buffer by its handle
  (func (export "clickhouse_destroy_buffer")
    (param $handle i32)  ;; Buffer handle to free
    (result))            ;; No return value

    ;; User-defined function
    (func (export "user_defined_function1")
      (param $input_buffer_handle i32)  ;; Input buffer handle
      (param $n i32)                    ;; Number of rows in input
      (result i32))                     ;; Returns output buffer handle
)
```

一个完整的独立运行 C 示例：`str_reverse` 使用 `serialization_format = 'RowBinary'` 反转每个输入字符串的字节。模块实例会跨块复用，因此 `clickhouse_destroy_buffer` 必须真正回收内存；这里会在所有缓冲区销毁后重置分配器。

```c theme={null}
#include <stdint.h>

typedef struct {
    uint8_t * data;
    uint32_t size;
} ClickHouseBuffer;

#define HEAP_SIZE (1 << 24)
static _Alignas(16) uint8_t heap[HEAP_SIZE];
static uint32_t heap_pos = 0;
static uint32_t live_buffers = 0;

__attribute__((export_name("clickhouse_create_buffer")))
ClickHouseBuffer * clickhouse_create_buffer(uint32_t size)
{
    uint32_t total = (sizeof(ClickHouseBuffer) + size + 15u) & ~15u;
    if (heap_pos + total > HEAP_SIZE)
        return 0; /* a zero handle makes the host fail the query with an error */
    ClickHouseBuffer * buf = (ClickHouseBuffer *) &heap[heap_pos];
    buf->data = &heap[heap_pos + sizeof(ClickHouseBuffer)];
    buf->size = size;
    heap_pos += total;
    ++live_buffers;
    return buf;
}

__attribute__((export_name("clickhouse_destroy_buffer")))
void clickhouse_destroy_buffer(ClickHouseBuffer * buf)
{
    if (--live_buffers == 0)
        heap_pos = 0;
}

/* RowBinary prefixes each String with its byte length as an unsigned varint (LEB128) */
static uint64_t read_varint(const uint8_t ** pp)
{
    uint64_t value = 0;
    for (int shift = 0;; shift += 7)
    {
        uint8_t b = *(*pp)++;
        value |= (uint64_t)(b & 0x7f) << shift;
        if (!(b & 0x80))
            return value;
    }
}

static void write_varint(uint8_t ** pp, uint64_t value)
{
    while (value >= 0x80)
    {
        *(*pp)++ = (uint8_t)(value | 0x80);
        value >>= 7;
    }
    *(*pp)++ = (uint8_t)value;
}

/* Reverses the bytes of each input string */
__attribute__((export_name("str_reverse")))
ClickHouseBuffer * str_reverse(ClickHouseBuffer * input, uint32_t num_rows)
{
    ClickHouseBuffer * out = clickhouse_create_buffer(input->size);
    if (!out)
        return 0;
    const uint8_t * in = input->data;
    uint8_t * o = out->data;
    for (uint32_t row = 0; row < num_rows; ++row)
    {
        uint64_t len = read_varint(&in);
        write_varint(&o, len);
        for (uint64_t i = 0; i < len; ++i)
            o[i] = in[len - 1 - i];
        in += len;
        o += len;
    }
    out->size = (uint32_t)(o - out->data);
    return out;
}
```

使用 clang 和 `wasm-ld` (随 LLVM/lld 提供) 构建：

```bash theme={null}
clang --target=wasm32 -ffreestanding -nostdlib -fno-builtin -c str_reverse.c
wasm-ld --no-entry str_reverse.o -o str_reverse.wasm
```

函数必须在模块导出中可见：要么如上所示使用 `__attribute__((export_name("...")))` 标记，要么通过 `wasm-ld --export-all` 链接。`-fno-builtin` 可防止 clang 将普通的字节循环优化为对 `memcpy`/`memset` 的调用；在没有标准库时，这些函数不可用。

加载模块并创建函数：

```bash theme={null}
cat str_reverse.wasm | clickhouse client -q "INSERT INTO system.webassembly_modules (name, code) SELECT 'str_reverse', code FROM input('code String') FORMAT RawBlob"
```

```sql theme={null}
CREATE FUNCTION str_reverse LANGUAGE WASM ABI BUFFERED_V1
FROM 'str_reverse' :: 'str_reverse'
ARGUMENTS (s String) RETURNS String
SETTINGS serialization_format = 'RowBinary';

SELECT str_reverse(toString(number + 100)) FROM numbers(3);
```

```text theme={null}
001
101
201
```

<div id="abi-assemblyscript">
  ### ABI ASSEMBLYSCRIPT
</div>

适用于由 [AssemblyScript](https://www.assemblyscript.org) 编译器生成的模块。每一行都会触发一次对导出函数的调用，将 ClickHouse 值映射为 AssemblyScript 基本类型和字符串对象。

**支持的类型**：

* 数值类型：`Int8`/`UInt8`、`Int16`/`UInt16` (在边界处扩展为 `i32`) 、`Int32`/`UInt32`、`Int64`/`UInt64`、`Float32`、`Float64`

* `String` — 映射为 AssemblyScript `string` (WASM 内存中的 UTF-16) 。ClickHouse 会自动处理 UTF-8 ↔ UTF-16 的转换。

* 不支持将自定义 AssemblyScript 类用作参数类型或返回类型——它们的运行时类 id 在不同编译结果之间并不稳定 (参见 [AssemblyScript#2982](https://github.com/AssemblyScript/assemblyscript/issues/2982)) 。

**模块要求**：

模块必须使用 AssemblyScript 托管运行时进行编译，以便导出 `__new`、`__pin` 和 `__unpin`。标准的输入/输出字符串处理依赖这些。推荐的调用方式：

```bash theme={null}
asc src.ts --runtime incremental --exportRuntime -o src.wasm
```

AssemblyScript 还会导入 `env.abort`，用于处理运行时陷阱 (如内存不足、边界检查等) 。ClickHouse 会自动提供此导入：当触发 `abort` 时，当前查询会因 `WASM_ERROR` Exception 而失败，并包含已解码的 AssemblyScript 消息和源位置。

**示例**:

```typescript theme={null}
// src.ts
export function add(a: u32, b: u32): u32 {
  return a + b;
}

export function greet(name: string): string {
  return "Hello, " + name + "!";
}
```

使用 `asc` 编译并将生成的 `.wasm` 加载到 `system.webassembly_modules` 后，可按如下方式声明 UDF：

```sql theme={null}
CREATE FUNCTION as_add
    LANGUAGE WASM ABI ASSEMBLYSCRIPT
    FROM 'as_example' :: 'add'
    ARGUMENTS (a UInt32, b UInt32) RETURNS UInt32;

CREATE FUNCTION as_greet
    LANGUAGE WASM ABI ASSEMBLYSCRIPT
    FROM 'as_example' :: 'greet'
    ARGUMENTS (name String) RETURNS String;
```

<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)` — 用随机字节填充内存。
* `env.abort(message: i32, fileName: i32, line: i32, column: i32)` — 为与 AssemblyScript 兼容的模块提供。调用它 (或触发会调用它的 AssemblyScript 运行时陷阱) 会以包含已解码消息和源代码位置的 `WASM_ERROR` 异常终止 UDF。未导入 \`env.abort\`\` 的模块不受影响。

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

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

* `webassembly_udf_max_fuel` — 每个 WebAssembly UDF 实例每次执行的 fuel 上限。每条 WebAssembly 指令都会消耗一定数量的 fuel。在传递给 runtime 之前，该值会先按 1024 进行缩放，因此 `webassembly_udf_max_fuel = 1` 对应大约 1024 个 fuel 单位。设为 0 表示没有有限上限。仅适用于每函数设置 `webassembly_udf_enable_fuel` 为 true 的函数，默认即为如此。

* `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)
