Skip to main content

sz_orm_core/
lambda.rs

1//! Lambda 类型安全 Wrapper
2//!
3//! 对应文档 6.8 节改进项 38(Lambda 类型安全 Wrapper)。
4//!
5//! # 核心概念
6//!
7//! - **Column**:字段标记 trait,关联 Model 类型 M,提供字段名与表名
8//! - **`LambdaWrapper<M>`**:类型安全的查询构造器,所有字段引用都通过 `Column` 类型而非 `&str`
9//! - **define_columns!**:宏,为 Model 定义所有字段的类型安全标记
10//!
11//! # 设计灵感
12//!
13//! - MyBatis-Plus `LambdaQueryWrapper` / `LambdaUpdateWrapper`
14//! - JOOQ 类型安全 DSL
15//! - Diesel 强类型 schema
16//! - SeaORM `Column` trait
17//!
18//! # 优势
19//!
20//! 1. **编译期字段名检查**:拼写错误直接编译失败
21//! 2. **IDE 自动补全**:`UserColumns::` 后可补全所有字段
22//! 3. **重构友好**:字段重命名后所有引用处编译失败,便于定位修改
23//! 4. **表名隔离**:不同 Model 的字段不会混淆
24//!
25//! # 使用示例
26//!
27//! ```
28//! use sz_orm_core::lambda::{LambdaWrapper, Column};
29//! use sz_orm_core::define_columns;
30//! use sz_orm_core::Value;
31//!
32//! // 1. 定义 Model(示例)
33//! struct User;
34//!
35//! // 2. 为 User 定义字段标记
36//! define_columns! {
37//!     UserColumns for User table = "users" {
38//!         Id => "id",
39//!         Name => "name",
40//!         Age => "age",
41//!     }
42//! }
43//!
44//! // 3. 使用 LambdaWrapper 构造类型安全查询
45//! let mut wrapper = LambdaWrapper::<User>::new("users");
46//! wrapper
47//!     .select(UserColumns::Id)
48//!     .select(UserColumns::Name)
49//!     .eq(UserColumns::Id, Value::I64(1))
50//!     .gt(UserColumns::Age, Value::I64(18));
51//!
52//! let sql = wrapper.build_select();
53//! assert!(sql.contains("SELECT `id`, `name`"));
54//! assert!(sql.contains("FROM `users`"));
55//! assert!(sql.contains("`id` = 1"));
56//! assert!(sql.contains("`age` > 18"));
57//! ```
58
59use crate::dialect::{Dialect, MySqlDialect};
60use crate::Value;
61use std::marker::PhantomData;
62
63// ============================================================================
64// Column trait — 字段标记
65// ============================================================================
66
67/// 字段标记 trait
68///
69/// 实现此 trait 的类型作为 Model 字段的类型安全引用。
70/// 一个 `Column<M>` 实例携带:
71/// - 字段名(`name()`)
72/// - 所属表名(`table()`,从 Model 关联)
73///
74/// # 实现方式
75///
76/// 通常通过 `define_columns!` 宏自动生成实现,无需手动实现。
77pub trait Column<M>: Send + Sync + Clone {
78    /// 返回字段名
79    fn name(&self) -> &'static str;
80
81    /// 返回字段所属的表名
82    fn table(&self) -> &'static str;
83}
84
85// ============================================================================
86// WhereClause — WHERE 条件子句
87// ============================================================================
88
89/// WHERE 条件子句(内部表示)
90#[derive(Debug, Clone)]
91pub enum WhereClause {
92    /// `col = value`
93    Eq(String, Value),
94    /// `col != value`
95    Ne(String, Value),
96    /// `col > value`
97    Gt(String, Value),
98    /// `col >= value`
99    Ge(String, Value),
100    /// `col < value`
101    Lt(String, Value),
102    /// `col <= value`
103    Le(String, Value),
104    /// `col LIKE value`
105    Like(String, Value),
106    /// `col IS NULL`
107    IsNull(String),
108    /// `col IS NOT NULL`
109    IsNotNull(String),
110    /// `col IN (v1, v2, ...)`
111    In(String, Vec<Value>),
112    /// `col NOT IN (v1, v2, ...)`
113    NotIn(String, Vec<Value>),
114    /// `col BETWEEN v1 AND v2`
115    Between(String, Value, Value),
116    /// 原始 SQL(用于 OR 等复杂条件)
117    Raw(String),
118}
119
120impl WhereClause {
121    /// 渲染为 SQL 片段(不带前缀 AND/OR)
122    fn render(&self, dialect: &dyn Dialect) -> String {
123        match self {
124            // v0.2.2 修复 H-1:使用方言感知的转义
125            WhereClause::Eq(col, v) => format!(
126                "{} = {}",
127                dialect.quote(col),
128                v.to_param_with_dialect(dialect)
129            ),
130            WhereClause::Ne(col, v) => format!(
131                "{} != {}",
132                dialect.quote(col),
133                v.to_param_with_dialect(dialect)
134            ),
135            WhereClause::Gt(col, v) => format!(
136                "{} > {}",
137                dialect.quote(col),
138                v.to_param_with_dialect(dialect)
139            ),
140            WhereClause::Ge(col, v) => format!(
141                "{} >= {}",
142                dialect.quote(col),
143                v.to_param_with_dialect(dialect)
144            ),
145            WhereClause::Lt(col, v) => format!(
146                "{} < {}",
147                dialect.quote(col),
148                v.to_param_with_dialect(dialect)
149            ),
150            WhereClause::Le(col, v) => format!(
151                "{} <= {}",
152                dialect.quote(col),
153                v.to_param_with_dialect(dialect)
154            ),
155            WhereClause::Like(col, v) => format!(
156                "{} LIKE {}",
157                dialect.quote(col),
158                v.to_param_with_dialect(dialect)
159            ),
160            WhereClause::IsNull(col) => format!("{} IS NULL", dialect.quote(col)),
161            WhereClause::IsNotNull(col) => format!("{} IS NOT NULL", dialect.quote(col)),
162            WhereClause::In(col, vs) => {
163                let values: Vec<String> = vs
164                    .iter()
165                    .map(|v| v.to_param_with_dialect(dialect).to_string())
166                    .collect();
167                format!("{} IN ({})", dialect.quote(col), values.join(", "))
168            }
169            WhereClause::NotIn(col, vs) => {
170                let values: Vec<String> = vs
171                    .iter()
172                    .map(|v| v.to_param_with_dialect(dialect).to_string())
173                    .collect();
174                format!("{} NOT IN ({})", dialect.quote(col), values.join(", "))
175            }
176            WhereClause::Between(col, a, b) => format!(
177                "{} BETWEEN {} AND {}",
178                dialect.quote(col),
179                a.to_param_with_dialect(dialect),
180                b.to_param_with_dialect(dialect)
181            ),
182            WhereClause::Raw(sql) => sql.clone(),
183        }
184    }
185}
186
187// ============================================================================
188// OrderBy — 排序子句
189// ============================================================================
190
191/// 排序方向
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193pub enum OrderDirection {
194    /// 升序
195    Asc,
196    /// 降序
197    Desc,
198}
199
200/// 排序子句
201#[derive(Debug, Clone)]
202pub struct OrderBy {
203    /// 字段名
204    pub column: String,
205    /// 排序方向
206    pub direction: OrderDirection,
207}
208
209// ============================================================================
210// LambdaWrapper — 类型安全查询构造器
211// ============================================================================
212
213/// Lambda 类型安全查询构造器
214///
215/// 泛型参数 `M` 是 Model 类型(仅用于类型隔离,不实际存储实例)。
216///
217/// # 字段引用方式
218///
219/// 与原始 `QueryBuilder` 使用 `&str` 字段名不同,`LambdaWrapper` 接受 `Column<M>` 实例,
220/// 从而在编译期检查字段拼写错误。
221///
222/// # 示例
223///
224/// ```
225/// use sz_orm_core::lambda::{LambdaWrapper, Column};
226/// use sz_orm_core::define_columns;
227/// use sz_orm_core::Value;
228///
229/// struct User;
230/// define_columns! {
231///     UserColumns for User table = "users" {
232///         Id => "id",
233///         Name => "name",
234///     }
235/// }
236///
237/// let mut w = LambdaWrapper::<User>::new("users");
238/// w.eq(UserColumns::Id, Value::I64(1));
239/// ```
240pub struct LambdaWrapper<M> {
241    /// 表名
242    table: String,
243    /// SELECT 字段列表(空表示 SELECT *)
244    selects: Vec<String>,
245    /// WHERE 条件列表(AND 连接)
246    wheres: Vec<WhereClause>,
247    /// ORDER BY 子句
248    orders: Vec<OrderBy>,
249    /// LIMIT
250    limit: Option<u64>,
251    /// OFFSET
252    offset: Option<u64>,
253    /// 数据库方言
254    dialect: Box<dyn Dialect>,
255    /// P3-2 修复:软删除配置
256    /// - `Some(column)`:启用软删除,build_select/count/exists 自动追加 `column = 0`
257    ///   build_delete 改为 `UPDATE ... SET column = 1`
258    /// - `None`:未启用软删除(默认)
259    soft_delete: Option<SoftDeleteConfig>,
260    /// P3-2 修复:多租户配置
261    /// - `Some(config)`:启用多租户过滤,build_select/count/exists/delete 自动追加 `column = tenant_id`
262    /// - `None`:未启用多租户(默认)
263    tenant: Option<TenantConfig>,
264    _marker: PhantomData<M>,
265}
266
267/// 软删除配置
268#[derive(Debug, Clone)]
269pub struct SoftDeleteConfig {
270    /// 软删除标记列名(如 "deleted"、"is_deleted"、"deleted_at")
271    pub column: String,
272    /// 未删除时的值(通常为 0 / false / NULL)
273    pub not_deleted_value: Value,
274    /// 已删除时的值(通常为 1 / true / 当前时间字符串)
275    pub deleted_value: Value,
276}
277
278/// 多租户配置
279#[derive(Debug, Clone)]
280pub struct TenantConfig {
281    /// 租户列名(如 "tenant_id")
282    pub column: String,
283    /// 当前租户 ID
284    pub tenant_id: Value,
285}
286
287impl<M> LambdaWrapper<M> {
288    /// 创建 LambdaWrapper,默认使用 MySQL 方言
289    pub fn new(table: impl Into<String>) -> Self {
290        Self {
291            table: table.into(),
292            selects: Vec::new(),
293            wheres: Vec::new(),
294            orders: Vec::new(),
295            limit: None,
296            offset: None,
297            dialect: Box::new(MySqlDialect),
298            soft_delete: None,
299            tenant: None,
300            _marker: PhantomData,
301        }
302    }
303
304    /// 创建 LambdaWrapper 并指定方言
305    pub fn with_dialect(table: impl Into<String>, dialect: Box<dyn Dialect>) -> Self {
306        Self {
307            table: table.into(),
308            selects: Vec::new(),
309            wheres: Vec::new(),
310            orders: Vec::new(),
311            limit: None,
312            offset: None,
313            dialect,
314            soft_delete: None,
315            tenant: None,
316            _marker: PhantomData,
317        }
318    }
319
320    // -------------------- P3-2 修复:软删除 / 多租户配置 --------------------
321
322    /// 启用软删除支持
323    ///
324    /// 启用后:
325    /// - `build_select` / `build_count` / `build_exists`:自动追加 `column = not_deleted_value`
326    /// - `build_delete`:改为 `UPDATE ... SET column = deleted_value`
327    ///
328    /// # 示例
329    ///
330    /// ```
331    /// use sz_orm_core::lambda::{LambdaWrapper, SoftDeleteConfig};
332    /// use sz_orm_core::Value;
333    ///
334    /// struct User;
335    /// let mut w = LambdaWrapper::<User>::new("users")
336    ///     .with_soft_delete(SoftDeleteConfig {
337    ///         column: "deleted".to_string(),
338    ///         not_deleted_value: Value::I64(0),
339    ///         deleted_value: Value::I64(1),
340    ///     });
341    /// ```
342    pub fn with_soft_delete(mut self, config: SoftDeleteConfig) -> Self {
343        // 校验列名为合法标识符
344        if crate::sql_safety::validate_identifier(&config.column, "soft_delete column").is_ok() {
345            self.soft_delete = Some(config);
346        }
347        self
348    }
349
350    /// 启用多租户过滤
351    ///
352    /// 启用后:
353    /// - `build_select` / `build_count` / `build_exists` / `build_delete`:
354    ///   自动追加 `column = tenant_id`
355    ///
356    /// # 示例
357    ///
358    /// ```
359    /// use sz_orm_core::lambda::{LambdaWrapper, TenantConfig};
360    /// use sz_orm_core::Value;
361    ///
362    /// struct User;
363    /// let mut w = LambdaWrapper::<User>::new("users")
364    ///     .with_tenant(TenantConfig {
365    ///         column: "tenant_id".to_string(),
366    ///         tenant_id: Value::I64(42),
367    ///     });
368    /// ```
369    pub fn with_tenant(mut self, config: TenantConfig) -> Self {
370        // 校验列名为合法标识符
371        if crate::sql_safety::validate_identifier(&config.column, "tenant column").is_ok() {
372            self.tenant = Some(config);
373        }
374        self
375    }
376
377    /// 收集所有隐式 WHERE 条件(软删除 + 多租户)
378    /// 返回 (conditions, 是否非空)
379    fn collect_implicit_wheres(&self) -> Vec<String> {
380        let mut implicit = Vec::new();
381        if let Some(ref sd) = self.soft_delete {
382            let col = self.dialect.quote(&sd.column);
383            let val = sd.not_deleted_value.to_param_with_dialect(self.dialect.as_ref());
384            implicit.push(format!("{} = {}", col, val));
385        }
386        if let Some(ref t) = self.tenant {
387            let col = self.dialect.quote(&t.column);
388            let val = t.tenant_id.to_param_with_dialect(self.dialect.as_ref());
389            implicit.push(format!("{} = {}", col, val));
390        }
391        implicit
392    }
393
394    /// 渲染 WHERE 子句(包含用户 wheres + 隐式软删除/多租户条件)
395    fn render_where_clause(&self) -> String {
396        let user_conds: Vec<String> = self
397            .wheres
398            .iter()
399            .map(|w| w.render(self.dialect.as_ref()))
400            .collect();
401        let implicit_conds = self.collect_implicit_wheres();
402        let all_conds: Vec<String> = user_conds.into_iter().chain(implicit_conds).collect();
403        if all_conds.is_empty() {
404            String::new()
405        } else {
406            format!(" WHERE {}", all_conds.join(" AND "))
407        }
408    }
409
410    // -------------------- SELECT 字段 --------------------
411
412    /// 添加 SELECT 字段(类型安全)
413    ///
414    /// M-4 修复:对列名进行 `validate_identifier` 校验,防止恶意实现 `Column` trait
415    /// 注入非法标识符。
416    pub fn select<C: Column<M>>(&mut self, col: C) -> &mut Self {
417        let name = col.name();
418        // 校验列名为合法 SQL 标识符(非空、仅 ASCII 字母数字+下划线、不以数字开头)
419        // 校验失败时跳过该列(保留向后兼容,不中断调用链)
420        if crate::sql_safety::validate_identifier(name, "lambda select column").is_ok() {
421            self.selects.push(name.to_string());
422        }
423        self
424    }
425
426    /// 批量添加 SELECT 字段
427    ///
428    /// M-4 修复:同 `select`,对每个列名校验。
429    pub fn select_many<C: Column<M>>(&mut self, cols: &[C]) -> &mut Self {
430        for c in cols {
431            let name = c.name();
432            if crate::sql_safety::validate_identifier(name, "lambda select column").is_ok() {
433                self.selects.push(name.to_string());
434            }
435        }
436        self
437    }
438
439    /// SELECT *(清空已有字段选择)
440    pub fn select_all(&mut self) -> &mut Self {
441        self.selects.clear();
442        self
443    }
444
445    // -------------------- WHERE 条件 --------------------
446
447    /// `col = value`
448    pub fn eq<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
449        self.wheres
450            .push(WhereClause::Eq(col.name().to_string(), value));
451        self
452    }
453
454    /// `col != value`
455    pub fn ne<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
456        self.wheres
457            .push(WhereClause::Ne(col.name().to_string(), value));
458        self
459    }
460
461    /// `col > value`
462    pub fn gt<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
463        self.wheres
464            .push(WhereClause::Gt(col.name().to_string(), value));
465        self
466    }
467
468    /// `col >= value`
469    pub fn ge<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
470        self.wheres
471            .push(WhereClause::Ge(col.name().to_string(), value));
472        self
473    }
474
475    /// `col < value`
476    pub fn lt<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
477        self.wheres
478            .push(WhereClause::Lt(col.name().to_string(), value));
479        self
480    }
481
482    /// `col <= value`
483    pub fn le<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
484        self.wheres
485            .push(WhereClause::Le(col.name().to_string(), value));
486        self
487    }
488
489    /// `col LIKE value`
490    pub fn like<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
491        self.wheres
492            .push(WhereClause::Like(col.name().to_string(), value));
493        self
494    }
495
496    /// `col IS NULL`
497    pub fn is_null<C: Column<M>>(&mut self, col: C) -> &mut Self {
498        self.wheres
499            .push(WhereClause::IsNull(col.name().to_string()));
500        self
501    }
502
503    /// `col IS NOT NULL`
504    pub fn is_not_null<C: Column<M>>(&mut self, col: C) -> &mut Self {
505        self.wheres
506            .push(WhereClause::IsNotNull(col.name().to_string()));
507        self
508    }
509
510    /// `col IN (v1, v2, ...)`
511    pub fn r#in<C: Column<M>>(&mut self, col: C, values: Vec<Value>) -> &mut Self {
512        self.wheres
513            .push(WhereClause::In(col.name().to_string(), values));
514        self
515    }
516
517    /// `col NOT IN (v1, v2, ...)`
518    pub fn not_in<C: Column<M>>(&mut self, col: C, values: Vec<Value>) -> &mut Self {
519        self.wheres
520            .push(WhereClause::NotIn(col.name().to_string(), values));
521        self
522    }
523
524    /// `col BETWEEN a AND b`
525    pub fn between<C: Column<M>>(&mut self, col: C, a: Value, b: Value) -> &mut Self {
526        self.wheres
527            .push(WhereClause::Between(col.name().to_string(), a, b));
528        self
529    }
530
531    /// 追加原始 SQL WHERE 条件(用于 OR 等复杂场景)
532    ///
533    /// # 安全警告
534    ///
535    /// 此方法是 escape hatch,传入的 SQL 会**原样拼接**到最终 SQL 中。
536    /// **严禁将用户输入直接拼接**到 `sql` 参数中(会引入 SQL 注入风险)。
537    /// 若需使用用户输入,请改用 `eq` / `ne` / `lt` 等参数化方法。
538    pub fn raw_where(&mut self, sql: impl Into<String>) -> &mut Self {
539        self.wheres.push(WhereClause::Raw(sql.into()));
540        self
541    }
542
543    // -------------------- ORDER BY / LIMIT / OFFSET --------------------
544
545    /// 添加升序排序
546    pub fn order_by_asc<C: Column<M>>(&mut self, col: C) -> &mut Self {
547        self.orders.push(OrderBy {
548            column: col.name().to_string(),
549            direction: OrderDirection::Asc,
550        });
551        self
552    }
553
554    /// 添加降序排序
555    pub fn order_by_desc<C: Column<M>>(&mut self, col: C) -> &mut Self {
556        self.orders.push(OrderBy {
557            column: col.name().to_string(),
558            direction: OrderDirection::Desc,
559        });
560        self
561    }
562
563    /// 设置 LIMIT
564    pub fn limit(&mut self, n: u64) -> &mut Self {
565        self.limit = Some(n);
566        self
567    }
568
569    /// 设置 OFFSET
570    pub fn offset(&mut self, n: u64) -> &mut Self {
571        self.offset = Some(n);
572        self
573    }
574
575    /// 分页(设置 LIMIT + OFFSET)
576    ///
577    /// page 从 1 开始计数,page=1 时无 OFFSET(从第 0 条开始)。
578    pub fn page(&mut self, page: u64, page_size: u64) -> &mut Self {
579        self.limit = Some(page_size);
580        if page > 1 {
581            self.offset = Some((page - 1) * page_size);
582        } else {
583            self.offset = None;
584        }
585        self
586    }
587
588    // -------------------- SQL 生成 --------------------
589
590    /// 生成 SELECT SQL
591    ///
592    /// P3-2 修复:若启用了软删除/多租户,自动追加隐式 WHERE 条件
593    pub fn build_select(&self) -> String {
594        let quoted_table = self.dialect.quote(&self.table);
595
596        // SELECT 字段
597        let select_sql = if self.selects.is_empty() {
598            "*".to_string()
599        } else {
600            self.selects
601                .iter()
602                .map(|c| self.dialect.quote(c))
603                .collect::<Vec<_>>()
604                .join(", ")
605        };
606
607        let mut sql = format!("SELECT {} FROM {}", select_sql, quoted_table);
608
609        // WHERE(含隐式软删除/多租户条件)
610        sql.push_str(&self.render_where_clause());
611
612        // ORDER BY
613        if !self.orders.is_empty() {
614            let orders: Vec<String> = self
615                .orders
616                .iter()
617                .map(|o| {
618                    let dir = match o.direction {
619                        OrderDirection::Asc => "ASC",
620                        OrderDirection::Desc => "DESC",
621                    };
622                    format!("{} {}", self.dialect.quote(&o.column), dir)
623                })
624                .collect();
625            sql.push_str(" ORDER BY ");
626            sql.push_str(&orders.join(", "));
627        }
628
629        // LIMIT / OFFSET
630        if let Some(l) = self.limit {
631            sql.push_str(&format!(" LIMIT {}", l));
632        }
633        if let Some(o) = self.offset {
634            sql.push_str(&format!(" OFFSET {}", o));
635        }
636
637        sql
638    }
639
640    /// 生成 COUNT SQL(SELECT COUNT(*) FROM ... WHERE ...)
641    ///
642    /// P3-2 修复:若启用了软删除/多租户,自动追加隐式 WHERE 条件
643    pub fn build_count(&self) -> String {
644        let quoted_table = self.dialect.quote(&self.table);
645        let mut sql = format!("SELECT COUNT(*) FROM {}", quoted_table);
646        sql.push_str(&self.render_where_clause());
647        sql
648    }
649
650    /// 生成 EXISTS SQL(SELECT EXISTS(SELECT 1 FROM ... WHERE ...) AS exists_flag)
651    ///
652    /// P3-2 修复:若启用了软删除/多租户,自动追加隐式 WHERE 条件
653    pub fn build_exists(&self) -> String {
654        let inner = self.build_select();
655        // 把 SELECT 字段部分替换为 SELECT 1
656        let inner = if let Some(pos) = inner.find(" FROM ") {
657            format!("SELECT 1{}", &inner[pos..])
658        } else {
659            inner
660        };
661        format!("SELECT EXISTS({}) AS exists_flag", inner)
662    }
663
664    /// 生成 DELETE SQL
665    ///
666    /// P3-2 修复:
667    /// - 若启用软删除,则改为 `UPDATE ... SET column = deleted_value`
668    ///   (即软删除:仅标记,不实际删除行)
669    /// - 若启用多租户,自动追加 `column = tenant_id` 条件,防止跨租户删除
670    pub fn build_delete(&self) -> String {
671        let quoted_table = self.dialect.quote(&self.table);
672
673        // 软删除:改为 UPDATE
674        if let Some(ref sd) = self.soft_delete {
675            let col = self.dialect.quote(&sd.column);
676            let val = sd.deleted_value.to_param_with_dialect(self.dialect.as_ref());
677            let mut sql = format!(
678                "UPDATE {} SET {} = {}",
679                quoted_table, col, val
680            );
681
682            // WHERE(含用户条件 + 多租户条件;软删除条件不再追加,因为本就是 UPDATE SET)
683            let user_conds: Vec<String> = self
684                .wheres
685                .iter()
686                .map(|w| w.render(self.dialect.as_ref()))
687                .collect();
688            let mut all_conds = user_conds;
689            // 多租户过滤仍需追加
690            if let Some(ref t) = self.tenant {
691                let tcol = self.dialect.quote(&t.column);
692                let tval = t.tenant_id.to_param_with_dialect(self.dialect.as_ref());
693                all_conds.push(format!("{} = {}", tcol, tval));
694            }
695            // 同时确保只更新未删除的行
696            let not_deleted_cond = format!(
697                "{} = {}",
698                col,
699                sd.not_deleted_value.to_param_with_dialect(self.dialect.as_ref())
700            );
701            all_conds.push(not_deleted_cond);
702
703            if !all_conds.is_empty() {
704                sql.push_str(" WHERE ");
705                sql.push_str(&all_conds.join(" AND "));
706            }
707            return sql;
708        }
709
710        // 硬删除:DELETE FROM
711        let mut sql = format!("DELETE FROM {}", quoted_table);
712        sql.push_str(&self.render_where_clause());
713        sql
714    }
715
716    // -------------------- 状态查询 --------------------
717
718    /// 返回当前 WHERE 条件数量
719    pub fn where_count(&self) -> usize {
720        self.wheres.len()
721    }
722
723    /// 返回当前 SELECT 字段数量
724    pub fn select_count(&self) -> usize {
725        self.selects.len()
726    }
727
728    /// 返回表名
729    pub fn table(&self) -> &str {
730        &self.table
731    }
732
733    /// 重置所有条件(保留表名和方言)
734    pub fn reset(&mut self) -> &mut Self {
735        self.selects.clear();
736        self.wheres.clear();
737        self.orders.clear();
738        self.limit = None;
739        self.offset = None;
740        self
741    }
742}
743
744// ============================================================================
745// define_columns! 宏 — 为 Model 定义字段标记
746// ============================================================================
747
748/// 为 Model 定义一组类型安全的字段标记
749///
750/// # 示例
751///
752/// ```
753/// use sz_orm_core::define_columns;
754///
755/// struct User;
756///
757/// define_columns! {
758///     UserColumns for User table = "users" {
759///         Id => "id",
760///         Name => "name",
761///         Age => "age",
762///     }
763/// }
764/// ```
765///
766/// 生成如下代码:
767///
768/// ```rust,ignore
769/// #[derive(Clone, Copy)]
770/// pub struct UserColumns;
771///
772/// impl UserColumns {
773///     pub const Id: UserColumn = UserColumn { name: "id", table: "users" };
774///     pub const Name: UserColumn = UserColumn { name: "name", table: "users" };
775///     pub const Age: UserColumn = UserColumn { name: "age", table: "users" };
776/// }
777///
778/// #[derive(Clone, Copy)]
779/// pub struct UserColumn {
780///     pub name: &'static str,
781///     pub table: &'static str,
782/// }
783///
784/// impl Column<User> for UserColumn {
785///     fn name(&self) -> &'static str { self.name }
786///     fn table(&self) -> &'static str { self.table }
787/// }
788/// ```
789#[macro_export]
790macro_rules! define_columns {
791    (
792        $columns_struct:ident for $model:ident table = $table:literal {
793            $( $field:ident => $name:literal ),* $(,)?
794        }
795    ) => {
796        /// 字段标记类型(每个 Model 一组)
797        ///
798        /// 通过 `ModelColumns::FieldName` 引用对应字段,编译期检查拼写错误。
799        #[derive(Debug, Clone, Copy)]
800        pub struct $columns_struct {
801            /// 字段名
802            pub name: &'static str,
803            /// 表名
804            pub table: &'static str,
805        }
806
807        impl $crate::lambda::Column<$model> for $columns_struct {
808            fn name(&self) -> &'static str {
809                self.name
810            }
811
812            fn table(&self) -> &'static str {
813                self.table
814            }
815        }
816
817        impl $columns_struct {
818            // 允许 PascalCase 常量名(如 Id、Name),符合字段命名习惯
819            $(
820                #[allow(non_upper_case_globals, dead_code)]
821                pub const $field: $columns_struct = $columns_struct { name: $name, table: $table };
822            )*
823        }
824    };
825}
826
827// ============================================================================
828// 单元测试
829// ============================================================================
830
831#[cfg(test)]
832mod tests {
833    use super::*;
834    use crate::dialect::PostgreSqlDialect;
835    use crate::get_dialect;
836    use crate::DbType;
837
838    // 测试用 Model
839    struct User;
840
841    // 为 User 定义字段标记
842    define_columns! {
843        UserColumns for User table = "users" {
844            Id => "id",
845            Name => "name",
846            Age => "age",
847            Email => "email",
848        }
849    }
850
851    // 另一个 Model 用于测试表名隔离
852    struct Order;
853
854    define_columns! {
855        OrderColumns for Order table = "orders" {
856            OrderId => "order_id",
857            UserId => "user_id",
858            Total => "total",
859        }
860    }
861
862    // ===== Column trait 测试 =====
863
864    #[test]
865    fn test_column_name_and_table() {
866        assert_eq!(UserColumns::Id.name(), "id");
867        assert_eq!(UserColumns::Id.table(), "users");
868        assert_eq!(UserColumns::Name.name(), "name");
869        assert_eq!(UserColumns::Age.name(), "age");
870        assert_eq!(UserColumns::Email.name(), "email");
871    }
872
873    #[test]
874    fn test_column_for_different_models() {
875        assert_eq!(OrderColumns::OrderId.name(), "order_id");
876        assert_eq!(OrderColumns::OrderId.table(), "orders");
877        assert_eq!(OrderColumns::UserId.name(), "user_id");
878    }
879
880    // ===== LambdaWrapper 基础测试 =====
881
882    #[test]
883    fn test_new_wrapper() {
884        let w = LambdaWrapper::<User>::new("users");
885        assert_eq!(w.table(), "users");
886        assert_eq!(w.where_count(), 0);
887        assert_eq!(w.select_count(), 0);
888    }
889
890    #[test]
891    fn test_select_single() {
892        let mut w = LambdaWrapper::<User>::new("users");
893        w.select(UserColumns::Id);
894        let sql = w.build_select();
895        assert!(sql.contains("SELECT `id` FROM `users`"));
896    }
897
898    #[test]
899    fn test_select_multiple() {
900        let mut w = LambdaWrapper::<User>::new("users");
901        w.select(UserColumns::Id)
902            .select(UserColumns::Name)
903            .select(UserColumns::Age);
904        let sql = w.build_select();
905        assert!(sql.contains("`id`, `name`, `age`"));
906    }
907
908    #[test]
909    fn test_select_many() {
910        let mut w = LambdaWrapper::<User>::new("users");
911        w.select_many(&[UserColumns::Id, UserColumns::Name, UserColumns::Age]);
912        let sql = w.build_select();
913        assert!(sql.contains("`id`, `name`, `age`"));
914    }
915
916    #[test]
917    fn test_select_all_clears_selects() {
918        let mut w = LambdaWrapper::<User>::new("users");
919        w.select(UserColumns::Id);
920        assert_eq!(w.select_count(), 1);
921        w.select_all();
922        assert_eq!(w.select_count(), 0);
923        let sql = w.build_select();
924        assert!(sql.contains("SELECT * FROM"));
925    }
926
927    #[test]
928    fn test_default_select_is_star() {
929        let w = LambdaWrapper::<User>::new("users");
930        let sql = w.build_select();
931        assert!(sql.contains("SELECT * FROM `users`"));
932    }
933
934    // ===== WHERE 条件测试 =====
935
936    #[test]
937    fn test_where_eq() {
938        let mut w = LambdaWrapper::<User>::new("users");
939        w.eq(UserColumns::Id, Value::I64(1));
940        let sql = w.build_select();
941        assert!(sql.contains("WHERE `id` = 1"));
942    }
943
944    #[test]
945    fn test_where_ne() {
946        let mut w = LambdaWrapper::<User>::new("users");
947        w.ne(UserColumns::Id, Value::I64(1));
948        let sql = w.build_select();
949        assert!(sql.contains("`id` != 1"));
950    }
951
952    #[test]
953    fn test_where_gt_ge_lt_le() {
954        let mut w = LambdaWrapper::<User>::new("users");
955        w.gt(UserColumns::Age, Value::I64(18))
956            .ge(UserColumns::Age, Value::I64(20))
957            .lt(UserColumns::Age, Value::I64(65))
958            .le(UserColumns::Age, Value::I64(60));
959        let sql = w.build_select();
960        assert!(sql.contains("`age` > 18"));
961        assert!(sql.contains("`age` >= 20"));
962        assert!(sql.contains("`age` < 65"));
963        assert!(sql.contains("`age` <= 60"));
964    }
965
966    #[test]
967    fn test_where_like() {
968        let mut w = LambdaWrapper::<User>::new("users");
969        w.like(UserColumns::Name, Value::String("%alice%".to_string()));
970        let sql = w.build_select();
971        assert!(sql.contains("`name` LIKE '%alice%'"));
972    }
973
974    #[test]
975    fn test_where_is_null() {
976        let mut w = LambdaWrapper::<User>::new("users");
977        w.is_null(UserColumns::Email);
978        let sql = w.build_select();
979        assert!(sql.contains("`email` IS NULL"));
980    }
981
982    #[test]
983    fn test_where_is_not_null() {
984        let mut w = LambdaWrapper::<User>::new("users");
985        w.is_not_null(UserColumns::Email);
986        let sql = w.build_select();
987        assert!(sql.contains("`email` IS NOT NULL"));
988    }
989
990    #[test]
991    fn test_where_in() {
992        let mut w = LambdaWrapper::<User>::new("users");
993        w.r#in(
994            UserColumns::Id,
995            vec![Value::I64(1), Value::I64(2), Value::I64(3)],
996        );
997        let sql = w.build_select();
998        assert!(sql.contains("`id` IN (1, 2, 3)"));
999    }
1000
1001    #[test]
1002    fn test_where_not_in() {
1003        let mut w = LambdaWrapper::<User>::new("users");
1004        w.not_in(UserColumns::Id, vec![Value::I64(1), Value::I64(2)]);
1005        let sql = w.build_select();
1006        assert!(sql.contains("`id` NOT IN (1, 2)"));
1007    }
1008
1009    #[test]
1010    fn test_where_between() {
1011        let mut w = LambdaWrapper::<User>::new("users");
1012        w.between(UserColumns::Age, Value::I64(18), Value::I64(65));
1013        let sql = w.build_select();
1014        assert!(sql.contains("`age` BETWEEN 18 AND 65"));
1015    }
1016
1017    #[test]
1018    fn test_where_multiple_anded() {
1019        let mut w = LambdaWrapper::<User>::new("users");
1020        w.eq(UserColumns::Id, Value::I64(1))
1021            .gt(UserColumns::Age, Value::I64(18))
1022            .like(UserColumns::Name, Value::String("alice%".to_string()));
1023        let sql = w.build_select();
1024        assert!(sql.contains("`id` = 1"));
1025        assert!(sql.contains("`age` > 18"));
1026        assert!(sql.contains("`name` LIKE 'alice%'"));
1027        // 所有条件用 AND 连接
1028        assert!(sql.contains(" AND "));
1029    }
1030
1031    #[test]
1032    fn test_where_raw() {
1033        let mut w = LambdaWrapper::<User>::new("users");
1034        w.raw_where("name = 'alice' OR name = 'bob'");
1035        let sql = w.build_select();
1036        assert!(sql.contains("name = 'alice' OR name = 'bob'"));
1037    }
1038
1039    // ===== ORDER BY / LIMIT / OFFSET 测试 =====
1040
1041    #[test]
1042    fn test_order_by_asc() {
1043        let mut w = LambdaWrapper::<User>::new("users");
1044        w.order_by_asc(UserColumns::Name);
1045        let sql = w.build_select();
1046        assert!(sql.contains("ORDER BY `name` ASC"));
1047    }
1048
1049    #[test]
1050    fn test_order_by_desc() {
1051        let mut w = LambdaWrapper::<User>::new("users");
1052        w.order_by_desc(UserColumns::Id);
1053        let sql = w.build_select();
1054        assert!(sql.contains("ORDER BY `id` DESC"));
1055    }
1056
1057    #[test]
1058    fn test_order_by_multiple() {
1059        let mut w = LambdaWrapper::<User>::new("users");
1060        w.order_by_asc(UserColumns::Name)
1061            .order_by_desc(UserColumns::Id);
1062        let sql = w.build_select();
1063        assert!(sql.contains("ORDER BY `name` ASC, `id` DESC"));
1064    }
1065
1066    #[test]
1067    fn test_limit() {
1068        let mut w = LambdaWrapper::<User>::new("users");
1069        w.limit(10);
1070        let sql = w.build_select();
1071        assert!(sql.contains("LIMIT 10"));
1072    }
1073
1074    #[test]
1075    fn test_offset() {
1076        let mut w = LambdaWrapper::<User>::new("users");
1077        w.limit(10).offset(20);
1078        let sql = w.build_select();
1079        assert!(sql.contains("LIMIT 10"));
1080        assert!(sql.contains("OFFSET 20"));
1081    }
1082
1083    #[test]
1084    fn test_page() {
1085        let mut w = LambdaWrapper::<User>::new("users");
1086        w.page(3, 20); // 第 3 页,每页 20 条
1087        let sql = w.build_select();
1088        assert!(sql.contains("LIMIT 20"));
1089        assert!(sql.contains("OFFSET 40")); // (3-1) * 20
1090    }
1091
1092    #[test]
1093    fn test_page_1_no_offset() {
1094        let mut w = LambdaWrapper::<User>::new("users");
1095        w.page(1, 10);
1096        let sql = w.build_select();
1097        assert!(sql.contains("LIMIT 10"));
1098        assert!(!sql.contains("OFFSET")); // 第 1 页无 OFFSET
1099    }
1100
1101    // ===== SQL 生成测试 =====
1102
1103    #[test]
1104    fn test_build_count() {
1105        let mut w = LambdaWrapper::<User>::new("users");
1106        w.gt(UserColumns::Age, Value::I64(18));
1107        let sql = w.build_count();
1108        assert!(sql.contains("SELECT COUNT(*) FROM `users`"));
1109        assert!(sql.contains("`age` > 18"));
1110        // 不应包含 ORDER BY / LIMIT
1111        assert!(!sql.contains("ORDER BY"));
1112        assert!(!sql.contains("LIMIT"));
1113    }
1114
1115    #[test]
1116    fn test_build_exists() {
1117        let mut w = LambdaWrapper::<User>::new("users");
1118        w.eq(UserColumns::Id, Value::I64(1));
1119        let sql = w.build_exists();
1120        assert!(sql.starts_with("SELECT EXISTS("));
1121        assert!(sql.contains("SELECT 1 FROM `users`"));
1122        assert!(sql.contains("`id` = 1"));
1123        assert!(sql.ends_with(") AS exists_flag"));
1124    }
1125
1126    #[test]
1127    fn test_build_delete() {
1128        let mut w = LambdaWrapper::<User>::new("users");
1129        w.eq(UserColumns::Id, Value::I64(1));
1130        let sql = w.build_delete();
1131        assert!(sql.starts_with("DELETE FROM `users`"));
1132        assert!(sql.contains("WHERE `id` = 1"));
1133    }
1134
1135    // ===== P3-2 修复:软删除 / 多租户测试 =====
1136
1137    #[test]
1138    fn test_soft_delete_select_appends_filter() {
1139        // 启用软删除后,build_select 应自动追加 `deleted = 0`
1140        let w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1141            column: "deleted".to_string(),
1142            not_deleted_value: Value::I64(0),
1143            deleted_value: Value::I64(1),
1144        });
1145        let sql = w.build_select();
1146        assert!(
1147            sql.contains("`deleted` = 0"),
1148            "软删除 SELECT 应追加 `deleted` = 0: {}",
1149            sql
1150        );
1151    }
1152
1153    #[test]
1154    fn test_soft_delete_count_appends_filter() {
1155        // 启用软删除后,build_count 应自动追加 `deleted = 0`
1156        let w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1157            column: "deleted".to_string(),
1158            not_deleted_value: Value::I64(0),
1159            deleted_value: Value::I64(1),
1160        });
1161        let sql = w.build_count();
1162        assert!(
1163            sql.contains("`deleted` = 0"),
1164            "软删除 COUNT 应追加 `deleted` = 0: {}",
1165            sql
1166        );
1167    }
1168
1169    #[test]
1170    fn test_soft_delete_exists_appends_filter() {
1171        // 启用软删除后,build_exists 应自动追加 `deleted = 0`
1172        let w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1173            column: "deleted".to_string(),
1174            not_deleted_value: Value::I64(0),
1175            deleted_value: Value::I64(1),
1176        });
1177        let sql = w.build_exists();
1178        assert!(
1179            sql.contains("`deleted` = 0"),
1180            "软删除 EXISTS 应追加 `deleted` = 0: {}",
1181            sql
1182        );
1183    }
1184
1185    #[test]
1186    fn test_soft_delete_delete_becomes_update() {
1187        // 启用软删除后,build_delete 应改为 UPDATE ... SET deleted = 1
1188        let mut w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1189            column: "deleted".to_string(),
1190            not_deleted_value: Value::I64(0),
1191            deleted_value: Value::I64(1),
1192        });
1193        w.eq(UserColumns::Id, Value::I64(42));
1194        let sql = w.build_delete();
1195        assert!(
1196            sql.starts_with("UPDATE `users` SET `deleted` = 1"),
1197            "软删除应为 UPDATE:{}",
1198            sql
1199        );
1200        assert!(
1201            sql.contains("`id` = 42"),
1202            "应保留用户 WHERE 条件:{}",
1203            sql
1204        );
1205        assert!(
1206            sql.contains("`deleted` = 0"),
1207            "应追加未删除条件防止重复删除:{}",
1208            sql
1209        );
1210        assert!(
1211            !sql.starts_with("DELETE"),
1212            "软删除不应生成 DELETE 语句:{}",
1213            sql
1214        );
1215    }
1216
1217    #[test]
1218    fn test_tenant_select_appends_filter() {
1219        // 启用多租户后,build_select 应自动追加 `tenant_id` = 42
1220        let w = LambdaWrapper::<User>::new("users").with_tenant(TenantConfig {
1221            column: "tenant_id".to_string(),
1222            tenant_id: Value::I64(42),
1223        });
1224        let sql = w.build_select();
1225        assert!(
1226            sql.contains("`tenant_id` = 42"),
1227            "多租户 SELECT 应追加 `tenant_id` = 42: {}",
1228            sql
1229        );
1230    }
1231
1232    #[test]
1233    fn test_tenant_delete_appends_filter() {
1234        // 启用多租户后,build_delete 应自动追加 `tenant_id` = 42 防止跨租户删除
1235        let mut w = LambdaWrapper::<User>::new("users").with_tenant(TenantConfig {
1236            column: "tenant_id".to_string(),
1237            tenant_id: Value::I64(42),
1238        });
1239        w.eq(UserColumns::Id, Value::I64(1));
1240        let sql = w.build_delete();
1241        assert!(
1242            sql.contains("`tenant_id` = 42"),
1243            "多租户 DELETE 应追加 `tenant_id` = 42: {}",
1244            sql
1245        );
1246    }
1247
1248    #[test]
1249    fn test_soft_delete_and_tenant_combined() {
1250        // 同时启用软删除和多租户
1251        let mut w = LambdaWrapper::<User>::new("users")
1252            .with_soft_delete(SoftDeleteConfig {
1253                column: "deleted".to_string(),
1254                not_deleted_value: Value::I64(0),
1255                deleted_value: Value::I64(1),
1256            })
1257            .with_tenant(TenantConfig {
1258                column: "tenant_id".to_string(),
1259                tenant_id: Value::I64(99),
1260            });
1261        w.eq(UserColumns::Id, Value::I64(7));
1262
1263        // SELECT 应同时包含两个隐式条件
1264        let sql = w.build_select();
1265        assert!(sql.contains("`deleted` = 0"), "应包含软删除条件: {}", sql);
1266        assert!(sql.contains("`tenant_id` = 99"), "应包含多租户条件: {}", sql);
1267        assert!(sql.contains("`id` = 7"), "应包含用户条件: {}", sql);
1268
1269        // DELETE 应改为 UPDATE,并追加所有条件
1270        let del_sql = w.build_delete();
1271        assert!(del_sql.starts_with("UPDATE `users` SET `deleted` = 1"));
1272        assert!(del_sql.contains("`tenant_id` = 99"));
1273        assert!(del_sql.contains("`deleted` = 0"));
1274        assert!(del_sql.contains("`id` = 7"));
1275    }
1276
1277    #[test]
1278    fn test_no_soft_delete_no_tenant_backward_compat() {
1279        // 未启用软删除/多租户时,行为与之前一致
1280        let mut w = LambdaWrapper::<User>::new("users");
1281        w.eq(UserColumns::Id, Value::I64(1));
1282        let sql = w.build_select();
1283        assert!(!sql.contains("deleted"));
1284        assert!(!sql.contains("tenant_id"));
1285
1286        let del_sql = w.build_delete();
1287        assert!(del_sql.starts_with("DELETE FROM"));
1288    }
1289
1290    // ===== 方言测试 =====
1291
1292    #[test]
1293    fn test_with_postgres_dialect() {
1294        let dialect: Box<dyn Dialect> = Box::new(PostgreSqlDialect);
1295        let mut w = LambdaWrapper::<User>::with_dialect("users", dialect);
1296        w.eq(UserColumns::Id, Value::I64(1));
1297        let sql = w.build_select();
1298        assert!(sql.contains("\"users\""));
1299        assert!(sql.contains("\"id\" = 1"));
1300    }
1301
1302    #[test]
1303    fn test_postgres_select() {
1304        let dialect: Box<dyn Dialect> = Box::new(PostgreSqlDialect);
1305        let mut w = LambdaWrapper::<User>::with_dialect("users", dialect);
1306        w.select(UserColumns::Id).select(UserColumns::Name);
1307        let sql = w.build_select();
1308        assert!(sql.contains("\"id\", \"name\""));
1309    }
1310
1311    // ===== 完整查询测试 =====
1312
1313    #[test]
1314    fn test_complex_query() {
1315        let mut w = LambdaWrapper::<User>::new("users");
1316        w.select(UserColumns::Id)
1317            .select(UserColumns::Name)
1318            .select(UserColumns::Age)
1319            .gt(UserColumns::Age, Value::I64(18))
1320            .like(UserColumns::Name, Value::String("a%".to_string()))
1321            .is_not_null(UserColumns::Email)
1322            .order_by_desc(UserColumns::Id)
1323            .limit(10)
1324            .offset(20);
1325
1326        let sql = w.build_select();
1327        assert!(sql.contains("SELECT `id`, `name`, `age` FROM `users`"));
1328        assert!(sql.contains("`age` > 18"));
1329        assert!(sql.contains("`name` LIKE 'a%'"));
1330        assert!(sql.contains("`email` IS NOT NULL"));
1331        assert!(sql.contains("ORDER BY `id` DESC"));
1332        assert!(sql.contains("LIMIT 10"));
1333        assert!(sql.contains("OFFSET 20"));
1334    }
1335
1336    #[test]
1337    fn test_reset_clears_all() {
1338        let mut w = LambdaWrapper::<User>::new("users");
1339        w.select(UserColumns::Id)
1340            .eq(UserColumns::Id, Value::I64(1))
1341            .order_by_asc(UserColumns::Name)
1342            .limit(10);
1343
1344        w.reset();
1345        assert_eq!(w.select_count(), 0);
1346        assert_eq!(w.where_count(), 0);
1347        let sql = w.build_select();
1348        assert!(sql.contains("SELECT * FROM `users`"));
1349        assert!(!sql.contains("WHERE"));
1350        assert!(!sql.contains("ORDER BY"));
1351        assert!(!sql.contains("LIMIT"));
1352    }
1353
1354    // ===== 跨 Model 类型隔离测试 =====
1355
1356    #[test]
1357    fn test_different_models_dont_share_columns() {
1358        let mut user_w = LambdaWrapper::<User>::new("users");
1359        user_w.eq(UserColumns::Id, Value::I64(1));
1360
1361        let mut order_w = LambdaWrapper::<Order>::new("orders");
1362        order_w.eq(OrderColumns::OrderId, Value::I64(100));
1363
1364        let user_sql = user_w.build_select();
1365        let order_sql = order_w.build_select();
1366
1367        assert!(user_sql.contains("`users`"));
1368        assert!(user_sql.contains("`id` = 1"));
1369        assert!(order_sql.contains("`orders`"));
1370        assert!(order_sql.contains("`order_id` = 100"));
1371    }
1372
1373    // ===== 编译期类型安全演示(运行时验证)=====
1374
1375    #[test]
1376    fn test_type_safety_compile_time_check() {
1377        // 以下代码若取消注释将无法编译:
1378        // let mut w = LambdaWrapper::<User>::new("users");
1379        // w.eq(OrderColumns::OrderId, Value::I64(1)); // OrderColumns 不能用于 User 的 wrapper
1380
1381        // 这里通过正常调用验证类型隔离工作正常
1382        let mut w = LambdaWrapper::<User>::new("users");
1383        w.eq(UserColumns::Id, Value::I64(1));
1384        assert_eq!(w.where_count(), 1);
1385    }
1386
1387    #[test]
1388    fn test_string_value_escape() {
1389        // v0.2.2 修复 H-1:默认使用 MySqlDialect,单引号转义为 \'
1390        let mut w = LambdaWrapper::<User>::new("users");
1391        w.eq(UserColumns::Name, Value::String("O'Brien".to_string()));
1392        let sql = w.build_select();
1393        // MySQL 方言下字符串中的单引号应被转义为 \'
1394        assert!(sql.contains("'O\\'Brien'"));
1395
1396        // 使用 PostgreSQL 方言时,单引号转义为 ''
1397        let pg_dialect: Box<dyn Dialect> = get_dialect(DbType::PostgreSQL).unwrap();
1398        let mut w_pg = LambdaWrapper::<User>::with_dialect("users", pg_dialect);
1399        w_pg.eq(UserColumns::Name, Value::String("O'Brien".to_string()));
1400        let sql_pg = w_pg.build_select();
1401        assert!(sql_pg.contains("'O''Brien'"));
1402    }
1403
1404    // ===== 使用真实方言验证集成 =====
1405
1406    #[test]
1407    fn test_with_real_mysql_dialect() {
1408        let dialect: Box<dyn Dialect> = get_dialect(DbType::MySQL).unwrap();
1409        let mut w = LambdaWrapper::<User>::with_dialect("users", dialect);
1410        w.select(UserColumns::Id)
1411            .select(UserColumns::Name)
1412            .eq(UserColumns::Id, Value::I64(42))
1413            .order_by_desc(UserColumns::Id);
1414
1415        let sql = w.build_select();
1416        assert!(sql.contains("SELECT `id`, `name` FROM `users`"));
1417        assert!(sql.contains("`id` = 42"));
1418        assert!(sql.contains("ORDER BY `id` DESC"));
1419    }
1420}