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, 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
384                .not_deleted_value
385                .to_param_with_dialect(self.dialect.as_ref());
386            implicit.push(format!("{} = {}", col, val));
387        }
388        if let Some(ref t) = self.tenant {
389            let col = self.dialect.quote(&t.column);
390            let val = t.tenant_id.to_param_with_dialect(self.dialect.as_ref());
391            implicit.push(format!("{} = {}", col, val));
392        }
393        implicit
394    }
395
396    /// 渲染 WHERE 子句(包含用户 wheres + 隐式软删除/多租户条件)
397    fn render_where_clause(&self) -> String {
398        let user_conds: Vec<String> = self
399            .wheres
400            .iter()
401            .map(|w| w.render(self.dialect.as_ref()))
402            .collect();
403        let implicit_conds = self.collect_implicit_wheres();
404        let all_conds: Vec<String> = user_conds.into_iter().chain(implicit_conds).collect();
405        if all_conds.is_empty() {
406            String::new()
407        } else {
408            format!(" WHERE {}", all_conds.join(" AND "))
409        }
410    }
411
412    // -------------------- SELECT 字段 --------------------
413
414    /// 添加 SELECT 字段(类型安全)
415    ///
416    /// M-4 修复:对列名进行 `validate_identifier` 校验,防止恶意实现 `Column` trait
417    /// 注入非法标识符。
418    pub fn select<C: Column<M>>(&mut self, col: C) -> &mut Self {
419        let name = col.name();
420        // 校验列名为合法 SQL 标识符(非空、仅 ASCII 字母数字+下划线、不以数字开头)
421        // 校验失败时跳过该列(保留向后兼容,不中断调用链)
422        if crate::sql_safety::validate_identifier(name, "lambda select column").is_ok() {
423            self.selects.push(name.to_string());
424        }
425        self
426    }
427
428    /// 批量添加 SELECT 字段
429    ///
430    /// M-4 修复:同 `select`,对每个列名校验。
431    pub fn select_many<C: Column<M>>(&mut self, cols: &[C]) -> &mut Self {
432        for c in cols {
433            let name = c.name();
434            if crate::sql_safety::validate_identifier(name, "lambda select column").is_ok() {
435                self.selects.push(name.to_string());
436            }
437        }
438        self
439    }
440
441    /// SELECT *(清空已有字段选择)
442    pub fn select_all(&mut self) -> &mut Self {
443        self.selects.clear();
444        self
445    }
446
447    // -------------------- WHERE 条件 --------------------
448
449    /// `col = value`
450    pub fn eq<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
451        self.wheres
452            .push(WhereClause::Eq(col.name().to_string(), value));
453        self
454    }
455
456    /// `col != value`
457    pub fn ne<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
458        self.wheres
459            .push(WhereClause::Ne(col.name().to_string(), value));
460        self
461    }
462
463    /// `col > value`
464    pub fn gt<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
465        self.wheres
466            .push(WhereClause::Gt(col.name().to_string(), value));
467        self
468    }
469
470    /// `col >= value`
471    pub fn ge<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
472        self.wheres
473            .push(WhereClause::Ge(col.name().to_string(), value));
474        self
475    }
476
477    /// `col < value`
478    pub fn lt<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
479        self.wheres
480            .push(WhereClause::Lt(col.name().to_string(), value));
481        self
482    }
483
484    /// `col <= value`
485    pub fn le<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
486        self.wheres
487            .push(WhereClause::Le(col.name().to_string(), value));
488        self
489    }
490
491    /// `col LIKE value`
492    pub fn like<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
493        self.wheres
494            .push(WhereClause::Like(col.name().to_string(), value));
495        self
496    }
497
498    /// `col IS NULL`
499    pub fn is_null<C: Column<M>>(&mut self, col: C) -> &mut Self {
500        self.wheres
501            .push(WhereClause::IsNull(col.name().to_string()));
502        self
503    }
504
505    /// `col IS NOT NULL`
506    pub fn is_not_null<C: Column<M>>(&mut self, col: C) -> &mut Self {
507        self.wheres
508            .push(WhereClause::IsNotNull(col.name().to_string()));
509        self
510    }
511
512    /// `col IN (v1, v2, ...)`
513    pub fn r#in<C: Column<M>>(&mut self, col: C, values: Vec<Value>) -> &mut Self {
514        self.wheres
515            .push(WhereClause::In(col.name().to_string(), values));
516        self
517    }
518
519    /// `col NOT IN (v1, v2, ...)`
520    pub fn not_in<C: Column<M>>(&mut self, col: C, values: Vec<Value>) -> &mut Self {
521        self.wheres
522            .push(WhereClause::NotIn(col.name().to_string(), values));
523        self
524    }
525
526    /// `col BETWEEN a AND b`
527    pub fn between<C: Column<M>>(&mut self, col: C, a: Value, b: Value) -> &mut Self {
528        self.wheres
529            .push(WhereClause::Between(col.name().to_string(), a, b));
530        self
531    }
532
533    /// 追加原始 SQL WHERE 条件(用于 OR 等复杂场景)
534    ///
535    /// # 安全警告
536    ///
537    /// 此方法是 escape hatch,传入的 SQL 会**原样拼接**到最终 SQL 中。
538    /// **严禁将用户输入直接拼接**到 `sql` 参数中(会引入 SQL 注入风险)。
539    /// 若需使用用户输入,请改用 `eq` / `ne` / `lt` 等参数化方法。
540    pub fn raw_where(&mut self, sql: impl Into<String>) -> &mut Self {
541        self.wheres.push(WhereClause::Raw(sql.into()));
542        self
543    }
544
545    // -------------------- ORDER BY / LIMIT / OFFSET --------------------
546
547    /// 添加升序排序
548    pub fn order_by_asc<C: Column<M>>(&mut self, col: C) -> &mut Self {
549        self.orders.push(OrderBy {
550            column: col.name().to_string(),
551            direction: OrderDirection::Asc,
552        });
553        self
554    }
555
556    /// 添加降序排序
557    pub fn order_by_desc<C: Column<M>>(&mut self, col: C) -> &mut Self {
558        self.orders.push(OrderBy {
559            column: col.name().to_string(),
560            direction: OrderDirection::Desc,
561        });
562        self
563    }
564
565    /// 设置 LIMIT
566    pub fn limit(&mut self, n: u64) -> &mut Self {
567        self.limit = Some(n);
568        self
569    }
570
571    /// 设置 OFFSET
572    pub fn offset(&mut self, n: u64) -> &mut Self {
573        self.offset = Some(n);
574        self
575    }
576
577    /// 分页(设置 LIMIT + OFFSET)
578    ///
579    /// page 从 1 开始计数,page=1 时无 OFFSET(从第 0 条开始)。
580    pub fn page(&mut self, page: u64, page_size: u64) -> &mut Self {
581        self.limit = Some(page_size);
582        if page > 1 {
583            self.offset = Some((page - 1) * page_size);
584        } else {
585            self.offset = None;
586        }
587        self
588    }
589
590    // -------------------- SQL 生成 --------------------
591
592    /// 生成 SELECT SQL
593    ///
594    /// P3-2 修复:若启用了软删除/多租户,自动追加隐式 WHERE 条件
595    pub fn build_select(&self) -> String {
596        let quoted_table = self.dialect.quote(&self.table);
597
598        // SELECT 字段
599        let select_sql = if self.selects.is_empty() {
600            "*".to_string()
601        } else {
602            self.selects
603                .iter()
604                .map(|c| self.dialect.quote(c))
605                .collect::<Vec<_>>()
606                .join(", ")
607        };
608
609        let mut sql = format!("SELECT {} FROM {}", select_sql, quoted_table);
610
611        // WHERE(含隐式软删除/多租户条件)
612        sql.push_str(&self.render_where_clause());
613
614        // ORDER BY
615        if !self.orders.is_empty() {
616            let orders: Vec<String> = self
617                .orders
618                .iter()
619                .map(|o| {
620                    let dir = match o.direction {
621                        OrderDirection::Asc => "ASC",
622                        OrderDirection::Desc => "DESC",
623                    };
624                    format!("{} {}", self.dialect.quote(&o.column), dir)
625                })
626                .collect();
627            sql.push_str(" ORDER BY ");
628            sql.push_str(&orders.join(", "));
629        }
630
631        // LIMIT / OFFSET
632        if let Some(l) = self.limit {
633            sql.push_str(&format!(" LIMIT {}", l));
634        }
635        if let Some(o) = self.offset {
636            sql.push_str(&format!(" OFFSET {}", o));
637        }
638
639        sql
640    }
641
642    /// 生成 COUNT SQL(SELECT COUNT(*) FROM ... WHERE ...)
643    ///
644    /// P3-2 修复:若启用了软删除/多租户,自动追加隐式 WHERE 条件
645    pub fn build_count(&self) -> String {
646        let quoted_table = self.dialect.quote(&self.table);
647        let mut sql = format!("SELECT COUNT(*) FROM {}", quoted_table);
648        sql.push_str(&self.render_where_clause());
649        sql
650    }
651
652    /// 生成 EXISTS SQL(SELECT EXISTS(SELECT 1 FROM ... WHERE ...) AS exists_flag)
653    ///
654    /// P3-2 修复:若启用了软删除/多租户,自动追加隐式 WHERE 条件
655    pub fn build_exists(&self) -> String {
656        let inner = self.build_select();
657        // 把 SELECT 字段部分替换为 SELECT 1
658        let inner = if let Some(pos) = inner.find(" FROM ") {
659            format!("SELECT 1{}", &inner[pos..])
660        } else {
661            inner
662        };
663        format!("SELECT EXISTS({}) AS exists_flag", inner)
664    }
665
666    /// 生成 DELETE SQL
667    ///
668    /// P3-2 修复:
669    /// - 若启用软删除,则改为 `UPDATE ... SET column = deleted_value`
670    ///   (即软删除:仅标记,不实际删除行)
671    /// - 若启用多租户,自动追加 `column = tenant_id` 条件,防止跨租户删除
672    pub fn build_delete(&self) -> String {
673        let quoted_table = self.dialect.quote(&self.table);
674
675        // 软删除:改为 UPDATE
676        if let Some(ref sd) = self.soft_delete {
677            let col = self.dialect.quote(&sd.column);
678            let val = sd
679                .deleted_value
680                .to_param_with_dialect(self.dialect.as_ref());
681            let mut sql = format!("UPDATE {} SET {} = {}", quoted_table, col, val);
682
683            // WHERE(含用户条件 + 多租户条件;软删除条件不再追加,因为本就是 UPDATE SET)
684            let user_conds: Vec<String> = self
685                .wheres
686                .iter()
687                .map(|w| w.render(self.dialect.as_ref()))
688                .collect();
689            let mut all_conds = user_conds;
690            // 多租户过滤仍需追加
691            if let Some(ref t) = self.tenant {
692                let tcol = self.dialect.quote(&t.column);
693                let tval = t.tenant_id.to_param_with_dialect(self.dialect.as_ref());
694                all_conds.push(format!("{} = {}", tcol, tval));
695            }
696            // 同时确保只更新未删除的行
697            let not_deleted_cond = format!(
698                "{} = {}",
699                col,
700                sd.not_deleted_value
701                    .to_param_with_dialect(self.dialect.as_ref())
702            );
703            all_conds.push(not_deleted_cond);
704
705            if !all_conds.is_empty() {
706                sql.push_str(" WHERE ");
707                sql.push_str(&all_conds.join(" AND "));
708            }
709            return sql;
710        }
711
712        // 硬删除:DELETE FROM
713        let mut sql = format!("DELETE FROM {}", quoted_table);
714        sql.push_str(&self.render_where_clause());
715        sql
716    }
717
718    // -------------------- 状态查询 --------------------
719
720    /// 返回当前 WHERE 条件数量
721    pub fn where_count(&self) -> usize {
722        self.wheres.len()
723    }
724
725    /// 返回当前 SELECT 字段数量
726    pub fn select_count(&self) -> usize {
727        self.selects.len()
728    }
729
730    /// 返回表名
731    pub fn table(&self) -> &str {
732        &self.table
733    }
734
735    /// 重置所有条件(保留表名和方言)
736    pub fn reset(&mut self) -> &mut Self {
737        self.selects.clear();
738        self.wheres.clear();
739        self.orders.clear();
740        self.limit = None;
741        self.offset = None;
742        self
743    }
744}
745
746// ============================================================================
747// define_columns! 宏 — 为 Model 定义字段标记
748// ============================================================================
749
750/// 为 Model 定义一组类型安全的字段标记
751///
752/// # 示例
753///
754/// ```
755/// use sz_orm_core::define_columns;
756///
757/// struct User;
758///
759/// define_columns! {
760///     UserColumns for User table = "users" {
761///         Id => "id",
762///         Name => "name",
763///         Age => "age",
764///     }
765/// }
766/// ```
767///
768/// 生成如下代码:
769///
770/// ```rust,ignore
771/// #[derive(Clone, Copy)]
772/// pub struct UserColumns;
773///
774/// impl UserColumns {
775///     pub const Id: UserColumn = UserColumn { name: "id", table: "users" };
776///     pub const Name: UserColumn = UserColumn { name: "name", table: "users" };
777///     pub const Age: UserColumn = UserColumn { name: "age", table: "users" };
778/// }
779///
780/// #[derive(Clone, Copy)]
781/// pub struct UserColumn {
782///     pub name: &'static str,
783///     pub table: &'static str,
784/// }
785///
786/// impl Column<User> for UserColumn {
787///     fn name(&self) -> &'static str { self.name }
788///     fn table(&self) -> &'static str { self.table }
789/// }
790/// ```
791#[macro_export]
792macro_rules! define_columns {
793    (
794        $columns_struct:ident for $model:ident table = $table:literal {
795            $( $field:ident => $name:literal ),* $(,)?
796        }
797    ) => {
798        /// 字段标记类型(每个 Model 一组)
799        ///
800        /// 通过 `ModelColumns::FieldName` 引用对应字段,编译期检查拼写错误。
801        #[derive(Debug, Clone, Copy)]
802        pub struct $columns_struct {
803            /// 字段名
804            pub name: &'static str,
805            /// 表名
806            pub table: &'static str,
807        }
808
809        impl $crate::lambda::Column<$model> for $columns_struct {
810            fn name(&self) -> &'static str {
811                self.name
812            }
813
814            fn table(&self) -> &'static str {
815                self.table
816            }
817        }
818
819        impl $columns_struct {
820            // 允许 PascalCase 常量名(如 Id、Name),符合字段命名习惯
821            $(
822                #[allow(non_upper_case_globals, dead_code)]
823                pub const $field: $columns_struct = $columns_struct { name: $name, table: $table };
824            )*
825        }
826    };
827}
828
829// ============================================================================
830// 单元测试
831// ============================================================================
832
833#[cfg(test)]
834mod tests {
835    use super::*;
836    use crate::PostgreSqlDialect;
837    use crate::get_dialect;
838    use crate::DbType;
839
840    // 测试用 Model
841    struct User;
842
843    // 为 User 定义字段标记
844    define_columns! {
845        UserColumns for User table = "users" {
846            Id => "id",
847            Name => "name",
848            Age => "age",
849            Email => "email",
850        }
851    }
852
853    // 另一个 Model 用于测试表名隔离
854    struct Order;
855
856    define_columns! {
857        OrderColumns for Order table = "orders" {
858            OrderId => "order_id",
859            UserId => "user_id",
860            Total => "total",
861        }
862    }
863
864    // ===== Column trait 测试 =====
865
866    #[test]
867    fn test_column_name_and_table() {
868        assert_eq!(UserColumns::Id.name(), "id");
869        assert_eq!(UserColumns::Id.table(), "users");
870        assert_eq!(UserColumns::Name.name(), "name");
871        assert_eq!(UserColumns::Age.name(), "age");
872        assert_eq!(UserColumns::Email.name(), "email");
873    }
874
875    #[test]
876    fn test_column_for_different_models() {
877        assert_eq!(OrderColumns::OrderId.name(), "order_id");
878        assert_eq!(OrderColumns::OrderId.table(), "orders");
879        assert_eq!(OrderColumns::UserId.name(), "user_id");
880    }
881
882    // ===== LambdaWrapper 基础测试 =====
883
884    #[test]
885    fn test_new_wrapper() {
886        let w = LambdaWrapper::<User>::new("users");
887        assert_eq!(w.table(), "users");
888        assert_eq!(w.where_count(), 0);
889        assert_eq!(w.select_count(), 0);
890    }
891
892    #[test]
893    fn test_select_single() {
894        let mut w = LambdaWrapper::<User>::new("users");
895        w.select(UserColumns::Id);
896        let sql = w.build_select();
897        assert!(sql.contains("SELECT `id` FROM `users`"));
898    }
899
900    #[test]
901    fn test_select_multiple() {
902        let mut w = LambdaWrapper::<User>::new("users");
903        w.select(UserColumns::Id)
904            .select(UserColumns::Name)
905            .select(UserColumns::Age);
906        let sql = w.build_select();
907        assert!(sql.contains("`id`, `name`, `age`"));
908    }
909
910    #[test]
911    fn test_select_many() {
912        let mut w = LambdaWrapper::<User>::new("users");
913        w.select_many(&[UserColumns::Id, UserColumns::Name, UserColumns::Age]);
914        let sql = w.build_select();
915        assert!(sql.contains("`id`, `name`, `age`"));
916    }
917
918    #[test]
919    fn test_select_all_clears_selects() {
920        let mut w = LambdaWrapper::<User>::new("users");
921        w.select(UserColumns::Id);
922        assert_eq!(w.select_count(), 1);
923        w.select_all();
924        assert_eq!(w.select_count(), 0);
925        let sql = w.build_select();
926        assert!(sql.contains("SELECT * FROM"));
927    }
928
929    #[test]
930    fn test_default_select_is_star() {
931        let w = LambdaWrapper::<User>::new("users");
932        let sql = w.build_select();
933        assert!(sql.contains("SELECT * FROM `users`"));
934    }
935
936    // ===== WHERE 条件测试 =====
937
938    #[test]
939    fn test_where_eq() {
940        let mut w = LambdaWrapper::<User>::new("users");
941        w.eq(UserColumns::Id, Value::I64(1));
942        let sql = w.build_select();
943        assert!(sql.contains("WHERE `id` = 1"));
944    }
945
946    #[test]
947    fn test_where_ne() {
948        let mut w = LambdaWrapper::<User>::new("users");
949        w.ne(UserColumns::Id, Value::I64(1));
950        let sql = w.build_select();
951        assert!(sql.contains("`id` != 1"));
952    }
953
954    #[test]
955    fn test_where_gt_ge_lt_le() {
956        let mut w = LambdaWrapper::<User>::new("users");
957        w.gt(UserColumns::Age, Value::I64(18))
958            .ge(UserColumns::Age, Value::I64(20))
959            .lt(UserColumns::Age, Value::I64(65))
960            .le(UserColumns::Age, Value::I64(60));
961        let sql = w.build_select();
962        assert!(sql.contains("`age` > 18"));
963        assert!(sql.contains("`age` >= 20"));
964        assert!(sql.contains("`age` < 65"));
965        assert!(sql.contains("`age` <= 60"));
966    }
967
968    #[test]
969    fn test_where_like() {
970        let mut w = LambdaWrapper::<User>::new("users");
971        w.like(UserColumns::Name, Value::String("%alice%".to_string()));
972        let sql = w.build_select();
973        assert!(sql.contains("`name` LIKE '%alice%'"));
974    }
975
976    #[test]
977    fn test_where_is_null() {
978        let mut w = LambdaWrapper::<User>::new("users");
979        w.is_null(UserColumns::Email);
980        let sql = w.build_select();
981        assert!(sql.contains("`email` IS NULL"));
982    }
983
984    #[test]
985    fn test_where_is_not_null() {
986        let mut w = LambdaWrapper::<User>::new("users");
987        w.is_not_null(UserColumns::Email);
988        let sql = w.build_select();
989        assert!(sql.contains("`email` IS NOT NULL"));
990    }
991
992    #[test]
993    fn test_where_in() {
994        let mut w = LambdaWrapper::<User>::new("users");
995        w.r#in(
996            UserColumns::Id,
997            vec![Value::I64(1), Value::I64(2), Value::I64(3)],
998        );
999        let sql = w.build_select();
1000        assert!(sql.contains("`id` IN (1, 2, 3)"));
1001    }
1002
1003    #[test]
1004    fn test_where_not_in() {
1005        let mut w = LambdaWrapper::<User>::new("users");
1006        w.not_in(UserColumns::Id, vec![Value::I64(1), Value::I64(2)]);
1007        let sql = w.build_select();
1008        assert!(sql.contains("`id` NOT IN (1, 2)"));
1009    }
1010
1011    #[test]
1012    fn test_where_between() {
1013        let mut w = LambdaWrapper::<User>::new("users");
1014        w.between(UserColumns::Age, Value::I64(18), Value::I64(65));
1015        let sql = w.build_select();
1016        assert!(sql.contains("`age` BETWEEN 18 AND 65"));
1017    }
1018
1019    #[test]
1020    fn test_where_multiple_anded() {
1021        let mut w = LambdaWrapper::<User>::new("users");
1022        w.eq(UserColumns::Id, Value::I64(1))
1023            .gt(UserColumns::Age, Value::I64(18))
1024            .like(UserColumns::Name, Value::String("alice%".to_string()));
1025        let sql = w.build_select();
1026        assert!(sql.contains("`id` = 1"));
1027        assert!(sql.contains("`age` > 18"));
1028        assert!(sql.contains("`name` LIKE 'alice%'"));
1029        // 所有条件用 AND 连接
1030        assert!(sql.contains(" AND "));
1031    }
1032
1033    #[test]
1034    fn test_where_raw() {
1035        let mut w = LambdaWrapper::<User>::new("users");
1036        w.raw_where("name = 'alice' OR name = 'bob'");
1037        let sql = w.build_select();
1038        assert!(sql.contains("name = 'alice' OR name = 'bob'"));
1039    }
1040
1041    // ===== ORDER BY / LIMIT / OFFSET 测试 =====
1042
1043    #[test]
1044    fn test_order_by_asc() {
1045        let mut w = LambdaWrapper::<User>::new("users");
1046        w.order_by_asc(UserColumns::Name);
1047        let sql = w.build_select();
1048        assert!(sql.contains("ORDER BY `name` ASC"));
1049    }
1050
1051    #[test]
1052    fn test_order_by_desc() {
1053        let mut w = LambdaWrapper::<User>::new("users");
1054        w.order_by_desc(UserColumns::Id);
1055        let sql = w.build_select();
1056        assert!(sql.contains("ORDER BY `id` DESC"));
1057    }
1058
1059    #[test]
1060    fn test_order_by_multiple() {
1061        let mut w = LambdaWrapper::<User>::new("users");
1062        w.order_by_asc(UserColumns::Name)
1063            .order_by_desc(UserColumns::Id);
1064        let sql = w.build_select();
1065        assert!(sql.contains("ORDER BY `name` ASC, `id` DESC"));
1066    }
1067
1068    #[test]
1069    fn test_limit() {
1070        let mut w = LambdaWrapper::<User>::new("users");
1071        w.limit(10);
1072        let sql = w.build_select();
1073        assert!(sql.contains("LIMIT 10"));
1074    }
1075
1076    #[test]
1077    fn test_offset() {
1078        let mut w = LambdaWrapper::<User>::new("users");
1079        w.limit(10).offset(20);
1080        let sql = w.build_select();
1081        assert!(sql.contains("LIMIT 10"));
1082        assert!(sql.contains("OFFSET 20"));
1083    }
1084
1085    #[test]
1086    fn test_page() {
1087        let mut w = LambdaWrapper::<User>::new("users");
1088        w.page(3, 20); // 第 3 页,每页 20 条
1089        let sql = w.build_select();
1090        assert!(sql.contains("LIMIT 20"));
1091        assert!(sql.contains("OFFSET 40")); // (3-1) * 20
1092    }
1093
1094    #[test]
1095    fn test_page_1_no_offset() {
1096        let mut w = LambdaWrapper::<User>::new("users");
1097        w.page(1, 10);
1098        let sql = w.build_select();
1099        assert!(sql.contains("LIMIT 10"));
1100        assert!(!sql.contains("OFFSET")); // 第 1 页无 OFFSET
1101    }
1102
1103    // ===== SQL 生成测试 =====
1104
1105    #[test]
1106    fn test_build_count() {
1107        let mut w = LambdaWrapper::<User>::new("users");
1108        w.gt(UserColumns::Age, Value::I64(18));
1109        let sql = w.build_count();
1110        assert!(sql.contains("SELECT COUNT(*) FROM `users`"));
1111        assert!(sql.contains("`age` > 18"));
1112        // 不应包含 ORDER BY / LIMIT
1113        assert!(!sql.contains("ORDER BY"));
1114        assert!(!sql.contains("LIMIT"));
1115    }
1116
1117    #[test]
1118    fn test_build_exists() {
1119        let mut w = LambdaWrapper::<User>::new("users");
1120        w.eq(UserColumns::Id, Value::I64(1));
1121        let sql = w.build_exists();
1122        assert!(sql.starts_with("SELECT EXISTS("));
1123        assert!(sql.contains("SELECT 1 FROM `users`"));
1124        assert!(sql.contains("`id` = 1"));
1125        assert!(sql.ends_with(") AS exists_flag"));
1126    }
1127
1128    #[test]
1129    fn test_build_delete() {
1130        let mut w = LambdaWrapper::<User>::new("users");
1131        w.eq(UserColumns::Id, Value::I64(1));
1132        let sql = w.build_delete();
1133        assert!(sql.starts_with("DELETE FROM `users`"));
1134        assert!(sql.contains("WHERE `id` = 1"));
1135    }
1136
1137    // ===== P3-2 修复:软删除 / 多租户测试 =====
1138
1139    #[test]
1140    fn test_soft_delete_select_appends_filter() {
1141        // 启用软删除后,build_select 应自动追加 `deleted = 0`
1142        let w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1143            column: "deleted".to_string(),
1144            not_deleted_value: Value::I64(0),
1145            deleted_value: Value::I64(1),
1146        });
1147        let sql = w.build_select();
1148        assert!(
1149            sql.contains("`deleted` = 0"),
1150            "软删除 SELECT 应追加 `deleted` = 0: {}",
1151            sql
1152        );
1153    }
1154
1155    #[test]
1156    fn test_soft_delete_count_appends_filter() {
1157        // 启用软删除后,build_count 应自动追加 `deleted = 0`
1158        let w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1159            column: "deleted".to_string(),
1160            not_deleted_value: Value::I64(0),
1161            deleted_value: Value::I64(1),
1162        });
1163        let sql = w.build_count();
1164        assert!(
1165            sql.contains("`deleted` = 0"),
1166            "软删除 COUNT 应追加 `deleted` = 0: {}",
1167            sql
1168        );
1169    }
1170
1171    #[test]
1172    fn test_soft_delete_exists_appends_filter() {
1173        // 启用软删除后,build_exists 应自动追加 `deleted = 0`
1174        let w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1175            column: "deleted".to_string(),
1176            not_deleted_value: Value::I64(0),
1177            deleted_value: Value::I64(1),
1178        });
1179        let sql = w.build_exists();
1180        assert!(
1181            sql.contains("`deleted` = 0"),
1182            "软删除 EXISTS 应追加 `deleted` = 0: {}",
1183            sql
1184        );
1185    }
1186
1187    #[test]
1188    fn test_soft_delete_delete_becomes_update() {
1189        // 启用软删除后,build_delete 应改为 UPDATE ... SET deleted = 1
1190        let mut w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1191            column: "deleted".to_string(),
1192            not_deleted_value: Value::I64(0),
1193            deleted_value: Value::I64(1),
1194        });
1195        w.eq(UserColumns::Id, Value::I64(42));
1196        let sql = w.build_delete();
1197        assert!(
1198            sql.starts_with("UPDATE `users` SET `deleted` = 1"),
1199            "软删除应为 UPDATE:{}",
1200            sql
1201        );
1202        assert!(sql.contains("`id` = 42"), "应保留用户 WHERE 条件:{}", sql);
1203        assert!(
1204            sql.contains("`deleted` = 0"),
1205            "应追加未删除条件防止重复删除:{}",
1206            sql
1207        );
1208        assert!(
1209            !sql.starts_with("DELETE"),
1210            "软删除不应生成 DELETE 语句:{}",
1211            sql
1212        );
1213    }
1214
1215    #[test]
1216    fn test_tenant_select_appends_filter() {
1217        // 启用多租户后,build_select 应自动追加 `tenant_id` = 42
1218        let w = LambdaWrapper::<User>::new("users").with_tenant(TenantConfig {
1219            column: "tenant_id".to_string(),
1220            tenant_id: Value::I64(42),
1221        });
1222        let sql = w.build_select();
1223        assert!(
1224            sql.contains("`tenant_id` = 42"),
1225            "多租户 SELECT 应追加 `tenant_id` = 42: {}",
1226            sql
1227        );
1228    }
1229
1230    #[test]
1231    fn test_tenant_delete_appends_filter() {
1232        // 启用多租户后,build_delete 应自动追加 `tenant_id` = 42 防止跨租户删除
1233        let mut w = LambdaWrapper::<User>::new("users").with_tenant(TenantConfig {
1234            column: "tenant_id".to_string(),
1235            tenant_id: Value::I64(42),
1236        });
1237        w.eq(UserColumns::Id, Value::I64(1));
1238        let sql = w.build_delete();
1239        assert!(
1240            sql.contains("`tenant_id` = 42"),
1241            "多租户 DELETE 应追加 `tenant_id` = 42: {}",
1242            sql
1243        );
1244    }
1245
1246    #[test]
1247    fn test_soft_delete_and_tenant_combined() {
1248        // 同时启用软删除和多租户
1249        let mut w = LambdaWrapper::<User>::new("users")
1250            .with_soft_delete(SoftDeleteConfig {
1251                column: "deleted".to_string(),
1252                not_deleted_value: Value::I64(0),
1253                deleted_value: Value::I64(1),
1254            })
1255            .with_tenant(TenantConfig {
1256                column: "tenant_id".to_string(),
1257                tenant_id: Value::I64(99),
1258            });
1259        w.eq(UserColumns::Id, Value::I64(7));
1260
1261        // SELECT 应同时包含两个隐式条件
1262        let sql = w.build_select();
1263        assert!(sql.contains("`deleted` = 0"), "应包含软删除条件: {}", sql);
1264        assert!(
1265            sql.contains("`tenant_id` = 99"),
1266            "应包含多租户条件: {}",
1267            sql
1268        );
1269        assert!(sql.contains("`id` = 7"), "应包含用户条件: {}", sql);
1270
1271        // DELETE 应改为 UPDATE,并追加所有条件
1272        let del_sql = w.build_delete();
1273        assert!(del_sql.starts_with("UPDATE `users` SET `deleted` = 1"));
1274        assert!(del_sql.contains("`tenant_id` = 99"));
1275        assert!(del_sql.contains("`deleted` = 0"));
1276        assert!(del_sql.contains("`id` = 7"));
1277    }
1278
1279    #[test]
1280    fn test_no_soft_delete_no_tenant_backward_compat() {
1281        // 未启用软删除/多租户时,行为与之前一致
1282        let mut w = LambdaWrapper::<User>::new("users");
1283        w.eq(UserColumns::Id, Value::I64(1));
1284        let sql = w.build_select();
1285        assert!(!sql.contains("deleted"));
1286        assert!(!sql.contains("tenant_id"));
1287
1288        let del_sql = w.build_delete();
1289        assert!(del_sql.starts_with("DELETE FROM"));
1290    }
1291
1292    // ===== 方言测试 =====
1293
1294    #[test]
1295    fn test_with_postgres_dialect() {
1296        let dialect: Box<dyn Dialect> = Box::new(PostgreSqlDialect);
1297        let mut w = LambdaWrapper::<User>::with_dialect("users", dialect);
1298        w.eq(UserColumns::Id, Value::I64(1));
1299        let sql = w.build_select();
1300        assert!(sql.contains("\"users\""));
1301        assert!(sql.contains("\"id\" = 1"));
1302    }
1303
1304    #[test]
1305    fn test_postgres_select() {
1306        let dialect: Box<dyn Dialect> = Box::new(PostgreSqlDialect);
1307        let mut w = LambdaWrapper::<User>::with_dialect("users", dialect);
1308        w.select(UserColumns::Id).select(UserColumns::Name);
1309        let sql = w.build_select();
1310        assert!(sql.contains("\"id\", \"name\""));
1311    }
1312
1313    // ===== 完整查询测试 =====
1314
1315    #[test]
1316    fn test_complex_query() {
1317        let mut w = LambdaWrapper::<User>::new("users");
1318        w.select(UserColumns::Id)
1319            .select(UserColumns::Name)
1320            .select(UserColumns::Age)
1321            .gt(UserColumns::Age, Value::I64(18))
1322            .like(UserColumns::Name, Value::String("a%".to_string()))
1323            .is_not_null(UserColumns::Email)
1324            .order_by_desc(UserColumns::Id)
1325            .limit(10)
1326            .offset(20);
1327
1328        let sql = w.build_select();
1329        assert!(sql.contains("SELECT `id`, `name`, `age` FROM `users`"));
1330        assert!(sql.contains("`age` > 18"));
1331        assert!(sql.contains("`name` LIKE 'a%'"));
1332        assert!(sql.contains("`email` IS NOT NULL"));
1333        assert!(sql.contains("ORDER BY `id` DESC"));
1334        assert!(sql.contains("LIMIT 10"));
1335        assert!(sql.contains("OFFSET 20"));
1336    }
1337
1338    #[test]
1339    fn test_reset_clears_all() {
1340        let mut w = LambdaWrapper::<User>::new("users");
1341        w.select(UserColumns::Id)
1342            .eq(UserColumns::Id, Value::I64(1))
1343            .order_by_asc(UserColumns::Name)
1344            .limit(10);
1345
1346        w.reset();
1347        assert_eq!(w.select_count(), 0);
1348        assert_eq!(w.where_count(), 0);
1349        let sql = w.build_select();
1350        assert!(sql.contains("SELECT * FROM `users`"));
1351        assert!(!sql.contains("WHERE"));
1352        assert!(!sql.contains("ORDER BY"));
1353        assert!(!sql.contains("LIMIT"));
1354    }
1355
1356    // ===== 跨 Model 类型隔离测试 =====
1357
1358    #[test]
1359    fn test_different_models_dont_share_columns() {
1360        let mut user_w = LambdaWrapper::<User>::new("users");
1361        user_w.eq(UserColumns::Id, Value::I64(1));
1362
1363        let mut order_w = LambdaWrapper::<Order>::new("orders");
1364        order_w.eq(OrderColumns::OrderId, Value::I64(100));
1365
1366        let user_sql = user_w.build_select();
1367        let order_sql = order_w.build_select();
1368
1369        assert!(user_sql.contains("`users`"));
1370        assert!(user_sql.contains("`id` = 1"));
1371        assert!(order_sql.contains("`orders`"));
1372        assert!(order_sql.contains("`order_id` = 100"));
1373    }
1374
1375    // ===== 编译期类型安全演示(运行时验证)=====
1376
1377    #[test]
1378    fn test_type_safety_compile_time_check() {
1379        // 以下代码若取消注释将无法编译:
1380        // let mut w = LambdaWrapper::<User>::new("users");
1381        // w.eq(OrderColumns::OrderId, Value::I64(1)); // OrderColumns 不能用于 User 的 wrapper
1382
1383        // 这里通过正常调用验证类型隔离工作正常
1384        let mut w = LambdaWrapper::<User>::new("users");
1385        w.eq(UserColumns::Id, Value::I64(1));
1386        assert_eq!(w.where_count(), 1);
1387    }
1388
1389    #[test]
1390    fn test_string_value_escape() {
1391        // v0.2.2 修复 H-1:默认使用 MySqlDialect,单引号转义为 \'
1392        let mut w = LambdaWrapper::<User>::new("users");
1393        w.eq(UserColumns::Name, Value::String("O'Brien".to_string()));
1394        let sql = w.build_select();
1395        // MySQL 方言下字符串中的单引号应被转义为 \'
1396        assert!(sql.contains("'O\\'Brien'"));
1397
1398        // 使用 PostgreSQL 方言时,单引号转义为 ''
1399        let pg_dialect: Box<dyn Dialect> = get_dialect(DbType::PostgreSQL).unwrap();
1400        let mut w_pg = LambdaWrapper::<User>::with_dialect("users", pg_dialect);
1401        w_pg.eq(UserColumns::Name, Value::String("O'Brien".to_string()));
1402        let sql_pg = w_pg.build_select();
1403        assert!(sql_pg.contains("'O''Brien'"));
1404    }
1405
1406    // ===== 使用真实方言验证集成 =====
1407
1408    #[test]
1409    fn test_with_real_mysql_dialect() {
1410        let dialect: Box<dyn Dialect> = get_dialect(DbType::MySQL).unwrap();
1411        let mut w = LambdaWrapper::<User>::with_dialect("users", dialect);
1412        w.select(UserColumns::Id)
1413            .select(UserColumns::Name)
1414            .eq(UserColumns::Id, Value::I64(42))
1415            .order_by_desc(UserColumns::Id);
1416
1417        let sql = w.build_select();
1418        assert!(sql.contains("SELECT `id`, `name` FROM `users`"));
1419        assert!(sql.contains("`id` = 42"));
1420        assert!(sql.contains("ORDER BY `id` DESC"));
1421    }
1422}