29 个版本

0.5.5 2024年5月7日
0.5.4 2023年9月10日
0.5.3 2023年3月29日
0.5.0 2022年12月20日
0.3.10 2022年11月13日

#122数据库接口

Download history 55/week @ 2024-04-14 102/week @ 2024-04-21 40/week @ 2024-04-28 260/week @ 2024-05-05 45/week @ 2024-05-12 82/week @ 2024-05-19 23/week @ 2024-05-26 108/week @ 2024-06-02 106/week @ 2024-06-09 11/week @ 2024-06-16 21/week @ 2024-06-23 16/week @ 2024-06-30 33/week @ 2024-07-07 38/week @ 2024-07-14 24/week @ 2024-07-21 38/week @ 2024-07-28

135 每月下载量
mysql_roaring 中使用

Apache-2.0 OR GPL-2.0-or-later

150KB
2.5K SLoC

UDF:Rust 中 MariaDB/MySQL 用户定义函数

本库旨在以最小错误的方式,使 SQL 用户定义函数 (UDF) 的实现变得极其简单。

寻找预先编写的有用 UDF?请查看 UDF 套件,它提供了一些有用函数的可下载二进制文件:https://github.com/pluots/udf-suite

在此处查看文档:https://docs.rs/udf/latest

UDF 理论

基本的 SQL UDF 包含三个公开函数

  • 一个初始化函数,用于检查参数并分配内存
  • 一个处理函数,返回一个结果
  • 一个析构函数,清理堆上的任何内容(在此库中自动执行)

聚合 UDF(一次处理多行的函数)只需注册两个到三个额外的函数。

此库处理编写 UDF 时所有原本困难的部分(动态注册、分配/释放、错误处理、可空值、日志记录),使得向您的 SQL 服务器实例添加任何函数变得 极其简单。它还包括一个模拟接口,以便在不需要服务器的情况下测试您的函数实现。

快速入门

使用此库创建工作 UDF 的步骤如下

  • 创建一个新的 rust 项目(cargo new --lib my-udf),将 udf 添加为依赖项(cargo add udf),并将 crate 类型更改为 cdylib,在 Cargo.toml 中添加以下内容

    [lib]
    crate-type = ["cdylib"]
    
  • 创建一个结构体或枚举,用于在初始化和处理步骤之间共享数据(它可以空)。您的 UDF 的默认名称将是您的结构体名称转换为蛇形命名法。

  • 在此结构体上实现 BasicUdf 特性

  • 如果您想将其实现为聚合函数,请实现 AggregateUdf 特性。

  • 将这些 impl 块添加 #[udf::register](可选地带有 (name = "my_name") 参数)

  • 使用 cargo build --release 编译项目(输出将位于 target/release/libmy_udf.so

  • 使用 CREATE FUNCTION ... 将结构加载到 MariaDB/MySql 中

  • 在 SQL 中使用该函数!

有关使用此库编写的某些 UDF 的示例,请参阅 udf-examples/ 目录或 udf-suite 存储库。

详细概述

本节将详细介绍使用此库实现 UDF 的细节,但并非详尽无遗。有关详细信息,请参阅文档或 udf-examples 目录中的注释良好的示例。

结构创建

第一步是创建一个结构(或枚举),它将用于在所有相关 SQL 函数之间共享数据。这些包括

  • init 每个结果集调用一次。在这里,您可以存储结构中的常量数据(如果适用)
  • process 每行调用一次(对于聚合函数,为每组调用一次)。此函数使用结构中的数据和当前行的参数中的数据
  • clear 仅聚合,在每组开始时调用一次。根据需要重置结构。
  • add 仅聚合,在组内的每行调用一次。执行所需的计算并将数据保存到结构中。
  • remove 窗口函数仅,用于从组中删除一个值

对于简单函数,尤其是没有需要共享的数据的情况,这是完全可能的。在这种情况下,只需创建一个空的结构,就不会发生分配。

/// Function `sum_int` just adds all arguments as integers and needs no shared data
struct SumInt;

/// Function `avg` on the other hand may want to save data to perform aggregation
struct Avg {
    running_total: f64
}

对于返回缓冲区的函数(字符串和十进制函数):如果字符串长度超过 MYSQL_RESULT_BUFFER_SIZE(255),则要返回的字符串必须包含在结构中(如果适用,则 process 函数将返回一个引用)。

/// Generate random lipsum that may be longer than 255 bytes
struct Lipsum {
    res: String
}

特质实现

下一步是实现 BasicUdf 和可选的 AggregateUdf 特性。有关更多信息,请参阅 文档

如果您在使用 IDE 时使用 rust-analyzer,它可以帮到您。只需输入 impl BasicUdf for MyStruct {} 并将光标放在括号之间 - 它将提供自动填充函数骨架的选项(如果默认情况下未显示,则 ctrl+.cmd+. 会弹出此菜单)。

use udf::prelude::*;

struct SumInt;

#[register]
impl BasicUdf for SumInt {
    type Returns<'a> = Option<i64>;

    fn init<'a>(
      cfg: &UdfCfg<Init>,
      args: &'a ArgList<'a, Init>
    ) -> Result<Self, String> {
      // ...
    }

    fn process<'a>(
        &'a mut self,
        cfg: &UdfCfg<Process>,
        args: &ArgList<Process>,
        error: Option<NonZeroU8>,
    ) -> Result<Self::Returns<'a>, ProcessError> {
      // ...
    }
}

