> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> 用户自定义函数 (UDFs) 文档

# 用户自定义函数 (UDFs)

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 功能。 <u><a href="/docs/docs/beta-and-experimental-features#experimental-features">了解详情。</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>
            ClickHouse Cloud 不支持此功能
        </div>;
};

export const BetaBadge = ({link, galaxyTrack, galaxyEvent}) => {
  if (link) {
    return <a href={link} target="_blank" rel="noopener noreferrer" className="betaBadge" onClick={galaxyTrack && galaxyEvent ? galaxyOnClick(galaxyEvent) : undefined}>
                <Icon />
                <span>Beta</span>
            </a>;
  }
  return <div className="betaBadge">
            <Icon />
            <span>
                Beta 版功能。 
                <u>
                    <a href="/docs/docs/beta-and-experimental-features#beta-features">
                        了解更多。
                    </a>
                </u>
            </span>
        </div>;
};

ClickHouse 支持多种类型的用户自定义函数 (UDFs) ：

* [可执行 UDFs](#executable-user-defined-functions) 会启动外部程序或脚本 (Python、Bash 等) ，并通过 STDIN / STDOUT 以流式方式向其传输数据块。可用于在无需重新编译 ClickHouse 的情况下集成现有代码或工具。与进程内方案相比，它们的单次调用开销更高，因此更适合较重的逻辑，或需要不同运行时的场景。
* [SQL UDFs](#sql-user-defined-functions) 使用 `CREATE FUNCTION` 通过纯 SQL 定义。它们会被内联/展开到查询计划中 (不存在进程边界) ，因此开销较低，非常适合复用表达式逻辑或简化复杂的计算列。
* [Experimental WebAssembly UDFs](#webassembly-user-defined-functions) 会在服务器进程内的沙箱中运行编译为 WebAssembly 的代码。与外部可执行程序相比，它们的单次调用开销更低；与原生扩展相比，又具备更好的隔离性，因此适合用可编译为 WASM 的语言 (如 C/C++/Rust) 编写自定义算法。
* [Experimental 基于 driver 的可执行 UDFs](#driver-based-executable-user-defined-functions) 允许由运维人员提供的 "driver" 在函数创建时，将 `CREATE FUNCTION ... ENGINE = DriverName(...) AS '...'` 中提供的代码片段转换为可执行 UDF (例如通过编译) 。它们构建在可执行 UDFs 之上，并且需要服务器端 driver 配置。

<div id="executable-user-defined-functions">
  ## 可执行用户自定义函数
</div>

<BetaBadge />

<Note>
  在 ClickHouse Cloud 中，可执行 UDF 目前处于 Public Beta 阶段，并通过 Cloud 控制台 UI 创建。有关 Cloud 特定工作流，请参见 [Cloud 中的用户自定义函数](/docs/zh/products/cloud/features/sql-console-features/user-defined-functions)。
</Note>

ClickHouse 可以调用任意外部可执行程序或脚本来处理数据。

可执行用户自定义函数的配置可位于一个或多个 XML 文件中。
配置路径由 [`user_defined_executable_functions_config`](/docs/zh/reference/settings/server-settings/settings#user_defined_executable_functions_config) 参数指定。

函数配置包含以下设置：

| Parameter                     | Description                                                                                                                                                                                                                                                         | Required | Default Value          |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------------------- |
| `name`                        | 函数名称                                                                                                                                                                                                                                                                | 是        | -                      |
| `command`                     | 要执行的脚本名称；如果 `execute_direct` 为 false，则为要执行的命令                                                                                                                                                                                                                       | 是        | -                      |
| `argument`                    | 参数描述，包括参数的 `type` 以及可选的 `name`。每个参数都在单独的配置项中描述。如果参数名称是用户自定义函数序列化格式的一部分 (例如 [Native](/docs/zh/reference/formats/Native) 或 [JSONEachRow](/docs/zh/reference/formats/JSON/JSONEachRow)) ，则必须指定名称                                                                                 | 是        | `c` + argument\_number |
| `format`                      | 向命令传递参数时使用的 [格式](/docs/zh/reference/formats/index)。命令输出也应使用相同的格式                                                                                                                                                                                                         | 是        | -                      |
| `return_type`                 | 返回值的类型                                                                                                                                                                                                                                                              | 是        | -                      |
| `return_name`                 | 返回值名称。如果返回值名称是用户自定义函数序列化格式的一部分 (例如 [Native](/docs/zh/reference/formats/Native) 或 [JSONEachRow](/docs/zh/reference/formats/JSON/JSONEachRow)) ，则必须指定返回值名称                                                                                                                      | 可选       | `result`               |
| `type`                        | 可执行类型。如果 `type` 设置为 `executable`，则会启动单个命令；如果设置为 `executable_pool`，则会创建命令池                                                                                                                                                                                           | 是        | -                      |
| `max_command_execution_time`  | 处理数据块的最大执行时间 (秒) 。此设置仅对 `executable_pool` 命令有效                                                                                                                                                                                                                      | 可选       | `10`                   |
| `command_termination_timeout` | 管道关闭后，命令应在多少秒内结束执行。超过该时间后，会向执行该命令的进程发送 `SIGTERM`                                                                                                                                                                                                                    | 可选       | `10`                   |
| `command_read_timeout`        | 从命令的 stdout 读取数据的超时时间 (毫秒)                                                                                                                                                                                                                                          | 可选       | `10000`                |
| `command_write_timeout`       | 向命令的 stdin 写入数据的超时时间 (毫秒)                                                                                                                                                                                                                                           | 可选       | `10000`                |
| `pool_size`                   | 命令池大小                                                                                                                                                                                                                                                               | 可选       | `16`                   |
| `send_chunk_header`           | 控制在向进程发送一块数据之前，是否先发送行数                                                                                                                                                                                                                                              | 可选       | `false`                |
| `execute_direct`              | 如果 `execute_direct` = `1`，则会在 [user\_scripts\_path](/docs/zh/reference/settings/server-settings/settings#user_scripts_path) 指定的 user\_scripts 文件夹中查找 `command`。可使用空白字符分隔符指定额外的脚本参数。例如：`script_name arg1 arg2`。如果 `execute_direct` = `0`，则 `command` 会作为参数传递给 `bin/sh -c` | 可选       | `1`                    |
| `lifetime`                    | 函数的重载间隔 (秒) 。如果设置为 `0`，则不会重新加载该函数                                                                                                                                                                                                                                   | 可选       | `0`                    |
| `deterministic`               | 函数是否为确定性的 (即对相同输入返回相同结果)                                                                                                                                                                                                                                            | 可选       | `false`                |
| `stderr_reaction`             | 如何处理命令的 stderr 输出。取值：`none` (忽略) 、`log` (立即记录所有 stderr) 、`log_first` (退出后记录前 4 KiB) 、`log_last` (退出后记录后 4 KiB) 、`throw` (一旦有任何 stderr 输出就立即抛出异常) 。当使用 `log_first` 或 `log_last` 且退出码非零时，stderr 内容会包含在异常消息中                                                           | 可选       | `log_last`             |
| `check_exit_code`             | 如果为 true，ClickHouse 会检查命令的退出码。非零退出码会导致异常                                                                                                                                                                                                                            | 可选       | `true`                 |

命令必须从 `STDIN` 读取参数，并将结果输出到 `STDOUT`。命令必须以迭代方式处理参数。也就是说，处理完一批参数后，它必须等待下一批参数。

<div id="executable-user-defined-functions">
  ## 可执行用户自定义函数
</div>

<div id="examples">
  ## 示例
</div>

<div id="udf-inline">
  ### 来自内联脚本的 UDF
</div>

通过 XML 或 YAML 配置手动创建 `test_function_sum`，并将 `execute_direct` 指定为 `0`。

<Tabs>
  <Tab title="XML">
    文件 `test_function.xml` (默认路径设置下为 `/etc/clickhouse-server/test_function.xml`) 。

    ```xml title="/etc/clickhouse-server/test_function.xml" theme={null}
    <functions>
        <function>
            <type>executable</type>
            <name>test_function_sum</name>
            <return_type>UInt64</return_type>
            <argument>
                <type>UInt64</type>
                <name>lhs</name>
            </argument>
            <argument>
                <type>UInt64</type>
                <name>rhs</name>
            </argument>
            <format>TabSeparated</format>
            <command>cd /; clickhouse-local --input-format TabSeparated --output-format TabSeparated --structure 'x UInt64, y UInt64' --query "SELECT x + y FROM table"</command>
            <execute_direct>0</execute_direct>
            <deterministic>true</deterministic>
        </function>
    </functions>
    ```
  </Tab>

  <Tab title="YAML">
    文件 `test_function.yaml` (默认路径设置下为 `/etc/clickhouse-server/test_function.yaml`) 。

    ```yml title="/etc/clickhouse-server/test_function.yaml" theme={null}
    functions:
      type: executable
      name: test_function_sum
      return_type: UInt64
      argument:
        - type: UInt64
          name: lhs
        - type: UInt64
          name: rhs
      format: TabSeparated
      command: 'cd /; clickhouse-local --input-format TabSeparated --output-format TabSeparated --structure ''x UInt64, y UInt64'' --query "SELECT x + y FROM table"'
      execute_direct: 0
      deterministic: true
    ```
  </Tab>
</Tabs>

<br />

```sql title="Query" theme={null}
SELECT test_function_sum(2, 2);
```

```text title="Result" theme={null}
┌─test_function_sum(2, 2)─┐
│                       4 │
└─────────────────────────┘
```

<div id="udf-python">
  ### 基于 Python 脚本的 UDF
</div>

在此示例中，我们将创建一个 UDF，它从 `STDIN` 读取一个值，并将其作为字符串返回。

使用 XML 或 YAML 配置创建 `test_function`。

<Tabs>
  <Tab title="XML">
    文件 `test_function.xml` (默认路径设置下为 `/etc/clickhouse-server/test_function.xml`) 。

    ```xml title="/etc/clickhouse-server/test_function.xml" theme={null}
    <functions>
        <function>
            <type>executable</type>
            <name>test_function_python</name>
            <return_type>String</return_type>
            <argument>
                <type>UInt64</type>
                <name>value</name>
            </argument>
            <format>TabSeparated</format>
            <command>test_function.py</command>
        </function>
    </functions>
    ```
  </Tab>

  <Tab title="YAML">
    文件 `test_function.yaml` (默认路径设置下为 `/etc/clickhouse-server/test_function.yaml`) 。

    ```yml title="/etc/clickhouse-server/test_function.yaml" theme={null}
    functions:
      type: executable
      name: test_function_python
      return_type: String
      argument:
        - type: UInt64
          name: value
      format: TabSeparated
      command: test_function.py
    ```
  </Tab>
</Tabs>

<br />

在 `user_scripts` 文件夹中创建脚本文件 `test_function.py` (默认路径设置下为 `/var/lib/clickhouse/user_scripts/test_function.py`) 。

```python theme={null}
#!/usr/bin/python3

import sys

if __name__ == '__main__':
    for line in sys.stdin:
        print("Value " + line, end='')
        sys.stdout.flush()
```

```sql title="Query" theme={null}
SELECT test_function_python(toUInt64(2));
```

```text title="Result" theme={null}
┌─test_function_python(2)─┐
│ Value 2                 │
└─────────────────────────┘
```

<div id="udf-stdin">
  ### 从 `STDIN` 读取两个值，并将它们的和作为 JSON 对象返回
</div>

使用 XML 或 YAML 配置，以命名参数和 [JSONEachRow](/docs/zh/reference/formats/JSON/JSONEachRow) 格式创建 `test_function_sum_json`。

<Tabs>
  <Tab title="XML">
    文件 `test_function.xml` (默认路径设置下为 `/etc/clickhouse-server/test_function.xml`) 。

    ```xml title="/etc/clickhouse-server/test_function.xml" theme={null}
    <functions>
        <function>
            <type>executable</type>
            <name>test_function_sum_json</name>
            <return_type>UInt64</return_type>
            <return_name>result_name</return_name>
            <argument>
                <type>UInt64</type>
                <name>argument_1</name>
            </argument>
            <argument>
                <type>UInt64</type>
                <name>argument_2</name>
            </argument>
            <format>JSONEachRow</format>
            <command>test_function_sum_json.py</command>
        </function>
    </functions>
    ```
  </Tab>

  <Tab title="YAML">
    文件 `test_function.yaml` (默认路径设置下为 `/etc/clickhouse-server/test_function.yaml`) 。

    ```yml title="/etc/clickhouse-server/test_function.yaml" theme={null}
    functions:
      type: executable
      name: test_function_sum_json
      return_type: UInt64
      return_name: result_name
      argument:
        - type: UInt64
          name: argument_1
        - type: UInt64
          name: argument_2
      format: JSONEachRow
      command: test_function_sum_json.py
    ```
  </Tab>
</Tabs>

<br />

在 `user_scripts` 文件夹中创建脚本文件 `test_function_sum_json.py` (默认路径设置下为 `/var/lib/clickhouse/user_scripts/test_function_sum_json.py`) 。

```python theme={null}
#!/usr/bin/python3

import sys
import json

if __name__ == '__main__':
    for line in sys.stdin:
        value = json.loads(line)
        first_arg = int(value['argument_1'])
        second_arg = int(value['argument_2'])
        result = {'result_name': first_arg + second_arg}
        print(json.dumps(result), end='\n')
        sys.stdout.flush()
```

```sql title="Query" theme={null}
SELECT test_function_sum_json(2, 2);
```

```text title="Result" theme={null}
┌─test_function_sum_json(2, 2)─┐
│                            4 │
└──────────────────────────────┘
```

<div id="udf-parameters-in-command">
  ### 在 `command` 设置中使用参数
</div>

可执行用户自定义函数可以接收在 `command` 设置中配置的常量参数 (仅适用于 `executable` 类型的用户自定义函数) 。
此外，还需要启用 `execute_direct` 选项，以避免 shell 参数展开漏洞。

<Tabs>
  <Tab title="XML">
    文件 `test_function_parameter_python.xml` (默认路径设置下为 `/etc/clickhouse-server/test_function_parameter_python.xml`) 。

    ```xml title="/etc/clickhouse-server/test_function_parameter_python.xml" theme={null}
    <functions>
        <function>
            <type>executable</type>
            <execute_direct>true</execute_direct>
            <name>test_function_parameter_python</name>
            <return_type>String</return_type>
            <argument>
                <type>UInt64</type>
            </argument>
            <format>TabSeparated</format>
            <command>test_function_parameter_python.py {test_parameter:UInt64}</command>
        </function>
    </functions>
    ```
  </Tab>

  <Tab title="YAML">
    文件 `test_function_parameter_python.yaml` (默认路径设置下为 `/etc/clickhouse-server/test_function_parameter_python.yaml`) 。

    ```yml title="/etc/clickhouse-server/test_function_parameter_python.yaml" theme={null}
    functions:
      type: executable
      execute_direct: true
      name: test_function_parameter_python
      return_type: String
      argument:
        - type: UInt64
      format: TabSeparated
      command: test_function_parameter_python.py {test_parameter:UInt64}
    ```
  </Tab>
</Tabs>

<br />

在 `user_scripts` 文件夹中创建脚本文件 `test_function_parameter_python.py` (默认路径设置下为 `/var/lib/clickhouse/user_scripts/test_function_parameter_python.py`) 。

```python theme={null}
#!/usr/bin/python3

import sys

if __name__ == "__main__":
    for line in sys.stdin:
        print("Parameter " + str(sys.argv[1]) + " value " + str(line), end="")
        sys.stdout.flush()
```

```sql title="Query" theme={null}
SELECT test_function_parameter_python(1)(2);
```

```text title="Result" theme={null}
┌─test_function_parameter_python(1)(2)─┐
│ Parameter 1 value 2                  │
└──────────────────────────────────────┘
```

<div id="udf-shell-script">
  ### 通过 shell 脚本创建 UDF
</div>

在本示例中，我们将创建一个 shell 脚本，把每个值乘以 2。

<Tabs>
  <Tab title="XML">
    文件 `test_function_shell.xml` (默认路径配置下为 `/etc/clickhouse-server/test_function_shell.xml`) 。

    ```xml title="/etc/clickhouse-server/test_function_shell.xml" theme={null}
    <functions>
        <function>
            <type>executable</type>
            <name>test_shell</name>
            <return_type>String</return_type>
            <argument>
                <type>UInt8</type>
                <name>value</name>
            </argument>
            <format>TabSeparated</format>
            <command>test_shell.sh</command>
        </function>
    </functions>
    ```
  </Tab>

  <Tab title="YAML">
    文件 `test_function_shell.yaml` (默认路径配置下为 `/etc/clickhouse-server/test_function_shell.yaml`) 。

    ```yml title="/etc/clickhouse-server/test_function_shell.yaml" theme={null}
    functions:
      type: executable
      name: test_shell
      return_type: String
      argument:
        - type: UInt8
          name: value
      format: TabSeparated
      command: test_shell.sh
    ```
  </Tab>
</Tabs>

<br />

在 `user_scripts` 文件夹中创建脚本文件 `test_shell.sh` (默认路径配置下为 `/var/lib/clickhouse/user_scripts/test_shell.sh`) 。

```bash title="/var/lib/clickhouse/user_scripts/test_shell.sh" theme={null}
#!/bin/bash

while read read_data;
    do printf "$(expr $read_data \* 2)\n";
done
```

```sql title="Query" theme={null}
SELECT test_shell(number) FROM numbers(10);
```

```text title="Result" theme={null}
    ┌─test_shell(number)─┐
 1. │ 0                  │
 2. │ 2                  │
 3. │ 4                  │
 4. │ 6                  │
 5. │ 8                  │
 6. │ 10                 │
 7. │ 12                 │
 8. │ 14                 │
 9. │ 16                 │
10. │ 18                 │
    └────────────────────┘
```

<div id="error-handling">
  ## 错误处理
</div>

如果数据无效，某些函数可能会抛出异常。
此时，查询会被取消，并向客户端返回错误信息。
对于分布式处理，当其中一台服务器上发生异常时，其他服务器也会尝试中止该查询。

<div id="evaluation-of-argument-expressions">
  ## 参数表达式的求值
</div>

在几乎所有编程语言中，对于某些运算符，某个参数可能不会被求值。
通常是运算符 `&&`、`||` 和 `?:`。
在 ClickHouse 中，函数 (运算符) 的参数始终都会被求值。
这是因为系统会一次性对整块列数据进行求值，而不是逐行分别计算。

<div id="performing-functions-for-distributed-query-processing">
  ## 分布式查询处理中函数的执行
</div>

在分布式查询处理中，会尽可能多地在远程服务器上完成查询处理的各个阶段，其余阶段 (合并中间结果及之后的所有操作) 则在请求方服务器上完成。

这意味着，函数可能会在不同的服务器上执行。
例如，在查询 `SELECT f(sum(g(x))) FROM distributed_table GROUP BY h(y),` 中：

* 如果 `distributed_table` 至少有两个分片，则函数 'g' 和 'h' 在远程服务器上执行，而函数 'f' 在请求方服务器上执行。
* 如果 `distributed_table` 只有一个分片，则 'f'、'g' 和 'h' 这几个函数都会在该分片所在的服务器上执行。

函数的结果通常与它在哪台服务器上执行无关。但有时这一点很重要。
例如，使用字典的函数会使用其运行所在服务器上的字典。
另一个例子是 `hostName` 函数，它返回其运行所在服务器的名称，以便在 `SELECT` 查询中按服务器进行 `GROUP BY`。

如果查询中的某个函数是在请求方服务器上执行的，但你需要它在远程服务器上执行，可以将其包裹在 'any' 聚合函数中，或者将其添加到 `GROUP BY` 键中。

<div id="sql-user-defined-functions">
  ## SQL 用户自定义函数
</div>

可使用 [CREATE FUNCTION](/docs/zh/reference/statements/create/function) 语句基于 Lambda 表达式创建自定义函数。要删除这些函数，请使用 [DROP FUNCTION](/docs/zh/reference/statements/drop#drop-function) 语句。

<div id="webassembly-user-defined-functions">
  ## WebAssembly 用户自定义函数
</div>

<CloudNotSupportedBadge />

<ExperimentalBadge />

WebAssembly 用户自定义函数 (WASM UDF) 允许你在 ClickHouse 服务器进程中运行编译为 WebAssembly 的自定义代码。

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

在 ClickHouse 配置中启用 Experimental WebAssembly 支持：

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

将编译好的 WASM 模块插入系统表：

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

使用你的 WASM 模块创建函数：

```sql theme={null}
CREATE FUNCTION my_function
LANGUAGE WASM
ABI ROW_DIRECT
FROM 'my_module'
ARGUMENTS (x UInt32, y UInt32)
RETURNS UInt32;
```

在查询中使用此函数：

```sql theme={null}
SELECT my_function(10, 20);
```

<div id="more-information">
  ### 更多信息
</div>

更多详情请参阅[WebAssembly 用户自定义函数](/docs/zh/reference/functions/regular-functions/wasm_udf)文档。

<div id="driver-based-executable-user-defined-functions">
  ## 基于驱动的可执行用户自定义函数
</div>

<CloudNotSupportedBadge />

<ExperimentalBadge />

<Note>
  这是一项 Experimental 功能，未来的发行版中可能会引入不向后兼容的变更。请通过 [`allow_experimental_executable_udf_drivers`](/docs/zh/reference/settings/server-settings/settings#allow_experimental_executable_udf_drivers) 服务器设置启用它。
</Note>

*驱动* 是由运维方提供的一种适配器，用于将用户编写的代码片段转换为可运行的[可执行 UDF](#executable-user-defined-functions)。当使用 `ENGINE = DriverName(...)` 创建函数时，ClickHouse 会运行该驱动的 `create_command`，并向其传递函数签名和代码主体；驱动会对代码主体进行编译或其他处理，然后输出一份可执行 UDF 配置，供 ClickHouse 存储和加载。

这样，管理员就能为用户提供一种安全且受限的方式，用任意语言定义函数 (例如，在沙箱容器中编译的 C) ，而无需授予他们访问服务器配置文件或文件系统的权限。可用驱动的范围完全由运维方控制。

<div id="enabling-drivers">
  ### 启用驱动
</div>

Driver-based executable UDFs 默认处于禁用状态。要启用它们，请按以下步骤操作：

1. 在服务器配置中启用 Experimental 功能开关：

   ```xml theme={null}
   <clickhouse>
       <allow_experimental_executable_udf_drivers>true</allow_experimental_executable_udf_drivers>
   </clickhouse>
   ```

2. 将 `user_defined_executable_function_drivers_config` 配置为指向一个或多个驱动配置文件 (支持 glob) ，并可选择设置 [`dynamic_user_defined_executable_functions_path`](/docs/zh/reference/settings/server-settings/settings#dynamic_user_defined_executable_functions_path)，即用于存储生成的可执行 UDF 配置的目录：

   ```xml theme={null}
   <clickhouse>
       <user_defined_executable_function_drivers_config>user_defined_executable_function_drivers_config.d/*_driver.xml</user_defined_executable_function_drivers_config>
       <dynamic_user_defined_executable_functions_path>/var/lib/clickhouse/dynamic_user_defined_executable_functions/</dynamic_user_defined_executable_functions_path>
   </clickhouse>
   ```

驱动 registry 会在服务器启动时加载，并在执行 `SYSTEM RELOAD CONFIG` 时刷新，因此无需重启服务器即可添加、更改或移除驱动。

<div id="driver-configuration">
  ### 驱动程序配置
</div>

驱动程序由一个以 `<driver>` 为顶层元素的 XML (或 YAML) 文件定义。支持以下字段：

| Field              | Description                                                                                  | Required |
| ------------------ | -------------------------------------------------------------------------------------------- | -------- |
| `name`             | 驱动程序名称，用于 `CREATE FUNCTION ... ENGINE = <name>(...)`。                                        | 是        |
| `create_command`   | 用于根据代码片段创建 UDF 的程序路径。相对路径会相对于驱动程序配置文件解析。                                                     | 是        |
| `drop_command`     | 删除基于此驱动程序的函数时调用的程序路径。                                                                        | 否        |
| `engine_arguments` | 声明 `ENGINE = DriverName(...)` 中允许使用的参数。每个子元素都是一个参数名；`<required>true</required>` 子元素表示该参数为必填。 | 否        |
| `env`              | 调用驱动程序命令时导出的环境变量。                                                                            | 否        |

驱动程序配置示例：

```xml theme={null}
<clickhouse>
    <driver>
        <name>DockerC</name>
        <create_command>../user_defined_executable_function_drivers/docker_c_create.sh</create_command>
        <drop_command>../user_defined_executable_function_drivers/docker_c_drop.sh</drop_command>
        <engine_arguments>
            <opt_level><required>false</required></opt_level>
        </engine_arguments>
        <env>
            <CLICKHOUSE_C_DRIVER_MEMORY>256m</CLICKHOUSE_C_DRIVER_MEMORY>
            <CLICKHOUSE_C_DRIVER_CPUS>1.0</CLICKHOUSE_C_DRIVER_CPUS>
        </env>
    </driver>
</clickhouse>
```

<div id="driver-invocation-contract">
  #### 驱动调用约定
</div>

运行 `CREATE FUNCTION` 时，会在设置好已配置的 `env` 变量后调用 `create_command`，并传入以下参数：

* `--name <function_name>`
* `--return <return_type>` (如果存在 `RETURNS` 子句)
* `--args <signature>` (如果存在 `ARGUMENTS` 子句) ，其中 signature 是声明的参数列表，例如 `x UInt8, y DateTime`
* 对于在 `ENGINE = DriverName(key = value)` 中提供的每个已声明 engine 参数，都会传入 `--<key> <value>`

用户代码主体 (即 `AS` 后面的文本) 会被发送到该命令的标准输入。该命令必须将可执行 UDF 的配置输出到标准输出。格式会自动检测：以 `<` 开头的输出会被视为 XML，否则视为 YAML。生成配置中定义的函数名必须与正在创建的函数名一致。如果 `create_command` 以非零状态退出，该 statement 会失败，并抛出包含退出码和驱动标准错误输出的异常。

如果存在 `drop_command`，则在删除函数时也会以相同方式调用它 (但不会通过 stdin 传入代码主体) 。

<div id="creating-a-function-with-a-driver">
  ### 创建函数
</div>

```sql theme={null}
CREATE [OR REPLACE] FUNCTION [IF NOT EXISTS] name [ON CLUSTER cluster]
    ARGUMENTS (a UInt8, b String) RETURNS UInt64
    ENGINE = DriverName(key1 = 'value1', key2 = 42)
    AS '...code body...'
```

ClickHouse 运行 driver 的 `create_command`，将生成的配置写入 [`dynamic_user_defined_executable_functions_path`](/docs/zh/reference/settings/server-settings/settings#dynamic_user_defined_executable_functions_path)，随后现有的可执行 UDF 加载器会检测并加载该配置。之后，这个函数就可以像其他函数一样调用。

<div id="dropping-a-function-with-a-driver">
  ### 删除函数
</div>

```sql theme={null}
DROP FUNCTION [IF EXISTS] name [ON CLUSTER cluster]
```

`DROP FUNCTION` 会调用驱动程序的 `drop_command` (如果存在) ，删除生成的动态配置以及每个函数的工作目录，重新加载可执行 UDF 加载器，并删除已持久化的查询。

<div id="driver-persistence-and-restart">
  ### 持久化与重启
</div>

原始查询会以 `ATTACH FUNCTION ...` 语句的形式保存在用户定义 SQL 对象目录中，因此服务器重启后该函数仍会保留。启动时，会直接加载 [`dynamic_user_defined_executable_functions_path`](/docs/zh/reference/settings/server-settings/settings#dynamic_user_defined_executable_functions_path) 中生成的配置，而无需重新运行驱动程序。如果已持久化的 `ATTACH FUNCTION` 没有对应的已生成配置 (例如动态目录丢失了) ，则会重新运行驱动程序来重新创建它。

<div id="driver-limitations">
  ### 限制
</div>

* 该功能为 Experimental，受 `allow_experimental_executable_udf_drivers` 控制。
* 基于 driver 的函数不支持复制型用户自定义函数存储 (`ON CLUSTER` 和 `<user_defined_zookeeper_path>`) ，因为被复制的只有发起查询，不包括生成的制品。
* 对已备份的基于 driver 的函数执行 `RESTORE` 时，会保留查询，但不会重新运行 driver；生成的 configuration 会在重启恢复期间稍后物化。

<div id="example-c-drivers">
  ### C 驱动程序示例
</div>

源码树在 `programs/server/user_defined_executable_function_drivers_config.d/` 下提供了一些概念验证驱动，用于编译并运行 C 函数体。它们仅作为示例，**不会随软件包一起安装**：

* `DockerC` - 在经过沙箱隔离的 Docker 容器内编译并运行代码 (`--network=none --read-only --cap-drop=ALL --security-opt=no-new-privileges`，以及内存/CPU/PID 限制) ，并输出一个 `executable_pool` UDF。
* `GVisorC` - 一种变体，在 [gVisor](https://gvisor.dev/) `runsc` 运行时下运行已编译的可执行文件。
* `UnsafeC` - 直接在主机上编译并运行代码，不使用沙箱。顾名思义，它不提供任何隔离，仅适用于受信任环境和测试。

这些示例驱动程序可作为起点；在将它们暴露给不受信任的用户之前，请先根据您的环境审查并加固其沙箱隔离机制。

<div id="related-content">
  ## 相关文章
</div>

* [ClickHouse Cloud 中的用户自定义函数](https://clickhouse.com/blog/user-defined-functions-clickhouse-udfs)
