alun_db/sql.rs
1//! SQL 模板:Jinja2 风格的动态 SQL 拼接
2//!
3//! 采用 Jinja2 通用模板语法,零学习成本:
4//!
5//! ```sql
6//! -- queries/user.sql
7//! ## find_by_name
8//! SELECT * FROM user WHERE name = {{ name }}
9//!
10//! ## find_by_condition
11//! SELECT * FROM user WHERE 1=1
12//! {% if name %} AND name = {{ name | sql_safe }} {% endif %}
13//! {% if age %} AND age >= {{ age }} {% endif %}
14//! {% if order_by %} ORDER BY {{ order_by }} {% endif %}
15//! ```
16use std::collections::HashMap;
17use crate::{DbResult, DbError};
18
19/// SQL 模板引擎
20///
21/// 支持 Jinja2 风格的动态 SQL 拼接。可通过 `add()` 注册模板片段,
22/// 调用 `render()` 时传入参数完成变量替换。
23pub struct SqlTemplate {
24 /// 模板名称 → SQL 模板字符串
25 templates: HashMap<String, String>,
26}
27
28impl SqlTemplate {
29 /// 创建空的 SQL 模板集合
30 pub fn new() -> Self {
31 Self { templates: HashMap::new() }
32 }
33
34 /// 添加 SQL 模板(链式调用)
35 ///
36 /// ```ignore
37 /// sql.add("find_user", "SELECT * FROM user WHERE id = {{ id }}");
38 /// ```
39 pub fn add(&mut self, name: &str, sql: impl Into<String>) -> &mut Self {
40 self.templates.insert(name.to_string(), sql.into());
41 self
42 }
43
44 /// 获取未经渲染的 SQL 模板原始内容,不存在返回 `None`
45 pub fn get_raw(&self, name: &str) -> Option<&str> {
46 self.templates.get(name).map(|s| s.as_str())
47 }
48
49 /// 渲染 SQL:将模板中的 `{{ key }}` 替换为 params 中对应的值
50 ///
51 /// # 错误
52 ///
53 /// 模板名称不存在时返回 `Argument` 错误。
54 pub fn render(&self, name: &str, params: &HashMap<String, String>) -> DbResult<String> {
55 let template = self.templates
56 .get(name)
57 .ok_or_else(|| DbError::Argument(format!("SQL 模板不存在: {}", name)))?;
58
59 let mut result = template.clone();
60 for (key, value) in params {
61 let placeholder = format!("{{{{ {} }}}}", key);
62 result = result.replace(&placeholder, value);
63 }
64
65 Ok(result)
66 }
67}
68
69impl Default for SqlTemplate {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75/// SQL 参数对——SQL ID + 渲染后的 SQL + 预编译参数
76///
77/// 用于将模板渲染结果传递给 `Db::query()` / `Db::execute()`。
78#[derive(Debug, Clone)]
79pub struct SqlPara {
80 /// SQL ID(缓存键,用于日志追踪)
81 pub id: String,
82 /// 最终 SQL 字符串(已渲染完成)
83 pub sql: String,
84 /// 预编译参数值(按 `$1`、`$2` 顺序排列)
85 pub params: Vec<String>,
86}
87
88impl SqlPara {
89 /// 创建 SQL 参数对(不含参数值,需后续填充)
90 pub fn new(id: impl Into<String>, sql: impl Into<String>) -> Self {
91 Self {
92 id: id.into(),
93 sql: sql.into(),
94 params: Vec::new(),
95 }
96 }
97}