编译

假设以上步骤已执行,所需做的只是为该项目生成一个C动态库。这可以通过在您的Cargo.toml文件中指定crate-type = ["cdylib"]来完成。之后,使用cargo build --release编译将会生成一个可加载的.so文件(位于target/release)。

重要版本说明:此crate依赖于名为泛型关联类型(GATs)的功能,该功能仅在rust >= 1.65的版本中可用。此版本刚刚稳定(2022-11-03),因此如果您遇到编译器问题,请务必运行rustup update

CI在Linux和Windows上都运行测试,因此此crate应适用于任一平台。MacOS尚未经过测试,但可能也能正常工作。

符号检查

如果您想验证是否存在正确的C可调用函数,可以使用nm检查动态库。

# Output of example .so
$ nm -gC --defined-only target/release/libudf_examples.so
00000000000081b0 T avg_cost
0000000000008200 T avg_cost_add
00000000000081e0 T avg_cost_clear
0000000000008190 T avg_cost_deinit
0000000000008100 T avg_cost_init
0000000000009730 T is_const
0000000000009710 T is_const_deinit
0000000000009680 T is_const_init
0000000000009320 T sql_sequence
...

使用方法

编译完成后,需要将生成的目标文件复制到SQL变量plugin_dir的位置 - 通常这是/usr/lib/mysql/plugin/

完成此操作后,在MariaDB/MySql中可以使用CREATE FUNCTION来加载它。

Docker使用

强烈建议在Docker中进行测试,以避免干扰主机SQL安装。有关如何操作的说明,请参阅udf-examples的readme

示例

udf-examples crate包含了各种UDF的示例以及如何编译它们的说明。有关详细信息,请参阅该readme

日志记录与调试说明

如果您需要在函数的正常使用过程中记录诸如警告之类的信息,任何打印到stderr的内容都将出现在服务器日志中(例如,如果使用Docker进行测试,可以通过docker logs mariadb_udf_test查看)。udf_log!宏将打印与SQL日志信息格式匹配的消息。您还可以启用crate功能logging-debug以进行函数入口/退出点调试,或启用logging-debug-calls以获取来自MariaDB/MySQL服务器的确切调用参数信息。

调试的最佳方法是使用udf::mock模块创建单元测试。这些测试可以运行以验证正确性,如果需要,也可以使用调试器逐步执行(这种情况可能相对较少)。所有类型都实现了Debug,因此也可以轻松打印(内置的dbg!宏将打印到stderr,因此这也会出现在日志中)。

dbg!(&self);
let arg0 = dbg!(args.get(0).unwrap())
[udf_examples/src/avgcost.rs:58] &self = AvgCost {
    count: 0,
    total_qty: 0,
    total_price: 0.0,
}

[udf_examples/src/avgcost.rs:60] args.get(0).unwrap() = SqlArg {
    value: Int(
        Some(
            10,
        ),
    ),
    attribute: "qty",
    maybe_null: true,
    arg_type: Cell {
        value: INT_RESULT,
    },
    marker: PhantomData<udf::traits::Process>,
}

许可证

自版本0.5.1起,此作品采用Apache 2.0和GPL 2.0(或任何后续版本)双重许可。如果您使用此作品,可以选择其中之一。

SPDX-许可证-标识符:Apache-2.0 GPL-2.0--后续版本

依赖关系

~1.3-2MB
~36K SLoC