sqltmplx
sqltmplx
sqltmplx 是一个面向 Go 的 SQL 优先条件模板渲染器。
它作为 dbx/sqltmplx 子包提供(隶属于 dbx),因此通常建议从 dbx 开始阅读与使用。
它把控制逻辑放在 SQL 注释里,保留可执行的 sample literal,并在运行时渲染真正的 bind 变量和参数列表。
包结构
- 核心渲染器:
github.com/DaiYuANg/arcgo/dbx/sqltmplx - 方言契约:
github.com/DaiYuANg/arcgo/dbx/dialect - 内置方言:
github.com/DaiYuANg/arcgo/dbx/dialect/mysqlgithub.com/DaiYuANg/arcgo/dbx/dialect/postgresgithub.com/DaiYuANg/arcgo/dbx/dialect/sqlite
- 校验器契约与注册中心:
github.com/DaiYuANg/arcgo/dbx/sqltmplx/validate - 可选 parser 校验后端:
github.com/DaiYuANg/arcgo/dbx/sqltmplx/validate/mysqlparsergithub.com/DaiYuANg/arcgo/dbx/sqltmplx/validate/postgresparsergithub.com/DaiYuANg/arcgo/dbx/sqltmplx/validate/sqliteparser
这样拆分后,核心包保持轻量。
引入 sqltmplx 不会再默认把全部数据库 parser 依赖拉进同一个模块图里。
支持的模板能力
/*%if expr */ ... /*%end *//*%where */ ... /*%end *//*%set */ ... /*%end */- Doma 风格占位符:
/* Name */'alice' - 切片展开:
/* IDs */(1, 2, 3) - 表达式辅助函数:
empty(x)、blank(x)、present(x) - 结构体绑定优先按字段名,再按
sqltmpl、db、json别名匹配
快速开始
package main
import (
"fmt"
"github.com/DaiYuANg/arcgo/dbx/sqltmplx"
"github.com/DaiYuANg/arcgo/dbx/dialect/postgres"
"github.com/DaiYuANg/arcgo/dbx/sqltmplx/validate"
_ "github.com/DaiYuANg/arcgo/dbx/sqltmplx/validate/postgresparser"
)
type Query struct {
Tenant string `db:"tenant"`
Name string `json:"name"`
IDs []int `json:"ids"`
}
func main() {
engine := sqltmplx.New(
postgres.New(),
sqltmplx.WithValidator(validate.NewSQLParser(postgres.New())),
)
tpl := `
SELECT id, tenant, name
FROM users
/*%where */
/*%if present(Tenant) */
AND tenant = /* Tenant */'acme'
/*%end */
/*%if present(Name) */
AND name = /* Name */'alice'
/*%end */
/*%if !empty(IDs) */
AND id IN (/* IDs */(1, 2, 3))
/*%end */
/*%end */
ORDER BY id DESC
`
bound, err := engine.Render(tpl, Query{
Tenant: "acme",
Name: "alice",
IDs: []int{1, 2, 3},
})
if err != nil {
panic(err)
}
fmt.Println(bound.Query)
fmt.Println(bound.Args)
}校验模型
如果你想按方言名自动选择校验器,使用 validate.NewSQLParser(dialect)。
它基于注册机制工作,所以程序里必须显式导入具体后端包。
例如:
import (
"github.com/DaiYuANg/arcgo/dbx/sqltmplx/validate"
_ "github.com/DaiYuANg/arcgo/dbx/sqltmplx/validate/mysqlparser"
)如果你不想走注册中心,也可以直接传具体校验器实现:
sqltmplx.WithValidator(mysqlparser.New())Registry 语句复用
通过 MustStatement 或 Statement 复用语句,避免重复解析。 Registry 按名称缓存已编译模板。在初始化或首次使用时获取 statement,再在热路径中传给 dbx.SQLList、dbx.SQLGet、session.SQL().Exec 等:
// 推荐:构建一次,多次执行
registry := sqltmplx.NewRegistry(sqlFS, core.Dialect())
stmt := registry.MustStatement("sql/user/find_active.sql")
for range batches {
items, _ := dbx.SQLList(ctx, session, stmt, params, mapper)
// ...
}
// 避免:每次调用都解析
for range batches {
items, _ := dbx.SQLList(ctx, session, registry.MustStatement("sql/user/find_active.sql"), params, mapper)
}编译一次,多次渲染
engine := sqltmplx.New(mysql.New())
tpl, err := engine.Compile(queryText)
if err != nil {
panic(err)
}
bound1, _ := tpl.Render(map[string]any{"Tenant": "acme", "Status": "PAID"})
bound2, _ := tpl.Render(map[string]any{"Tenant": "acme", "Status": "SHIPPED"})
fmt.Println(bound1.Query)
fmt.Println(bound2.Query)示例
- 示例说明页:sqltmplx examples
- 可运行示例:
相关 dbx 文档
- dbx 快速开始:/docs/dbx/getting-started/
- dbx schema 声明:/docs/dbx/schema-design/
- dbx ID 策略:/docs/dbx/id-generation/
- dbx 索引配置:/docs/dbx/indexes/
- dbx + sqltmplx 集成:/docs/dbx/sqltmplx/
测试与 Benchmark
go test ./dbx/sqltmplx/...
go test ./dbx/sqltmplx -run ^$ -bench . -benchmem
go test ./dbx/sqltmplx/render -run ^$ -bench . -benchmem
go test ./dbx/sqltmplx/validate/mysqlparser -run ^$ -bench . -benchmem