Skip to main content

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}