halo-sqlx 0.2.2

一个可组合的 SQL 拼接与参数收集库(参考 go-sqlbuilder 的设计)
Documentation

halo sqlx

它是一个对齐 huandu/go-sqlbuilder 设计的 Rust crate,提供:

  • Args + Flavor:支持 ?$1@p1:1 等多种占位符策略,并且允许通过 Flavor 跟随不同 SQL 方言;
  • 完整的各类 Builder:SelectBuilderInsertBuilderUpdateBuilderDeleteBuilderUnionBuilderCTEBuilderCTEQueryBuilderCreateTableBuilder,同时内建查、插、改、删、聚合、CTE、Union 和 clone 重用模式;
  • Build/Buildf/BuildNamed:支持 ${name}$0$?$$RawListTuple 等语法,并支持嵌套 builder、named arg 重用、literal $ 等特殊行为;
  • Struct + field_mapper:通过 macro_rules! 生成 FieldMeta,支持 db/fieldtag/fieldopt/fieldaswith_tag/without_tag、自定义 field mapper(如 snake_case/kebab_case/prefix/suffix)并兼容 SqlValuer
  • Scan + ScanCell:仿照 Go 的 Addr 实现数据扫描;
  • interpolate:为不支持参数化的驱动提供 SQL 插值,涵盖多 flavor 的字符串/数字/日期/布尔等转义;
  • SqlValuer:支持延迟计算参数,兼容自定义数据源;
  • 全部示例/单测对齐 Go:138 条单测 + doc-test,覆盖 README 中的 builder、Struct、CTE、Union、field mapper、命名参数等场景。

典型用法

创建 SELECT

use halo::sqlx::{
    macros::{from_tables, select_cols, where_exprs},
    select::SelectBuilder,
};

let mut sb = SelectBuilder::new();
select_cols!(sb, "id");
from_tables!(sb, "user");
where_exprs!(sb, sb.in_("status", [1_i64, 2, 3]));

let (sql, args) = sb.build();
assert_eq!(sql, "SELECT id FROM user WHERE status IN (?, ?, ?)");
assert_eq!(args.len(), 3);

变长参数宏

halo::sqlx::macros 模块提供一组 Go 风格的可变参数宏(select_cols!from_tables!where_exprs!returning_cols! 等),它们会自动把多个字符串/列名展开为 Vec<String>,从而无需手动构造切片。

use halo::sqlx::{
    macros::{from_tables, order_by_cols, select_cols, where_exprs},
    select::SelectBuilder,
};

let mut sb = SelectBuilder::new();
select_cols!(sb, "id", "name");
from_tables!(sb, "users");
where_exprs!(sb, "status = 'active'", "type <> 'guest'");
order_by_cols!(sb, "name");

let (sql, _) = sb.build();
assert!(sql.contains("WHERE"));

宏还覆盖了 insert_cols! / insert_select_cols! / delete_from_tables! / update_set! / create_table_define! / struct_with_tag! 等常见接受字符串 varargs 的接口。

嵌套 Builder / Buildf

use halo::sqlx::{
    builder::buildf,
    macros::{from_tables, select_cols},
    select::SelectBuilder,
};

let mut sb = SelectBuilder::new();
select_cols!(sb, "id");
from_tables!(sb, "user");

let explain = buildf(
    "EXPLAIN %v LEFT JOIN SELECT * FROM banned WHERE state IN (%v, %v)",
    [sb.into(), 1_i64, 2_i64],
);
let (sql, _) = explain.build();
assert!(sql.contains("EXPLAIN SELECT id FROM user"));

named 参数

use halo::sqlx::{
    builder::build_named,
    modifiers::{SqlNamedArg, raw, list},
};

let mut named = std::collections::HashMap::new();
named.insert("table".to_string(), raw("user"));
named.insert("status".to_string(), list([1_i64, 2, 3]));
named.insert("time".to_string(), SqlNamedArg::new("start", 1_514_458_225_i64).into());

let (sql, args) = build_named(
    "SELECT * FROM ${table} WHERE status IN (${status}) AND created_at > ${time}",
    named,
)
.build();
assert!(sql.contains("@start"));

Struct ORM + field mapper

use halo::sqlx::{field_mapper::snake_case_mapper, Struct};

let _= halo::sqlx::field_mapper::set_default_field_mapper_scoped(std::sync::Arc::new(snake_case_mapper));

halo::sqlx::sql_struct! {
    impl User {
        id: { db: "id", tags: [], omitempty: [], quote: false, as: None },
        user_name: { db: "", tags: [], omitempty: [], quote: false, as: None }
    }
}

    let s = Struct::<User>::new();
let (sql, _) = s.select_from("user").build();
assert!(sql.contains("user.user_name"));

CTE 与 Union

use halo::sqlx::{
    cte::with,
    cte_query::CTEQueryBuilder,
    macros::{from_tables, select_cols, where_exprs},
    select::SelectBuilder,
};

let mut users_cte = CTEQueryBuilder::new();
let mut query = SelectBuilder::new();
select_cols!(query, "id");
from_tables!(query, "users");
where_exprs!(query, "name IS NOT NULL");
users_cte.table("users", ["id"]).as_(query);

let cte = with([users_cte]);
let mut sb = cte.select(Vec::<String>::new());
select_cols!(sb, "users.id");
from_tables!(sb, "users");
let (sql, _) = sb.build();
assert!(sql.contains("WITH users"));

维护与测试

cargo fmt
cargo clippy --all-targets --all-features -- -D warnings
cargo test

许可证

MIT