Skip to main content

sz_orm_core/
query.rs

1//! 查询构造器
2//!
3//! 提供类似 ThinkORM 的链式查询构造 API
4//!
5//! # P0-1 软删除集成(v1.3.0+)
6//!
7//! 当 `M: Model` 实现了 `soft_delete_field()` 返回 `Some(field)` 时,
8//! `QueryBuilder` 会在以下场景自动追加 `WHERE {field} IS NULL`:
9//! - `build_select` / `build_select_with_params`
10//! - `build_count` / `build_exists` / `build_max` / `build_min` / `build_sum` / `build_avg`
11//! - `build_update` / `build_update_with_params`(防止更新已删除记录)
12//! - `build_delete` / `build_delete_with_params`(自动转为 `UPDATE SET {field} = NOW()`)
13//!
14//! 使用 `without_soft_delete()` 可临时禁用软删除过滤(用于查询已删除记录)。
15//!
16//! # P0-2 参数化 WHERE 条件(v1.3.0+)
17//!
18//! 新增类型安全的参数化 WHERE API:
19//! - `where_eq(field, value)` / `where_ne` / `where_gt` / `where_ge` / `where_lt` / `where_le`
20//! - `where_like(field, pattern)`
21//!
22//! 这些方法使用 `?` 占位符 + `Value` 绑定,杜绝 SQL 注入。
23//! 原有 `where_cond(condition: impl Into<String>)` 因字符串拼接存在注入风险,
24//! 保留以兼容复杂表达式(如 `age > 18 AND status = 'active'`),但文档标记为不推荐。
25
26use crate::db_type::DbType;
27use crate::dialect::Dialect;
28use crate::dialect::LockType;
29use crate::model::Model;
30use crate::typed::TypedColumn;
31use crate::value::Value;
32use std::fmt;
33use std::time::Duration;
34
35/// 用于构造 SQL 查询的查询构造器
36pub struct QueryBuilder<M: Model> {
37    table: Option<String>,
38    select_columns: Vec<String>,
39    where_conditions: Vec<WhereCondition>,
40    order_by: Vec<OrderClause>,
41    group_by: Vec<String>,
42    having_conditions: Vec<WhereCondition>,
43    limit_value: Option<usize>,
44    offset_value: Option<usize>,
45    joins: Vec<JoinClause>,
46    dialect: Box<dyn Dialect>,
47    /// P0-1:是否禁用软删除过滤(true 表示禁用,查询包含已删除记录)
48    soft_delete_disabled: bool,
49    /// P0-3:当前租户 ID(运行时注入)。设置后自动追加 `WHERE {tenant_field} = ?`
50    tenant_id_value: Option<i64>,
51    /// P0-3:是否禁用租户过滤(true 表示禁用,跨租户查询)
52    tenant_disabled: bool,
53    /// P2-5:Keyset 分页游标条件(field, value, direction)
54    ///
55    /// 设置后,`build_select`/`build_select_with_params` 会追加 `WHERE {field} > ?` 或
56    /// `WHERE {field} < ?` 条件(取决于排序方向),实现基于游标的高效分页。
57    /// 与 OFFSET 分页相比,Keyset 分页在大数据集下性能稳定,不受数据插入/删除影响。
58    keyset_cursor: Option<KeysetCursor>,
59    /// P2-2:查询缓存 TTL(TASK-022)
60    ///
61    /// 设置后,查询结果会被缓存指定时长。相同 SQL + 参数在 TTL 内返回缓存结果。
62    /// 空结果也会缓存(TTL 缩短为 1/10),避免缓存穿透。
63    cache_ttl: Option<Duration>,
64    /// P2-3:行锁类型(TASK-025/026)
65    ///
66    /// 设置后,`build_select`/`build_select_with_params` 会在 SQL 末尾追加锁子句。
67    /// - `ForUpdate`:排他锁(`FOR UPDATE`)
68    /// - `Shared`:共享锁(`FOR SHARE` / `LOCK IN SHARE MODE`)
69    ///
70    /// SQLite 不支持行锁,设置后会返回 `DbError::QueryError`。
71    lock_type: Option<LockType>,
72    /// P2-4:INSERT OR IGNORE 标志(TASK-028)
73    ///
74    /// 设置后,`build_insert` 会生成 `INSERT IGNORE INTO`(MySQL)或
75    /// `INSERT OR IGNORE INTO`(PG/SQLite),避免主键/唯一键冲突时报错。
76    insert_or_ignore: bool,
77    #[allow(dead_code)]
78    model: std::marker::PhantomData<M>,
79}
80
81/// P2-5:Keyset 分页游标
82///
83/// 表示一个基于排序字段值的分页游标。结合 `ORDER BY {field} {direction}` 和
84/// `WHERE {field} {op} ?` 实现游标分页。
85///
86/// - `After(value)` + `Asc`:查询 `field > value` 的记录(下一页)
87/// - `Before(value)` + `Desc`:查询 `field < value` 的记录(上一页)
88#[derive(Debug, Clone)]
89struct KeysetCursor {
90    /// 排序字段名
91    field: String,
92    /// 游标值(上一页/下一页最后一行的该字段值)
93    value: Value,
94    /// 游标方向:After = 下一页(field > value),Before = 上一页(field < value)
95    direction: KeysetDirection,
96}
97
98/// P2-5:Keyset 游标方向
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100enum KeysetDirection {
101    /// 下一页:`WHERE field > cursor_value`(配合 ASC 排序)
102    After,
103    /// 上一页:`WHERE field < cursor_value`(配合 DESC 排序)
104    Before,
105}
106
107#[derive(Debug, Clone)]
108#[allow(dead_code)]
109enum WhereCondition {
110    /// 原始字符串条件(AND)— **存在注入风险,不推荐使用**
111    ///
112    /// 保留以兼容复杂表达式如 `age > 18 AND status = 'active'`。
113    /// 调用方必须确保字符串来自可信来源。
114    And(String),
115    /// 原始字符串条件(OR)— **存在注入风险,不推荐使用**
116    Or(String),
117    /// P0-2:参数化等值条件 `field = ?`
118    Eq(String, Value),
119    /// P0-2:参数化不等条件 `field != ?`
120    Ne(String, Value),
121    /// P0-2:参数化大于条件 `field > ?`
122    Gt(String, Value),
123    /// P0-2:参数化大于等于条件 `field >= ?`
124    Ge(String, Value),
125    /// P0-2:参数化小于条件 `field < ?`
126    Lt(String, Value),
127    /// P0-2:参数化小于等于条件 `field <= ?`
128    Le(String, Value),
129    /// P0-2:参数化 LIKE 条件 `field LIKE ?`
130    Like(String, Value),
131    /// P0-2:参数化 OR 等值条件 `OR field = ?`
132    OrEq(String, Value),
133    /// P0-2:参数化 OR 不等条件 `OR field != ?`
134    OrNe(String, Value),
135    /// P0-2:参数化 OR 大于条件 `OR field > ?`
136    OrGt(String, Value),
137    /// P0-2:参数化 OR 大于等于条件 `OR field >= ?`
138    OrGe(String, Value),
139    /// P0-2:参数化 OR 小于条件 `OR field < ?`
140    OrLt(String, Value),
141    /// P0-2:参数化 OR 小于等于条件 `OR field <= ?`
142    OrLe(String, Value),
143    /// P0-2:参数化 OR LIKE 条件 `OR field LIKE ?`
144    OrLike(String, Value),
145    In(String, Vec<Value>),
146    NotIn(String, Vec<Value>),
147    Between(String, Value, Value),
148    NotBetween(String, Value, Value),
149    Null(String),
150    NotNull(String),
151    Exists(String),
152    NotExists(String),
153}
154
155#[derive(Debug, Clone)]
156struct OrderClause {
157    field: String,
158    direction: OrderDirection,
159}
160
161#[derive(Debug, Clone)]
162enum OrderDirection {
163    Asc,
164    Desc,
165}
166
167#[derive(Debug, Clone)]
168#[allow(dead_code)]
169enum JoinClause {
170    Inner(String, String, String),
171    Left(String, String, String),
172    Right(String, String, String),
173    Cross(String, String),
174}
175
176impl<M: Model> QueryBuilder<M> {
177    pub fn new(dialect: Box<dyn Dialect>) -> Self {
178        Self {
179            table: None,
180            select_columns: vec!["*".to_string()],
181            where_conditions: Vec::new(),
182            order_by: Vec::new(),
183            group_by: Vec::new(),
184            having_conditions: Vec::new(),
185            limit_value: None,
186            offset_value: None,
187            joins: Vec::new(),
188            dialect,
189            soft_delete_disabled: false,
190            tenant_id_value: None,
191            tenant_disabled: false,
192            keyset_cursor: None,
193            cache_ttl: None,
194            lock_type: None,
195            insert_or_ignore: false,
196            model: std::marker::PhantomData,
197        }
198    }
199
200    pub fn table(mut self, table: impl Into<String>) -> Self {
201        self.table = Some(table.into());
202        self
203    }
204
205    /// P0-1:临时禁用软删除过滤,用于查询已删除的记录。
206    ///
207    /// 等价于 SeaORM 的 `Entity::find().filter(Column::DeletedAt.is_not_null())`
208    /// 或 Laravel Eloquent 的 `Model::withTrashed()`。
209    ///
210    /// # 示例
211    ///
212    /// ```ignore
213    /// use sz_orm_core::query::QueryBuilder;
214    /// use sz_orm_core::dialect::MySqlDialect;
215    ///
216    /// // 查询包含已软删除的用户
217    /// let sql = QueryBuilder::<User>::new(Box::new(MySqlDialect))
218    ///     .table("users")
219    ///     .without_soft_delete()
220    ///     .build_select();
221    /// // 不会自动追加 WHERE deleted_at IS NULL
222    /// ```
223    pub fn without_soft_delete(mut self) -> Self {
224        self.soft_delete_disabled = true;
225        self
226    }
227
228    /// P0-1:返回软删除过滤是否被禁用
229    pub fn is_soft_delete_disabled(&self) -> bool {
230        self.soft_delete_disabled
231    }
232
233    /// P2-2:设置查询缓存 TTL(TASK-022)
234    ///
235    /// 设置后,查询结果会被缓存指定时长。相同 SQL + 参数在 TTL 内返回缓存结果。
236    /// 空结果也会缓存(TTL 缩短为 1/10),避免缓存穿透。
237    ///
238    /// # 示例
239    ///
240    /// ```ignore
241    /// use sz_orm_core::query::QueryBuilder;
242    /// use sz_orm_core::dialect::MySqlDialect;
243    /// use std::time::Duration;
244    ///
245    /// // 缓存热点查询 5 分钟
246    /// let sql = QueryBuilder::<User>::new(Box::new(MySqlDialect))
247    ///     .table("users")
248    ///     .where_eq("status", 1)
249    ///     .cache_ttl(Duration::from_secs(300))
250    ///     .build_select_with_params();
251    /// ```
252    pub fn cache_ttl(mut self, ttl: Duration) -> Self {
253        self.cache_ttl = Some(ttl);
254        self
255    }
256
257    /// P2-2:返回当前缓存 TTL
258    pub fn get_cache_ttl(&self) -> Option<Duration> {
259        self.cache_ttl
260    }
261
262    /// P2-3:设置排他锁(FOR UPDATE)(TASK-025)
263    ///
264    /// 锁定查询结果行,其他事务无法读取或修改这些行,直到当前事务提交。
265    ///
266    /// # 各方言行为
267    ///
268    /// - MySQL:生成 `FOR UPDATE`
269    /// - PostgreSQL:生成 `FOR UPDATE`
270    /// - SQLite:返回 `Err(DbError::QueryError)`(不支持行锁)
271    ///
272    /// # 示例
273    ///
274    /// ```ignore
275    /// use sz_orm_core::query::QueryBuilder;
276    /// use sz_orm_core::dialect::MySqlDialect;
277    ///
278    /// // 锁定用户行以供更新
279    /// let sql = QueryBuilder::<User>::new(Box::new(MySqlDialect))
280    ///     .table("users")
281    ///     .where_eq("id", 1)
282    ///     .lock_for_update()
283    ///     .build_select_with_params();
284    /// // 生成:SELECT * FROM users WHERE id = ? FOR UPDATE
285    /// ```
286    pub fn lock_for_update(mut self) -> Result<Self, crate::error::DbError> {
287        if !self.dialect.supports_lock_for_update() {
288            return Err(crate::error::DbError::QueryError(
289                "FOR UPDATE lock is not supported by this dialect".to_string(),
290            ));
291        }
292        self.lock_type = Some(LockType::ForUpdate);
293        Ok(self)
294    }
295
296    /// P2-3:设置共享锁(FOR SHARE / LOCK IN SHARE MODE)(TASK-026)
297    ///
298    /// 锁定查询结果行,其他事务可读取但无法修改这些行,直到当前事务提交。
299    ///
300    /// # 各方言行为
301    ///
302    /// - MySQL:生成 `LOCK IN SHARE MODE`
303    /// - PostgreSQL:生成 `FOR SHARE`
304    /// - SQLite:返回 `Err(DbError::QueryError)`(不支持行锁)
305    ///
306    /// # 示例
307    ///
308    /// ```ignore
309    /// use sz_orm_core::query::QueryBuilder;
310    /// use sz_orm_core::dialect::PostgreSqlDialect;
311    ///
312    /// // 共享锁定用户行
313    /// let sql = QueryBuilder::<User>::new(Box::new(PostgreSqlDialect))
314    ///     .table("users")
315    ///     .where_eq("id", 1)
316    ///     .lock_shared()
317    ///     .build_select_with_params();
318    /// // 生成:SELECT * FROM users WHERE id = ? FOR SHARE
319    /// ```
320    pub fn lock_shared(mut self) -> Result<Self, crate::error::DbError> {
321        if !self.dialect.supports_lock_shared() {
322            return Err(crate::error::DbError::QueryError(
323                "Shared lock is not supported by this dialect".to_string(),
324            ));
325        }
326        self.lock_type = Some(LockType::Shared);
327        Ok(self)
328    }
329
330    /// P2-4:设置 INSERT OR IGNORE 标志(TASK-028)
331    ///
332    /// 设置后,`build_insert` 会生成 `INSERT IGNORE INTO`(MySQL)或
333    /// `INSERT OR IGNORE INTO`(PG/SQLite),避免主键/唯一键冲突时报错。
334    ///
335    /// # 示例
336    ///
337    /// ```ignore
338    /// use sz_orm_core::query::QueryBuilder;
339    /// use sz_orm_core::dialect::MySqlDialect;
340    ///
341    /// // 忽略重复插入
342    /// let sql = QueryBuilder::<User>::new(Box::new(MySqlDialect))
343    ///     .table("users")
344    ///     .insert_or_ignore()
345    ///     .build_insert(&[("name", Value::String("Alice".to_string()))]);
346    /// // 生成:INSERT IGNORE INTO `users` (`name`) VALUES (?)
347    /// ```
348    pub fn insert_or_ignore(mut self) -> Self {
349        self.insert_or_ignore = true;
350        self
351    }
352
353    /// P2-3:返回当前锁类型
354    pub fn get_lock_type(&self) -> Option<LockType> {
355        self.lock_type
356    }
357
358    /// P2-4:返回 INSERT OR IGNORE 标志
359    pub fn is_insert_or_ignore(&self) -> bool {
360        self.insert_or_ignore
361    }
362
363    /// P0-4:克隆当前 `QueryBuilder` 用于 COUNT 查询。
364    ///
365    /// 保留 `table` / `joins` / `where_conditions` / `group_by` / `having_conditions`
366    /// 以及软删除 / 租户过滤设置,清空 `select_columns` / `order_by` / `limit` / `offset` / `keyset_cursor`。
367    ///
368    /// 用于 `PaginatorTrait` 的 COUNT 子查询构造。
369    pub fn clone_for_count(&self) -> Self {
370        Self {
371            table: self.table.clone(),
372            select_columns: vec!["*".to_string()],
373            where_conditions: self.where_conditions.clone(),
374            order_by: Vec::new(),
375            group_by: self.group_by.clone(),
376            having_conditions: self.having_conditions.clone(),
377            limit_value: None,
378            offset_value: None,
379            joins: self.joins.clone(),
380            dialect: self.dialect.clone_box(),
381            soft_delete_disabled: self.soft_delete_disabled,
382            tenant_id_value: self.tenant_id_value,
383            tenant_disabled: self.tenant_disabled,
384            keyset_cursor: None,
385            cache_ttl: None,         // COUNT 查询不缓存
386            lock_type: None,         // COUNT 查询不加锁
387            insert_or_ignore: false, // COUNT 查询不使用 INSERT
388            model: std::marker::PhantomData,
389        }
390    }
391
392    /// P0-1:返回当前 Model 的软删除字段名(若启用)
393    ///
394    /// 内部使用,用于 `build_*` 方法决定是否追加 `WHERE {field} IS NULL`。
395    fn soft_delete_field(&self) -> Option<&'static str> {
396        if self.soft_delete_disabled {
397            return None;
398        }
399        M::soft_delete_field()
400    }
401
402    /// P0-1:构造软删除过滤条件 SQL 片段(不含 `AND` 前缀)
403    ///
404    /// 返回 `None` 表示无需过滤;返回 `Some(sql)` 表示追加 `AND {sql}` 到 WHERE 子句。
405    fn build_soft_delete_condition(&self) -> Option<String> {
406        self.soft_delete_field()
407            .map(|field| format!("{} IS NULL", self.dialect.quote(field)))
408    }
409
410    // ===================== P0-3 多租户过滤 =====================
411
412    /// P0-3:设置当前租户 ID,启用多租户自动过滤。
413    ///
414    /// 当 `M::tenant_field()` 返回 `Some(field)` 时,`QueryBuilder` 会在以下场景
415    /// 自动追加 `WHERE {field} = ?`(参数化,值通过 `params` 绑定):
416    /// - `build_select` / `build_select_with_params`
417    /// - `build_count` / `build_exists` / `build_max` / `build_min` / `build_sum` / `build_avg`
418    /// - `build_update` / `build_update_with_params`(防止跨租户更新)
419    /// - `build_delete` / `build_delete_with_params`(防止跨租户删除)
420    ///
421    /// 使用 `without_tenant()` 可临时禁用租户过滤(用于跨租户管理查询)。
422    ///
423    /// # 示例
424    ///
425    /// ```ignore
426    /// use sz_orm_core::query::QueryBuilder;
427    /// use sz_orm_core::dialect::MySqlDialect;
428    ///
429    /// let (sql, params) = QueryBuilder::<Order>::new(Box::new(MySqlDialect))
430    ///     .table("orders")
431    ///     .with_tenant_id(42)
432    ///     .build_select_with_params();
433    /// // sql => "SELECT * FROM `orders` WHERE `tenant_id` = ?"
434    /// // params => [Value::I64(42)]
435    /// ```
436    pub fn with_tenant_id(mut self, tenant_id: i64) -> Self {
437        self.tenant_id_value = Some(tenant_id);
438        self
439    }
440
441    /// P0-3:临时禁用租户过滤,用于跨租户管理查询。
442    ///
443    /// 等价于 Laravel Eloquent 的全局作用域禁用。
444    pub fn without_tenant(mut self) -> Self {
445        self.tenant_disabled = true;
446        self
447    }
448
449    /// P0-3:返回租户过滤是否被禁用
450    pub fn is_tenant_disabled(&self) -> bool {
451        self.tenant_disabled
452    }
453
454    /// P0-3:返回当前 Model 的租户字段名(若启用且未禁用)
455    ///
456    /// 内部使用,用于 `build_*` 方法决定是否追加租户条件。
457    fn tenant_field(&self) -> Option<&'static str> {
458        if self.tenant_disabled {
459            return None;
460        }
461        M::tenant_field()
462    }
463
464    /// P0-3:返回当前租户 ID(若设置了且未禁用)
465    fn tenant_id_value(&self) -> Option<i64> {
466        if self.tenant_disabled {
467            return None;
468        }
469        self.tenant_id_value
470    }
471
472    /// P0-3:构造租户过滤条件(SQL 片段 + 参数值)
473    ///
474    /// 返回 `None` 表示无需过滤;返回 `Some((sql, value))` 表示追加 `AND {sql}` 到 WHERE 子句,
475    /// 并将 `value` 加入参数列表。
476    fn build_tenant_condition(&self) -> Option<(String, Value)> {
477        let field = self.tenant_field()?;
478        let tid = self.tenant_id_value()?;
479        Some((
480            format!("{} = ?", self.dialect.quote(field)),
481            Value::I64(tid),
482        ))
483    }
484
485    /// 设置 SELECT 列。
486    ///
487    /// **M-3 安全警告**:本方法直接拼接 `columns` 到 SQL,**不**进行标识符校验或 quote。
488    /// 调用方必须确保 `columns` 来自可信来源(硬编码或经 `sql_safety::validate_identifier`
489    /// 校验)。若列名可能来自不可信输入,请使用 [`QueryBuilder::select_quoted`]。
490    ///
491    /// 本方法保留原行为以兼容复杂表达式(如 `COUNT(*)`、`users.id AS uid`)。
492    pub fn select(mut self, columns: Vec<&str>) -> Self {
493        self.select_columns = columns.into_iter().map(|s| s.to_string()).collect();
494        self
495    }
496
497    /// M-3 修复:安全的 SELECT 列设置,自动校验每个列名并 quote。
498    ///
499    /// 每个 `column` 必须通过 `sql_safety::validate_identifier` 校验
500    /// (仅允许 ASCII 字母数字 + 下划线,不以数字开头,长度 1-63)。
501    /// 校验失败时返回 `DbError::InvalidInput`。
502    ///
503    /// 对于复杂表达式(如 `COUNT(*)`、`users.id AS uid`),请使用 [`QueryBuilder::select`]
504    /// 并自行确保安全。
505    pub fn select_quoted(mut self, columns: Vec<&str>) -> Result<Self, crate::DbError> {
506        let mut quoted = Vec::with_capacity(columns.len());
507        for col in columns {
508            crate::sql_safety::validate_identifier(col, "select column")?;
509            quoted.push(self.dialect.quote(col));
510        }
511        self.select_columns = quoted;
512        Ok(self)
513    }
514
515    /// P0-2:参数化等值条件 `field = ?`(AND 关系)。
516    ///
517    /// 值通过 `?` 占位符绑定,杜绝 SQL 注入。
518    ///
519    /// # 示例
520    ///
521    /// ```ignore
522    /// use sz_orm_core::Value;
523    ///
524    /// builder
525    ///     .where_eq("status", Value::String("active".into()))
526    ///     .where_eq("tenant_id", Value::I64(42));
527    /// ```
528    pub fn where_eq(mut self, field: impl Into<String>, value: Value) -> Self {
529        self.where_conditions
530            .push(WhereCondition::Eq(field.into(), value));
531        self
532    }
533
534    /// P0-2:参数化不等条件 `field != ?`(AND 关系)。
535    pub fn where_ne(mut self, field: impl Into<String>, value: Value) -> Self {
536        self.where_conditions
537            .push(WhereCondition::Ne(field.into(), value));
538        self
539    }
540
541    /// P0-2:参数化大于条件 `field > ?`(AND 关系)。
542    pub fn where_gt(mut self, field: impl Into<String>, value: Value) -> Self {
543        self.where_conditions
544            .push(WhereCondition::Gt(field.into(), value));
545        self
546    }
547
548    /// P0-2:参数化大于等于条件 `field >= ?`(AND 关系)。
549    pub fn where_ge(mut self, field: impl Into<String>, value: Value) -> Self {
550        self.where_conditions
551            .push(WhereCondition::Ge(field.into(), value));
552        self
553    }
554
555    /// P0-2:参数化小于条件 `field < ?`(AND 关系)。
556    pub fn where_lt(mut self, field: impl Into<String>, value: Value) -> Self {
557        self.where_conditions
558            .push(WhereCondition::Lt(field.into(), value));
559        self
560    }
561
562    /// P0-2:参数化小于等于条件 `field <= ?`(AND 关系)。
563    pub fn where_le(mut self, field: impl Into<String>, value: Value) -> Self {
564        self.where_conditions
565            .push(WhereCondition::Le(field.into(), value));
566        self
567    }
568
569    /// P0-2:参数化 LIKE 条件 `field LIKE ?`(AND 关系)。
570    ///
571    /// 调用方负责在 `pattern` 中包含 `%` 通配符。
572    ///
573    /// # 示例
574    ///
575    /// ```ignore
576    /// use sz_orm_core::Value;
577    ///
578    /// builder.where_like("name", Value::String("%alice%".into()));
579    /// ```
580    pub fn where_like(mut self, field: impl Into<String>, pattern: Value) -> Self {
581        self.where_conditions
582            .push(WhereCondition::Like(field.into(), pattern));
583        self
584    }
585
586    /// P0-2:参数化 OR 等值条件 `OR field = ?`。
587    ///
588    /// 值通过 `?` 占位符绑定,杜绝 SQL 注入。OR 条件会与相邻的 OR 条件组合成 `(cond1 OR cond2)` 形式。
589    pub fn or_where_eq(mut self, field: impl Into<String>, value: Value) -> Self {
590        self.where_conditions
591            .push(WhereCondition::OrEq(field.into(), value));
592        self
593    }
594
595    /// P0-2:参数化 OR 不等条件 `OR field != ?`。
596    pub fn or_where_ne(mut self, field: impl Into<String>, value: Value) -> Self {
597        self.where_conditions
598            .push(WhereCondition::OrNe(field.into(), value));
599        self
600    }
601
602    /// P0-2:参数化 OR 大于条件 `OR field > ?`。
603    pub fn or_where_gt(mut self, field: impl Into<String>, value: Value) -> Self {
604        self.where_conditions
605            .push(WhereCondition::OrGt(field.into(), value));
606        self
607    }
608
609    /// P0-2:参数化 OR 大于等于条件 `OR field >= ?`。
610    pub fn or_where_ge(mut self, field: impl Into<String>, value: Value) -> Self {
611        self.where_conditions
612            .push(WhereCondition::OrGe(field.into(), value));
613        self
614    }
615
616    /// P0-2:参数化 OR 小于条件 `OR field < ?`。
617    pub fn or_where_lt(mut self, field: impl Into<String>, value: Value) -> Self {
618        self.where_conditions
619            .push(WhereCondition::OrLt(field.into(), value));
620        self
621    }
622
623    /// P0-2:参数化 OR 小于等于条件 `OR field <= ?`。
624    pub fn or_where_le(mut self, field: impl Into<String>, value: Value) -> Self {
625        self.where_conditions
626            .push(WhereCondition::OrLe(field.into(), value));
627        self
628    }
629
630    /// P0-2:参数化 OR LIKE 条件 `OR field LIKE ?`。
631    pub fn or_where_like(mut self, field: impl Into<String>, pattern: Value) -> Self {
632        self.where_conditions
633            .push(WhereCondition::OrLike(field.into(), pattern));
634        self
635    }
636
637    pub fn where_in(mut self, field: impl Into<String>, values: Vec<Value>) -> Self {
638        self.where_conditions
639            .push(WhereCondition::In(field.into(), values));
640        self
641    }
642
643    pub fn where_not_in(mut self, field: impl Into<String>, values: Vec<Value>) -> Self {
644        self.where_conditions
645            .push(WhereCondition::NotIn(field.into(), values));
646        self
647    }
648
649    pub fn where_between(mut self, field: impl Into<String>, start: Value, end: Value) -> Self {
650        self.where_conditions
651            .push(WhereCondition::Between(field.into(), start, end));
652        self
653    }
654
655    pub fn where_not_between(mut self, field: impl Into<String>, start: Value, end: Value) -> Self {
656        self.where_conditions
657            .push(WhereCondition::NotBetween(field.into(), start, end));
658        self
659    }
660
661    pub fn where_null(mut self, field: impl Into<String>) -> Self {
662        self.where_conditions
663            .push(WhereCondition::Null(field.into()));
664        self
665    }
666
667    pub fn where_not_null(mut self, field: impl Into<String>) -> Self {
668        self.where_conditions
669            .push(WhereCondition::NotNull(field.into()));
670        self
671    }
672
673    pub fn order_by(mut self, field: impl Into<String>) -> Self {
674        self.order_by.push(OrderClause {
675            field: field.into(),
676            direction: OrderDirection::Asc,
677        });
678        self
679    }
680
681    pub fn order_desc(mut self, field: impl Into<String>) -> Self {
682        self.order_by.push(OrderClause {
683            field: field.into(),
684            direction: OrderDirection::Desc,
685        });
686        self
687    }
688
689    pub fn group_by(mut self, field: impl Into<String>) -> Self {
690        self.group_by.push(field.into());
691        self
692    }
693
694    // -----------------------------------------------------------------------
695    // 类型安全列引用(TypedColumn)变体
696    // 以下方法接受实现 TypedColumn 的零大小标记类型,在编译期确保列名
697    // 与表归属正确,避免字符串列名拼写错误逃逸到运行时。
698    // -----------------------------------------------------------------------
699
700    /// 类型安全 `where_eq`:通过 [`TypedColumn`] 标记类型指定列。
701    ///
702    /// # 示例
703    ///
704    /// ```ignore
705    /// use sz_orm_core::typed::TypedColumn;
706    /// // 假设 typed_query! 生成了 users::col_id
707    /// QueryBuilder::<User>::new()
708    ///     .where_eq_typed::<users::col_id>(Value::from(1i64))
709    ///     .build();
710    /// // 生成:WHERE `id` = ?
711    /// ```
712    pub fn where_eq_typed<C: TypedColumn>(mut self, value: Value) -> Self {
713        self.where_conditions
714            .push(WhereCondition::Eq(C::NAME.to_string(), value));
715        self
716    }
717
718    /// 类型安全 `where_ne`。
719    pub fn where_ne_typed<C: TypedColumn>(mut self, value: Value) -> Self {
720        self.where_conditions
721            .push(WhereCondition::Ne(C::NAME.to_string(), value));
722        self
723    }
724
725    /// 类型安全 `where_gt`。
726    pub fn where_gt_typed<C: TypedColumn>(mut self, value: Value) -> Self {
727        self.where_conditions
728            .push(WhereCondition::Gt(C::NAME.to_string(), value));
729        self
730    }
731
732    /// 类型安全 `where_ge`。
733    pub fn where_ge_typed<C: TypedColumn>(mut self, value: Value) -> Self {
734        self.where_conditions
735            .push(WhereCondition::Ge(C::NAME.to_string(), value));
736        self
737    }
738
739    /// 类型安全 `where_lt`。
740    pub fn where_lt_typed<C: TypedColumn>(mut self, value: Value) -> Self {
741        self.where_conditions
742            .push(WhereCondition::Lt(C::NAME.to_string(), value));
743        self
744    }
745
746    /// 类型安全 `where_le`。
747    pub fn where_le_typed<C: TypedColumn>(mut self, value: Value) -> Self {
748        self.where_conditions
749            .push(WhereCondition::Le(C::NAME.to_string(), value));
750        self
751    }
752
753    /// 类型安全 `where_null`。
754    pub fn where_null_typed<C: TypedColumn>(mut self) -> Self {
755        self.where_conditions
756            .push(WhereCondition::Null(C::NAME.to_string()));
757        self
758    }
759
760    /// 类型安全 `where_not_null`。
761    pub fn where_not_null_typed<C: TypedColumn>(mut self) -> Self {
762        self.where_conditions
763            .push(WhereCondition::NotNull(C::NAME.to_string()));
764        self
765    }
766
767    /// 类型安全 `order_by`(升序)。
768    pub fn order_by_typed<C: TypedColumn>(mut self) -> Self {
769        self.order_by.push(OrderClause {
770            field: C::NAME.to_string(),
771            direction: OrderDirection::Asc,
772        });
773        self
774    }
775
776    /// 类型安全 `order_desc`。
777    pub fn order_desc_typed<C: TypedColumn>(mut self) -> Self {
778        self.order_by.push(OrderClause {
779            field: C::NAME.to_string(),
780            direction: OrderDirection::Desc,
781        });
782        self
783    }
784
785    /// 类型安全 `group_by`。
786    pub fn group_by_typed<C: TypedColumn>(mut self) -> Self {
787        self.group_by.push(C::NAME.to_string());
788        self
789    }
790
791    /// 类型安全 `select`:添加单个列到 SELECT 列表。
792    ///
793    /// 多次调用追加列;与 `select`/`select_quoted` 混用时注意顺序。
794    pub fn select_typed<C: TypedColumn>(mut self) -> Self {
795        self.select_columns.push(C::NAME.to_string());
796        self
797    }
798
799    /// 类型安全 `select`:一次性添加多个列。
800    pub fn select_typed_cols<C: TypedColumn, const N: usize>(mut self) -> Self {
801        // 此方法用于固定数量列的场景;泛型 C 作为占位,实际列名由宏展开填充
802        // 对于多列场景,推荐链式调用 select_typed::<Col1>().select_typed::<Col2>()
803        self.select_columns.push(C::NAME.to_string());
804        self
805    }
806
807    pub fn having(mut self, condition: impl Into<String>) -> Self {
808        self.having_conditions
809            .push(WhereCondition::And(condition.into()));
810        self
811    }
812
813    pub fn limit(mut self, limit: usize) -> Self {
814        self.limit_value = Some(limit);
815        self
816    }
817
818    pub fn offset(mut self, offset: usize) -> Self {
819        self.offset_value = Some(offset);
820        self
821    }
822
823    pub fn page(mut self, page: usize, page_size: usize) -> Self {
824        self.limit_value = Some(page_size);
825        self.offset_value = Some((page.saturating_sub(1)) * page_size);
826        self
827    }
828
829    /// P2-5:基于游标的 Keyset 分页 — 查询指定字段值之后的记录(下一页)
830    ///
831    /// 生成 `WHERE {field} > ? ORDER BY {field} ASC LIMIT {page_size}`。
832    /// 适用于按主键或时间戳递增遍历大表的场景,性能不受数据插入/删除影响。
833    ///
834    /// # 参数
835    ///
836    /// - `field`:排序字段(通常是主键或索引列,如 `id`、`created_at`)
837    /// - `cursor_value`:当前页最后一条记录的该字段值
838    /// - `page_size`:每页大小
839    ///
840    /// # 示例
841    ///
842    /// ```
843    /// use sz_orm_core::{QueryBuilder, DbType, dialect::get_dialect, Value};
844    /// # use sz_orm_core::{Model, ModelExt};
845    /// # #[derive(Clone, Debug)]
846    /// # struct User { id: i64 }
847    /// # impl Model for User {
848    /// #     type PrimaryKey = i64;
849    /// #     fn table_name() -> &'static str { "users" }
850    /// #     fn pk(&self) -> i64 { self.id }
851    /// #     fn set_pk(&mut self, pk: i64) { self.id = pk; }
852    /// # }
853    /// # impl ModelExt for User {
854    /// #     fn columns() -> Vec<&'static str> { vec!["id"] }
855    /// #     fn fillable() -> Vec<&'static str> { vec![] }
856    /// #     fn guarded() -> Vec<&'static str> { vec!["id"] }
857    /// #     fn hidden() -> Vec<&'static str> { vec![] }
858    /// #     fn relations() -> std::collections::HashMap<&'static str, sz_orm_core::Relation> { Default::default() }
859    /// #     fn fill(&mut self, _: std::collections::HashMap<String, Value>) {}
860    /// #     fn to_json(&self) -> serde_json::Value { serde_json::json!({}) }
861    /// # }
862    /// let dialect = get_dialect(DbType::MySQL).unwrap();
863    /// let builder = QueryBuilder::<User>::new(dialect)
864    ///     .keyset_after("id", Value::I64(100), 20);
865    /// let (sql, params) = builder.build_select_with_params();
866    /// // 方言会引用字段名(如 MySQL 的 `id`),去除引号后检查
867    /// let sql_clean = sql.replace('`', "").replace('"', "");
868    /// assert!(sql_clean.contains("id > ?"));
869    /// assert!(sql.to_uppercase().contains("ORDER BY"));
870    /// assert!(sql.to_uppercase().contains("ASC"));
871    /// assert!(sql.contains("LIMIT 20"));
872    /// assert_eq!(params, vec![Value::I64(100)]);
873    /// ```
874    pub fn keyset_after(
875        mut self,
876        field: impl Into<String>,
877        cursor_value: Value,
878        page_size: usize,
879    ) -> Self {
880        let field_str = field.into();
881        // 自动设置 ORDER BY ASC(若字段已存在则更新方向为 ASC,保证 keyset 语义一致)
882        if let Some(existing) = self.order_by.iter_mut().find(|o| o.field == field_str) {
883            existing.direction = OrderDirection::Asc;
884        } else {
885            self.order_by.push(OrderClause {
886                field: field_str.clone(),
887                direction: OrderDirection::Asc,
888            });
889        }
890        self.limit_value = Some(page_size);
891        // 清除 offset(keyset 与 offset 互斥)
892        self.offset_value = None;
893        self.keyset_cursor = Some(KeysetCursor {
894            field: field_str,
895            value: cursor_value,
896            direction: KeysetDirection::After,
897        });
898        self
899    }
900
901    /// P2-5:基于游标的 Keyset 分页 — 查询指定字段值之前的记录(上一页)
902    ///
903    /// 生成 `WHERE {field} < ? ORDER BY {field} DESC LIMIT {page_size}`。
904    /// 适用于反向遍历场景。
905    ///
906    /// # 参数
907    ///
908    /// - `field`:排序字段
909    /// - `cursor_value`:当前页第一条记录的该字段值
910    /// - `page_size`:每页大小
911    ///
912    /// # 示例
913    ///
914    /// ```
915    /// use sz_orm_core::{QueryBuilder, DbType, dialect::get_dialect, Value};
916    /// # use sz_orm_core::{Model, ModelExt};
917    /// # #[derive(Clone, Debug)]
918    /// # struct User { id: i64 }
919    /// # impl Model for User {
920    /// #     type PrimaryKey = i64;
921    /// #     fn table_name() -> &'static str { "users" }
922    /// #     fn pk(&self) -> i64 { self.id }
923    /// #     fn set_pk(&mut self, pk: i64) { self.id = pk; }
924    /// # }
925    /// # impl ModelExt for User {
926    /// #     fn columns() -> Vec<&'static str> { vec!["id"] }
927    /// #     fn fillable() -> Vec<&'static str> { vec![] }
928    /// #     fn guarded() -> Vec<&'static str> { vec!["id"] }
929    /// #     fn hidden() -> Vec<&'static str> { vec![] }
930    /// #     fn relations() -> std::collections::HashMap<&'static str, sz_orm_core::Relation> { Default::default() }
931    /// #     fn fill(&mut self, _: std::collections::HashMap<String, Value>) {}
932    /// #     fn to_json(&self) -> serde_json::Value { serde_json::json!({}) }
933    /// # }
934    /// let dialect = get_dialect(DbType::MySQL).unwrap();
935    /// let builder = QueryBuilder::<User>::new(dialect)
936    ///     .keyset_before("id", Value::I64(100), 20);
937    /// let (sql, params) = builder.build_select_with_params();
938    /// // 方言会引用字段名(如 MySQL 的 `id`),去除引号后检查
939    /// let sql_clean = sql.replace('`', "").replace('"', "");
940    /// assert!(sql_clean.contains("id < ?"));
941    /// assert!(sql.to_uppercase().contains("ORDER BY"));
942    /// assert!(sql.to_uppercase().contains("DESC"));
943    /// assert!(sql.contains("LIMIT 20"));
944    /// assert_eq!(params, vec![Value::I64(100)]);
945    /// ```
946    pub fn keyset_before(
947        mut self,
948        field: impl Into<String>,
949        cursor_value: Value,
950        page_size: usize,
951    ) -> Self {
952        let field_str = field.into();
953        // 自动设置 ORDER BY DESC(若字段已存在则更新方向为 DESC,保证 keyset 语义一致)
954        if let Some(existing) = self.order_by.iter_mut().find(|o| o.field == field_str) {
955            existing.direction = OrderDirection::Desc;
956        } else {
957            self.order_by.push(OrderClause {
958                field: field_str.clone(),
959                direction: OrderDirection::Desc,
960            });
961        }
962        self.limit_value = Some(page_size);
963        self.offset_value = None;
964        self.keyset_cursor = Some(KeysetCursor {
965            field: field_str,
966            value: cursor_value,
967            direction: KeysetDirection::Before,
968        });
969        self
970    }
971
972    pub fn join_inner(
973        mut self,
974        table: impl Into<String>,
975        on_left: impl Into<String>,
976        on_right: impl Into<String>,
977    ) -> Self {
978        self.joins.push(JoinClause::Inner(
979            table.into(),
980            on_left.into(),
981            on_right.into(),
982        ));
983        self
984    }
985
986    pub fn join_left(
987        mut self,
988        table: impl Into<String>,
989        on_left: impl Into<String>,
990        on_right: impl Into<String>,
991    ) -> Self {
992        self.joins.push(JoinClause::Left(
993            table.into(),
994            on_left.into(),
995            on_right.into(),
996        ));
997        self
998    }
999
1000    pub fn join_right(
1001        mut self,
1002        table: impl Into<String>,
1003        on_left: impl Into<String>,
1004        on_right: impl Into<String>,
1005    ) -> Self {
1006        self.joins.push(JoinClause::Right(
1007            table.into(),
1008            on_left.into(),
1009            on_right.into(),
1010        ));
1011        self
1012    }
1013
1014    /// 构建 SELECT SQL 语句
1015    ///
1016    /// L-5 修复:补充示例文档
1017    ///
1018    /// 根据 `table`、`select_columns`、`where_conditions`、`joins`、`order_by`、
1019    /// `group_by`、`having`、`limit`、`offset` 等条件拼装最终 SQL。
1020    /// 若未通过 `table()` 指定表名,则使用 `M::table_name()`。
1021    ///
1022    /// # 示例
1023    ///
1024    /// ```ignore
1025    /// use sz_orm_core::query::QueryBuilder;
1026    /// use sz_orm_core::dialect::MySqlDialect;
1027    /// use sz_orm_core::model::Model;
1028    ///
1029    /// #[derive(Default)]
1030    /// struct User;
1031    /// impl Model for User {
1032    ///     type PrimaryKey = i64;
1033    ///     fn table_name() -> &'static str { "users" }
1034    ///     fn pk(&self) -> Self::PrimaryKey { 0 }
1035    ///     fn set_pk(&mut self, _: Self::PrimaryKey) {}
1036    /// }
1037    ///
1038    /// let sql = QueryBuilder::<User>::new(Box::new(MySqlDialect))
1039    ///     .select(vec!["id", "name"])
1040    ///     .where_cond("age > 18")
1041    ///     .order_by("id DESC")
1042    ///     .limit(10)
1043    ///     .build_select();
1044    /// // sql => "SELECT id, name FROM `users` WHERE age > 18 ORDER BY id DESC LIMIT 10"
1045    /// ```
1046    #[tracing::instrument(skip(self), fields(op = "select"))]
1047    pub fn build_select(&self) -> String {
1048        let table = self
1049            .table
1050            .clone()
1051            .unwrap_or_else(|| M::table_name().to_string());
1052
1053        let columns = if self.select_columns.is_empty() {
1054            "*".to_string()
1055        } else {
1056            self.select_columns.join(", ")
1057        };
1058
1059        let mut sql = format!("SELECT {} FROM {}", columns, self.dialect.quote(&table));
1060
1061        for join in &self.joins {
1062            match join {
1063                JoinClause::Inner(t, l, r) => {
1064                    sql.push_str(&format!(
1065                        " INNER JOIN {} ON {} = {}",
1066                        self.dialect.quote(t),
1067                        self.dialect.quote(l),
1068                        self.dialect.quote(r)
1069                    ));
1070                }
1071                JoinClause::Left(t, l, r) => {
1072                    sql.push_str(&format!(
1073                        " LEFT JOIN {} ON {} = {}",
1074                        self.dialect.quote(t),
1075                        self.dialect.quote(l),
1076                        self.dialect.quote(r)
1077                    ));
1078                }
1079                JoinClause::Right(t, l, r) => {
1080                    sql.push_str(&format!(
1081                        " RIGHT JOIN {} ON {} = {}",
1082                        self.dialect.quote(t),
1083                        self.dialect.quote(l),
1084                        self.dialect.quote(r)
1085                    ));
1086                }
1087                JoinClause::Cross(t, on) => {
1088                    sql.push_str(&format!(
1089                        " CROSS JOIN {} ON {}",
1090                        self.dialect.quote(t),
1091                        self.dialect.quote(on)
1092                    ));
1093                }
1094            }
1095        }
1096
1097        // P0-1:build_where_clause 内部已处理软删除条件,即使 where_conditions 为空也可能返回非空
1098        let where_clause = self.build_where_clause();
1099        if !where_clause.is_empty() {
1100            sql.push_str(&where_clause);
1101        }
1102
1103        if !self.group_by.is_empty() {
1104            let cols: Vec<String> = self
1105                .group_by
1106                .iter()
1107                .map(|c| self.dialect.quote(c))
1108                .collect();
1109            sql.push_str(" GROUP BY ");
1110            sql.push_str(&cols.join(", "));
1111        }
1112
1113        if !self.having_conditions.is_empty() {
1114            sql.push_str(" HAVING ");
1115            for (i, cond) in self.having_conditions.iter().enumerate() {
1116                if i > 0 {
1117                    sql.push_str(" AND ");
1118                }
1119                if let WhereCondition::And(c) = cond {
1120                    sql.push_str(c);
1121                }
1122            }
1123        }
1124
1125        if !self.order_by.is_empty() {
1126            let order_cols: Vec<String> = self
1127                .order_by
1128                .iter()
1129                .map(|o| {
1130                    let dir = match o.direction {
1131                        OrderDirection::Asc => " ASC",
1132                        OrderDirection::Desc => " DESC",
1133                    };
1134                    format!("{}{}", self.dialect.quote(&o.field), dir)
1135                })
1136                .collect();
1137            sql.push_str(" ORDER BY ");
1138            sql.push_str(&order_cols.join(", "));
1139        }
1140
1141        if let Some(limit) = self.limit_value {
1142            sql.push_str(&format!(" LIMIT {}", limit));
1143        }
1144
1145        if let Some(offset) = self.offset_value {
1146            sql.push_str(&format!(" OFFSET {}", offset));
1147        }
1148
1149        sql
1150    }
1151
1152    /// 构建 WHERE 子句(处理所有条件类型:And/Or/In/NotIn/Between/Null/Eq/Ne/Gt/Lt/Like 等)
1153    ///
1154    /// P0-1:自动追加软删除过滤条件(`AND {soft_delete_field} IS NULL`)
1155    ///
1156    /// 返回空字符串表示无 WHERE 子句
1157    fn build_where_clause(&self) -> String {
1158        self.build_where_clause_with_options(true)
1159    }
1160
1161    /// 构建 WHERE 子句(可控制是否追加软删除过滤)。
1162    ///
1163    /// `include_soft_delete = true`:追加 `AND {soft_delete_field} IS NULL`(默认行为)
1164    /// `include_soft_delete = false`:不追加软删除过滤(用于 `build_force_delete`)
1165    ///
1166    /// P0-3:租户条件总是追加(若启用),不受 `include_soft_delete` 控制。
1167    /// 物理删除也应受租户隔离约束,跨租户操作需显式 `without_tenant()`。
1168    fn build_where_clause_with_options(&self, include_soft_delete: bool) -> String {
1169        // P0-1:构造软删除条件(若有且启用)
1170        let soft_delete_cond = if include_soft_delete {
1171            self.build_soft_delete_condition()
1172        } else {
1173            None
1174        };
1175
1176        // P0-3:构造租户条件(若有且启用)— 无参数版本内嵌转义值
1177        let tenant_cond = self.build_tenant_condition().map(|(sql, value)| {
1178            // sql 形如 "`tenant_id` = ?",将 ? 替换为内嵌值
1179            sql.replacen('?', &value.to_param_with_dialect(&*self.dialect), 1)
1180        });
1181
1182        // 无用户条件且无软删除条件且无租户条件且无 keyset 游标 → 空 WHERE
1183        if self.where_conditions.is_empty()
1184            && soft_delete_cond.is_none()
1185            && tenant_cond.is_none()
1186            && self.keyset_cursor.is_none()
1187        {
1188            return String::new();
1189        }
1190
1191        // 将每个条件转换为字符串,OR 条件标记前缀
1192        let mut conditions: Vec<String> = self
1193            .where_conditions
1194            .iter()
1195            .map(|cond| match cond {
1196                WhereCondition::And(c) => c.clone(),
1197                WhereCondition::Or(c) => format!("OR {}", c),
1198                // P0-2:参数化条件在无参数版本中直接 inline 值(用于 build_select 等无参数绑定场景)
1199                WhereCondition::Eq(f, v) => format!(
1200                    "{} = {}",
1201                    self.dialect.quote(f),
1202                    v.to_param_with_dialect(&*self.dialect)
1203                ),
1204                WhereCondition::Ne(f, v) => format!(
1205                    "{} != {}",
1206                    self.dialect.quote(f),
1207                    v.to_param_with_dialect(&*self.dialect)
1208                ),
1209                WhereCondition::Gt(f, v) => format!(
1210                    "{} > {}",
1211                    self.dialect.quote(f),
1212                    v.to_param_with_dialect(&*self.dialect)
1213                ),
1214                WhereCondition::Ge(f, v) => format!(
1215                    "{} >= {}",
1216                    self.dialect.quote(f),
1217                    v.to_param_with_dialect(&*self.dialect)
1218                ),
1219                WhereCondition::Lt(f, v) => format!(
1220                    "{} < {}",
1221                    self.dialect.quote(f),
1222                    v.to_param_with_dialect(&*self.dialect)
1223                ),
1224                WhereCondition::Le(f, v) => format!(
1225                    "{} <= {}",
1226                    self.dialect.quote(f),
1227                    v.to_param_with_dialect(&*self.dialect)
1228                ),
1229                WhereCondition::Like(f, v) => format!(
1230                    "{} LIKE {}",
1231                    self.dialect.quote(f),
1232                    v.to_param_with_dialect(&*self.dialect)
1233                ),
1234                WhereCondition::OrEq(f, v) => format!(
1235                    "OR {} = {}",
1236                    self.dialect.quote(f),
1237                    v.to_param_with_dialect(&*self.dialect)
1238                ),
1239                WhereCondition::OrNe(f, v) => format!(
1240                    "OR {} != {}",
1241                    self.dialect.quote(f),
1242                    v.to_param_with_dialect(&*self.dialect)
1243                ),
1244                WhereCondition::OrGt(f, v) => format!(
1245                    "OR {} > {}",
1246                    self.dialect.quote(f),
1247                    v.to_param_with_dialect(&*self.dialect)
1248                ),
1249                WhereCondition::OrGe(f, v) => format!(
1250                    "OR {} >= {}",
1251                    self.dialect.quote(f),
1252                    v.to_param_with_dialect(&*self.dialect)
1253                ),
1254                WhereCondition::OrLt(f, v) => format!(
1255                    "OR {} < {}",
1256                    self.dialect.quote(f),
1257                    v.to_param_with_dialect(&*self.dialect)
1258                ),
1259                WhereCondition::OrLe(f, v) => format!(
1260                    "OR {} <= {}",
1261                    self.dialect.quote(f),
1262                    v.to_param_with_dialect(&*self.dialect)
1263                ),
1264                WhereCondition::OrLike(f, v) => format!(
1265                    "OR {} LIKE {}",
1266                    self.dialect.quote(f),
1267                    v.to_param_with_dialect(&*self.dialect)
1268                ),
1269                WhereCondition::In(f, vals) => {
1270                    // v0.2.2 修复 H-1:使用方言感知的转义
1271                    let vals_str: Vec<String> = vals
1272                        .iter()
1273                        .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1274                        .collect();
1275                    format!("{} IN ({})", self.dialect.quote(f), vals_str.join(", "))
1276                }
1277                WhereCondition::NotIn(f, vals) => {
1278                    let vals_str: Vec<String> = vals
1279                        .iter()
1280                        .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1281                        .collect();
1282                    format!("{} NOT IN ({})", self.dialect.quote(f), vals_str.join(", "))
1283                }
1284                WhereCondition::Between(f, start, end) => {
1285                    format!(
1286                        "{} BETWEEN {} AND {}",
1287                        self.dialect.quote(f),
1288                        start.to_param_with_dialect(&*self.dialect),
1289                        end.to_param_with_dialect(&*self.dialect)
1290                    )
1291                }
1292                WhereCondition::NotBetween(f, start, end) => {
1293                    format!(
1294                        "{} NOT BETWEEN {} AND {}",
1295                        self.dialect.quote(f),
1296                        start.to_param_with_dialect(&*self.dialect),
1297                        end.to_param_with_dialect(&*self.dialect)
1298                    )
1299                }
1300                WhereCondition::Null(f) => format!("{} IS NULL", self.dialect.quote(f)),
1301                WhereCondition::NotNull(f) => format!("{} IS NOT NULL", self.dialect.quote(f)),
1302                WhereCondition::Exists(s) => format!("EXISTS ({})", s),
1303                WhereCondition::NotExists(s) => format!("NOT EXISTS ({})", s),
1304            })
1305            .collect();
1306
1307        // P0-1:追加软删除条件(作为最后一个 AND 条件)
1308        if let Some(sd_cond) = soft_delete_cond {
1309            conditions.push(sd_cond);
1310        }
1311
1312        // P0-3:追加租户条件(在软删除之后,作为 AND 条件)
1313        if let Some(t_cond) = tenant_cond {
1314            conditions.push(t_cond);
1315        }
1316
1317        // P2-5:追加 keyset 游标条件(在租户条件之后,内嵌转义值)
1318        if let Some(ref cursor) = self.keyset_cursor {
1319            let op = match cursor.direction {
1320                KeysetDirection::After => ">",
1321                KeysetDirection::Before => "<",
1322            };
1323            conditions.push(format!(
1324                "{} {} {}",
1325                self.dialect.quote(&cursor.field),
1326                op,
1327                cursor.value.to_param_with_dialect(&*self.dialect)
1328            ));
1329        }
1330
1331        if conditions.is_empty() {
1332            return String::new();
1333        }
1334
1335        // OR 分组逻辑:将相邻的 OR 条件组合成 (cond1 OR cond2) 形式
1336        // 边界处理:如果第一个条件就是 OR(不合理但需防御),当作 AND 处理
1337        let mut groups: Vec<Vec<String>> = Vec::new();
1338        let mut current_group: Vec<String> = Vec::new();
1339        for cond in conditions.iter() {
1340            if let Some(stripped) = cond.strip_prefix("OR ") {
1341                // OR 条件:无论是否首个,都把 OR 前缀去掉当作普通条件加入当前组
1342                current_group.push(stripped.to_string());
1343            } else {
1344                // AND 条件:如果当前组非空,先保存
1345                if !current_group.is_empty() {
1346                    groups.push(std::mem::take(&mut current_group));
1347                }
1348                current_group.push(cond.clone());
1349            }
1350        }
1351        if !current_group.is_empty() {
1352            groups.push(current_group);
1353        }
1354
1355        let group_strs: Vec<String> = groups
1356            .iter()
1357            .map(|g| {
1358                if g.len() == 1 {
1359                    g[0].clone()
1360                } else {
1361                    format!("({})", g.join(" OR "))
1362                }
1363            })
1364            .collect();
1365
1366        // SAFETY: group_strs 来自 WhereCondition::Eq/Gt 等参数化变体渲染,值已绑定为 ? 占位符或内联常量
1367        format!(" WHERE {}", group_strs.join(" AND "))
1368    }
1369
1370    #[tracing::instrument(skip(self, data), fields(op = "insert"))]
1371    pub fn build_insert(&self, data: &std::collections::HashMap<String, Value>) -> String {
1372        let table = self
1373            .table
1374            .clone()
1375            .unwrap_or_else(|| M::table_name().to_string());
1376
1377        if data.is_empty() {
1378            return String::new();
1379        }
1380
1381        let columns: Vec<String> = data.keys().map(|k| self.dialect.quote(k)).collect();
1382        // v0.2.2 修复 H-1:使用方言感知的转义
1383        let values: Vec<String> = data
1384            .values()
1385            .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1386            .collect();
1387
1388        format!(
1389            "INSERT INTO {} ({}) VALUES ({})",
1390            self.dialect.quote(&table),
1391            columns.join(", "),
1392            values.join(", ")
1393        )
1394    }
1395
1396    #[tracing::instrument(skip(self, data), fields(op = "update"))]
1397    pub fn build_update(&self, data: &std::collections::HashMap<String, Value>) -> String {
1398        let table = self
1399            .table
1400            .clone()
1401            .unwrap_or_else(|| M::table_name().to_string());
1402
1403        if data.is_empty() {
1404            return String::new();
1405        }
1406
1407        let set_clauses: Vec<String> = data
1408            .iter()
1409            .map(|(k, v)| {
1410                format!(
1411                    "{} = {}",
1412                    self.dialect.quote(k),
1413                    v.to_param_with_dialect(&*self.dialect)
1414                )
1415            })
1416            .collect();
1417
1418        let mut sql = format!(
1419            "UPDATE {} SET {}",
1420            self.dialect.quote(&table),
1421            set_clauses.join(", ")
1422        );
1423
1424        sql.push_str(&self.build_where_clause());
1425        sql
1426    }
1427
1428    /// 构建 DELETE SQL 语句。
1429    ///
1430    /// **P0-1 软删除集成(v1.3.0+)**:当 `M: Model` 实现了 `soft_delete_field()`
1431    /// 返回 `Some(field)` 且未调用 `without_soft_delete()` 时,本方法自动生成
1432    /// `UPDATE {table} SET {field} = NOW() WHERE ...` 而非 `DELETE FROM ...`。
1433    ///
1434    /// 这与 SeaORM 的 `ActiveModelBehavior::after_delete` + `ActiveValue::Set`
1435    /// 行为对齐:删除操作实际是软删除 UPDATE。
1436    ///
1437    /// 若需物理删除,请使用 [`build_force_delete`](Self::build_force_delete)。
1438    #[tracing::instrument(skip(self), fields(op = "delete"))]
1439    pub fn build_delete(&self) -> String {
1440        let table = self
1441            .table
1442            .clone()
1443            .unwrap_or_else(|| M::table_name().to_string());
1444
1445        // P0-1:软删除启用时转为 UPDATE SET {field} = NOW()
1446        if let Some(field) = self.soft_delete_field() {
1447            let where_clause = self.build_where_clause();
1448            return format!(
1449                "UPDATE {} SET {} = NOW(){}",
1450                self.dialect.quote(&table),
1451                self.dialect.quote(field),
1452                where_clause
1453            );
1454        }
1455
1456        let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1457        sql.push_str(&self.build_where_clause());
1458        sql
1459    }
1460
1461    /// 构建物理 DELETE SQL 语句(绕过软删除)。
1462    ///
1463    /// 即使 Model 实现了 `soft_delete_field()`,也生成 `DELETE FROM ...`,
1464    /// 且**不追加** `WHERE deleted_at IS NULL` 过滤(保留用户指定的 WHERE 条件)。
1465    /// 用于管理员强制清除场景。
1466    ///
1467    /// # 安全警告
1468    ///
1469    /// 物理删除不可恢复,请谨慎使用。
1470    pub fn build_force_delete(&self) -> String {
1471        let table = self
1472            .table
1473            .clone()
1474            .unwrap_or_else(|| M::table_name().to_string());
1475
1476        let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1477        // P0-1:物理删除不追加软删除过滤(include_soft_delete = false)
1478        sql.push_str(&self.build_where_clause_with_options(false));
1479        sql
1480    }
1481
1482    // ===================== 参数绑定版本(v1.1.0 新增) =====================
1483
1484    /// 构建 WHERE 子句(参数绑定版本)。
1485    ///
1486    /// P0-1:自动追加软删除过滤条件(`AND {soft_delete_field} IS NULL`,无参数)
1487    ///
1488    /// 将 `In`/`NotIn`/`Between`/`NotBetween`/`Eq`/`Ne`/`Gt`/`Ge`/`Lt`/`Le`/`Like`
1489    /// 条件中的值替换为 `?` 占位符,值收集到 `params` 向量中。
1490    /// `And`/`Or`/`Exists` 等原始字符串条件不提取参数(调用方负责安全)。
1491    fn build_where_clause_with_params(&self) -> (String, Vec<Value>) {
1492        // 默认包含软删除条件
1493        self.build_where_clause_with_params_options(true)
1494    }
1495
1496    /// 构建参数化 WHERE 子句(可控是否包含软删除条件)
1497    ///
1498    /// # 参数
1499    ///
1500    /// - `include_soft_delete`:true 时追加软删除条件;false 时跳过(用于物理删除等场景)
1501    ///
1502    /// P0-3:租户条件总是追加(若启用),不受 `include_soft_delete` 控制。
1503    /// 租户值通过 `?` 占位符绑定,加入 `params` 列表末尾。
1504    fn build_where_clause_with_params_options(
1505        &self,
1506        include_soft_delete: bool,
1507    ) -> (String, Vec<Value>) {
1508        // P0-1:构造软删除条件(若有且启用,无参数)
1509        let soft_delete_cond = if include_soft_delete {
1510            self.build_soft_delete_condition()
1511        } else {
1512            None
1513        };
1514
1515        // P0-3:构造租户条件(若有且启用)— 参数化版本保留 (sql, value)
1516        let tenant_cond = self.build_tenant_condition();
1517
1518        // 无用户条件且无软删除条件且无租户条件且无 keyset 游标 → 空 WHERE
1519        if self.where_conditions.is_empty()
1520            && soft_delete_cond.is_none()
1521            && tenant_cond.is_none()
1522            && self.keyset_cursor.is_none()
1523        {
1524            return (String::new(), Vec::new());
1525        }
1526
1527        let mut params = Vec::new();
1528
1529        let mut conditions: Vec<String> = self
1530            .where_conditions
1531            .iter()
1532            .map(|cond| match cond {
1533                WhereCondition::And(c) => c.clone(),
1534                WhereCondition::Or(c) => format!("OR {}", c),
1535                // P0-2:参数化条件使用 `?` 占位符
1536                WhereCondition::Eq(f, v) => {
1537                    params.push(v.clone());
1538                    format!("{} = ?", self.dialect.quote(f))
1539                }
1540                WhereCondition::Ne(f, v) => {
1541                    params.push(v.clone());
1542                    format!("{} != ?", self.dialect.quote(f))
1543                }
1544                WhereCondition::Gt(f, v) => {
1545                    params.push(v.clone());
1546                    format!("{} > ?", self.dialect.quote(f))
1547                }
1548                WhereCondition::Ge(f, v) => {
1549                    params.push(v.clone());
1550                    format!("{} >= ?", self.dialect.quote(f))
1551                }
1552                WhereCondition::Lt(f, v) => {
1553                    params.push(v.clone());
1554                    format!("{} < ?", self.dialect.quote(f))
1555                }
1556                WhereCondition::Le(f, v) => {
1557                    params.push(v.clone());
1558                    format!("{} <= ?", self.dialect.quote(f))
1559                }
1560                WhereCondition::Like(f, v) => {
1561                    params.push(v.clone());
1562                    format!("{} LIKE ?", self.dialect.quote(f))
1563                }
1564                WhereCondition::OrEq(f, v) => {
1565                    params.push(v.clone());
1566                    format!("OR {} = ?", self.dialect.quote(f))
1567                }
1568                WhereCondition::OrNe(f, v) => {
1569                    params.push(v.clone());
1570                    format!("OR {} != ?", self.dialect.quote(f))
1571                }
1572                WhereCondition::OrGt(f, v) => {
1573                    params.push(v.clone());
1574                    format!("OR {} > ?", self.dialect.quote(f))
1575                }
1576                WhereCondition::OrGe(f, v) => {
1577                    params.push(v.clone());
1578                    format!("OR {} >= ?", self.dialect.quote(f))
1579                }
1580                WhereCondition::OrLt(f, v) => {
1581                    params.push(v.clone());
1582                    format!("OR {} < ?", self.dialect.quote(f))
1583                }
1584                WhereCondition::OrLe(f, v) => {
1585                    params.push(v.clone());
1586                    format!("OR {} <= ?", self.dialect.quote(f))
1587                }
1588                WhereCondition::OrLike(f, v) => {
1589                    params.push(v.clone());
1590                    format!("OR {} LIKE ?", self.dialect.quote(f))
1591                }
1592                WhereCondition::In(f, vals) => {
1593                    let placeholders: Vec<&str> = vals.iter().map(|_| "?").collect();
1594                    params.extend(vals.iter().cloned());
1595                    format!("{} IN ({})", self.dialect.quote(f), placeholders.join(", "))
1596                }
1597                WhereCondition::NotIn(f, vals) => {
1598                    let placeholders: Vec<&str> = vals.iter().map(|_| "?").collect();
1599                    params.extend(vals.iter().cloned());
1600                    format!(
1601                        "{} NOT IN ({})",
1602                        self.dialect.quote(f),
1603                        placeholders.join(", ")
1604                    )
1605                }
1606                WhereCondition::Between(f, start, end) => {
1607                    params.push(start.clone());
1608                    params.push(end.clone());
1609                    format!("{} BETWEEN ? AND ?", self.dialect.quote(f))
1610                }
1611                WhereCondition::NotBetween(f, start, end) => {
1612                    params.push(start.clone());
1613                    params.push(end.clone());
1614                    format!("{} NOT BETWEEN ? AND ?", self.dialect.quote(f))
1615                }
1616                WhereCondition::Null(f) => format!("{} IS NULL", self.dialect.quote(f)),
1617                WhereCondition::NotNull(f) => format!("{} IS NOT NULL", self.dialect.quote(f)),
1618                WhereCondition::Exists(s) => format!("EXISTS ({})", s),
1619                WhereCondition::NotExists(s) => format!("NOT EXISTS ({})", s),
1620            })
1621            .collect();
1622
1623        // P0-1:追加软删除条件(作为最后一个 AND 条件,无参数)
1624        if let Some(sd_cond) = soft_delete_cond {
1625            conditions.push(sd_cond);
1626        }
1627
1628        // P0-3:追加租户条件(在软删除之后,参数化绑定)
1629        if let Some((t_sql, t_value)) = tenant_cond {
1630            conditions.push(t_sql);
1631            params.push(t_value);
1632        }
1633
1634        // P2-5:追加 keyset 游标条件(在租户条件之后,参数化绑定)
1635        if let Some(ref cursor) = self.keyset_cursor {
1636            let op = match cursor.direction {
1637                KeysetDirection::After => ">",
1638                KeysetDirection::Before => "<",
1639            };
1640            conditions.push(format!("{} {} ?", self.dialect.quote(&cursor.field), op));
1641            params.push(cursor.value.clone());
1642        }
1643
1644        if conditions.is_empty() {
1645            return (String::new(), params);
1646        }
1647
1648        // OR 分组逻辑:与 build_where_clause 相同
1649        let mut groups: Vec<Vec<String>> = Vec::new();
1650        let mut current_group: Vec<String> = Vec::new();
1651        for cond in conditions.iter() {
1652            if let Some(stripped) = cond.strip_prefix("OR ") {
1653                current_group.push(stripped.to_string());
1654            } else {
1655                if !current_group.is_empty() {
1656                    groups.push(std::mem::take(&mut current_group));
1657                }
1658                current_group.push(cond.clone());
1659            }
1660        }
1661        if !current_group.is_empty() {
1662            groups.push(current_group);
1663        }
1664
1665        let group_strs: Vec<String> = groups
1666            .iter()
1667            .map(|g| {
1668                if g.len() == 1 {
1669                    g[0].clone()
1670                } else {
1671                    format!("({})", g.join(" OR "))
1672                }
1673            })
1674            .collect();
1675
1676        // SAFETY: group_strs 来自 WhereCondition::Eq/Gt 等参数化变体渲染,值已绑定为 ? 占位符
1677        (format!(" WHERE {}", group_strs.join(" AND ")), params)
1678    }
1679
1680    /// 构建 SELECT SQL(参数绑定版本)。
1681    ///
1682    /// WHERE 子句中的值使用 `?` 占位符,值通过 `params` 返回。
1683    /// 适用于 `Connection::query_with_params()`。
1684    pub fn build_select_with_params(&self) -> (String, Vec<Value>) {
1685        let table = self
1686            .table
1687            .clone()
1688            .unwrap_or_else(|| M::table_name().to_string());
1689        let columns = if self.select_columns.is_empty() {
1690            "*".to_string()
1691        } else {
1692            self.select_columns.join(", ")
1693        };
1694
1695        let mut sql = format!("SELECT {} FROM {}", columns, self.dialect.quote(&table));
1696
1697        for join in &self.joins {
1698            match join {
1699                JoinClause::Inner(t, l, r) => {
1700                    sql.push_str(&format!(
1701                        " INNER JOIN {} ON {} = {}",
1702                        self.dialect.quote(t),
1703                        self.dialect.quote(l),
1704                        self.dialect.quote(r)
1705                    ));
1706                }
1707                JoinClause::Left(t, l, r) => {
1708                    sql.push_str(&format!(
1709                        " LEFT JOIN {} ON {} = {}",
1710                        self.dialect.quote(t),
1711                        self.dialect.quote(l),
1712                        self.dialect.quote(r)
1713                    ));
1714                }
1715                JoinClause::Right(t, l, r) => {
1716                    sql.push_str(&format!(
1717                        " RIGHT JOIN {} ON {} = {}",
1718                        self.dialect.quote(t),
1719                        self.dialect.quote(l),
1720                        self.dialect.quote(r)
1721                    ));
1722                }
1723                JoinClause::Cross(t, on) => {
1724                    sql.push_str(&format!(
1725                        " CROSS JOIN {} ON {}",
1726                        self.dialect.quote(t),
1727                        self.dialect.quote(on)
1728                    ));
1729                }
1730            }
1731        }
1732
1733        let mut params = Vec::new();
1734        // P0-1:build_where_clause_with_params 内部已处理软删除条件
1735        let (where_clause, where_params) = self.build_where_clause_with_params();
1736        if !where_clause.is_empty() {
1737            sql.push_str(&where_clause);
1738            params = where_params;
1739        }
1740
1741        if !self.group_by.is_empty() {
1742            let cols: Vec<String> = self
1743                .group_by
1744                .iter()
1745                .map(|c| self.dialect.quote(c))
1746                .collect();
1747            sql.push_str(" GROUP BY ");
1748            sql.push_str(&cols.join(", "));
1749        }
1750
1751        if !self.having_conditions.is_empty() {
1752            sql.push_str(" HAVING ");
1753            for (i, cond) in self.having_conditions.iter().enumerate() {
1754                if i > 0 {
1755                    sql.push_str(" AND ");
1756                }
1757                if let WhereCondition::And(c) = cond {
1758                    sql.push_str(c);
1759                }
1760            }
1761        }
1762
1763        if !self.order_by.is_empty() {
1764            let order_cols: Vec<String> = self
1765                .order_by
1766                .iter()
1767                .map(|o| {
1768                    let dir = match o.direction {
1769                        OrderDirection::Asc => " ASC",
1770                        OrderDirection::Desc => " DESC",
1771                    };
1772                    format!("{}{}", self.dialect.quote(&o.field), dir)
1773                })
1774                .collect();
1775            sql.push_str(" ORDER BY ");
1776            sql.push_str(&order_cols.join(", "));
1777        }
1778
1779        if let Some(limit) = self.limit_value {
1780            sql.push_str(&format!(" LIMIT {}", limit));
1781        }
1782        if let Some(offset) = self.offset_value {
1783            sql.push_str(&format!(" OFFSET {}", offset));
1784        }
1785
1786        // P2-3:追加行锁子句(TASK-025/026)
1787        if let Some(lock_type) = &self.lock_type {
1788            if let Some(lock_clause) = self.dialect.build_lock_clause(*lock_type) {
1789                sql.push(' ');
1790                sql.push_str(&lock_clause);
1791            }
1792        }
1793
1794        (sql, params)
1795    }
1796
1797    /// 构建 INSERT SQL(参数绑定版本)。
1798    pub fn build_insert_with_params(
1799        &self,
1800        data: &std::collections::HashMap<String, Value>,
1801    ) -> (String, Vec<Value>) {
1802        let table = self
1803            .table
1804            .clone()
1805            .unwrap_or_else(|| M::table_name().to_string());
1806        if data.is_empty() {
1807            return (String::new(), Vec::new());
1808        }
1809
1810        let mut columns = Vec::with_capacity(data.len());
1811        let mut params = Vec::with_capacity(data.len());
1812        let placeholders: Vec<&str> = data.iter().map(|_| "?").collect();
1813        for (k, v) in data.iter() {
1814            columns.push(self.dialect.quote(k));
1815            params.push(v.clone());
1816        }
1817
1818        // P2-4:INSERT OR IGNORE 前缀(TASK-027/028)
1819        let insert_clause = if self.insert_or_ignore {
1820            self.dialect.build_insert_or_ignore_prefix(&table)
1821        } else {
1822            format!("INSERT INTO {}", self.dialect.quote(&table))
1823        };
1824
1825        let sql = format!(
1826            "{} ({}) VALUES ({})",
1827            insert_clause,
1828            columns.join(", "),
1829            placeholders.join(", ")
1830        );
1831        (sql, params)
1832    }
1833
1834    /// P2-6:构建批量 INSERT SQL(参数绑定版本)。
1835    ///
1836    /// 生成 `INSERT INTO t (c1, c2) VALUES (?, ?), (?, ?), ...` 形式的多行插入 SQL。
1837    /// 所有行的列必须一致(取第一行的列顺序);空行列表返回空 SQL。
1838    ///
1839    /// **L3 实现深度**:使用参数化占位符 `?`,所有值通过 `params` 绑定,杜绝 SQL 注入。
1840    pub fn build_batch_insert_with_params(
1841        &self,
1842        rows: &[std::collections::HashMap<String, Value>],
1843    ) -> (String, Vec<Value>) {
1844        let table = self
1845            .table
1846            .clone()
1847            .unwrap_or_else(|| M::table_name().to_string());
1848        if rows.is_empty() {
1849            return (String::new(), Vec::new());
1850        }
1851
1852        // 取第一行的列作为列顺序(所有行必须一致)
1853        let first_row = &rows[0];
1854        let columns: Vec<String> = first_row.keys().cloned().collect();
1855        let quoted_columns: Vec<String> = columns.iter().map(|c| self.dialect.quote(c)).collect();
1856
1857        let mut params = Vec::with_capacity(rows.len() * columns.len());
1858        let mut value_groups: Vec<String> = Vec::with_capacity(rows.len());
1859        let is_pg = self.dialect.db_type() == DbType::PostgreSQL;
1860        let mut param_idx = 1usize;
1861        for row in rows {
1862            let placeholders: Vec<String> = columns
1863                .iter()
1864                .map(|col| match row.get(col) {
1865                    Some(v) => {
1866                        params.push(v.clone());
1867                        if is_pg {
1868                            let p = format!("${}", param_idx);
1869                            param_idx += 1;
1870                            p
1871                        } else {
1872                            "?".to_string()
1873                        }
1874                    }
1875                    None => "NULL".to_string(),
1876                })
1877                .collect();
1878            value_groups.push(format!("({})", placeholders.join(", ")));
1879        }
1880
1881        let sql = format!(
1882            "INSERT INTO {} ({}) VALUES {}",
1883            self.dialect.quote(&table),
1884            quoted_columns.join(", "),
1885            value_groups.join(", ")
1886        );
1887        (sql, params)
1888    }
1889
1890    /// P2-6:构建批量 Upsert SQL(参数绑定版本)。
1891    ///
1892    /// 在 `build_batch_insert_with_params` 基础上追加冲突处理子句:
1893    /// - MySQL: `ON DUPLICATE KEY UPDATE col=VALUES(col), ...`
1894    /// - PostgreSQL/SQLite: `ON CONFLICT (conflict_cols) DO UPDATE SET col=EXCLUDED.col, ...`
1895    /// - Oracle/SQL Server/ClickHouse/Db2: 返回 `Err(DbError::InvalidInput)`(不支持)
1896    ///
1897    /// # 参数
1898    /// - `rows`: 批量数据行(所有行的列必须一致)
1899    /// - `conflict_columns`: 冲突检测列(主键/唯一键);MySQL 自动检测可传空
1900    /// - `update_columns`: 冲突时更新的列;空切片表示更新所有非冲突列
1901    ///
1902    /// # 返回
1903    /// - `Ok((sql, params))`: 生成的 SQL 和参数列表
1904    /// - `Err(DbError::InvalidInput)`: 方言不支持 upsert 或 rows 为空
1905    ///
1906    /// **L3 实现深度**:
1907    /// 1. SQL 下推:冲突处理由数据库执行,非内存判断
1908    /// 2. 参数化:所有值通过 `?` 占位符绑定,不拼接用户值
1909    /// 3. 实际执行:生成标准 INSERT...ON CONFLICT/ON DUPLICATE KEY SQL
1910    pub fn build_batch_upsert_with_params(
1911        &self,
1912        rows: &[std::collections::HashMap<String, Value>],
1913        conflict_columns: &[&str],
1914        update_columns: &[&str],
1915    ) -> Result<(String, Vec<Value>), crate::DbError> {
1916        if rows.is_empty() {
1917            return Err(crate::DbError::InvalidInput(
1918                "build_batch_upsert_with_params: rows cannot be empty".to_string(),
1919            ));
1920        }
1921
1922        // 构建批量 INSERT 部分
1923        let (insert_sql, params) = self.build_batch_insert_with_params(rows);
1924        if insert_sql.is_empty() {
1925            return Err(crate::DbError::InvalidInput(
1926                "build_batch_upsert_with_params: failed to build INSERT part".to_string(),
1927            ));
1928        }
1929
1930        // 取所有列名(原始未 quote)
1931        let all_columns: Vec<String> = rows[0].keys().cloned().collect();
1932
1933        // 调用方言生成冲突处理子句
1934        let conflict_clause = self
1935            .dialect
1936            .build_upsert_on_conflict(conflict_columns, update_columns, &all_columns)
1937            .ok_or_else(|| {
1938                crate::DbError::InvalidInput(format!(
1939                    "build_batch_upsert_with_params: dialect {:?} does not support upsert (ON CONFLICT / ON DUPLICATE KEY UPDATE). Consider using MERGE statement or individual upserts instead.",
1940                    self.dialect.db_type()
1941                ))
1942            })?;
1943
1944        let sql = format!("{} {}", insert_sql, conflict_clause);
1945        Ok((sql, params))
1946    }
1947
1948    /// 构建 UPDATE SQL(参数绑定版本)。
1949    /// 参数顺序:SET 参数在前,WHERE 参数在后。
1950    pub fn build_update_with_params(
1951        &self,
1952        data: &std::collections::HashMap<String, Value>,
1953    ) -> (String, Vec<Value>) {
1954        let table = self
1955            .table
1956            .clone()
1957            .unwrap_or_else(|| M::table_name().to_string());
1958        if data.is_empty() {
1959            return (String::new(), Vec::new());
1960        }
1961
1962        let mut set_clauses = Vec::with_capacity(data.len());
1963        let mut params = Vec::with_capacity(data.len());
1964        for (k, v) in data.iter() {
1965            set_clauses.push(format!("{} = ?", self.dialect.quote(k)));
1966            params.push(v.clone());
1967        }
1968
1969        let mut sql = format!(
1970            "UPDATE {} SET {}",
1971            self.dialect.quote(&table),
1972            set_clauses.join(", ")
1973        );
1974
1975        // P0-1:build_where_clause_with_params 内部已处理软删除条件
1976        let (where_clause, where_params) = self.build_where_clause_with_params();
1977        if !where_clause.is_empty() {
1978            sql.push_str(&where_clause);
1979            params.extend(where_params);
1980        }
1981
1982        (sql, params)
1983    }
1984
1985    /// 构建 DELETE SQL(参数绑定版本)。
1986    ///
1987    /// **P0-1 软删除集成(v1.3.0+)**:当 Model 启用软删除时,自动生成
1988    /// `UPDATE {table} SET {field} = NOW() WHERE ...` 而非 `DELETE FROM ...`。
1989    /// 参数列表为空(NOW() 由数据库填充)。
1990    pub fn build_delete_with_params(&self) -> (String, Vec<Value>) {
1991        let table = self
1992            .table
1993            .clone()
1994            .unwrap_or_else(|| M::table_name().to_string());
1995
1996        // P0-1:软删除启用时转为 UPDATE SET {field} = NOW()
1997        if let Some(field) = self.soft_delete_field() {
1998            let (where_clause, where_params) = self.build_where_clause_with_params();
1999            let sql = format!(
2000                "UPDATE {} SET {} = NOW(){}",
2001                self.dialect.quote(&table),
2002                self.dialect.quote(field),
2003                where_clause
2004            );
2005            return (sql, where_params);
2006        }
2007
2008        let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
2009        let mut params = Vec::new();
2010
2011        let (where_clause, where_params) = self.build_where_clause_with_params();
2012        if !where_clause.is_empty() {
2013            sql.push_str(&where_clause);
2014            params = where_params;
2015        }
2016
2017        (sql, params)
2018    }
2019
2020    /// 构建物理 DELETE SQL(参数绑定版本,绕过软删除)。
2021    ///
2022    /// 即使 Model 启用软删除,也生成 `DELETE FROM ...`,且不追加软删除过滤。
2023    pub fn build_force_delete_with_params(&self) -> (String, Vec<Value>) {
2024        let table = self
2025            .table
2026            .clone()
2027            .unwrap_or_else(|| M::table_name().to_string());
2028
2029        let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
2030        let mut params = Vec::new();
2031
2032        // P0-1:物理删除不追加软删除过滤,使用 build_where_clause_with_params_no_soft_delete
2033        let (where_clause, where_params) = self.build_where_clause_with_params_options(false);
2034        if !where_clause.is_empty() {
2035            sql.push_str(&where_clause);
2036            params = where_params;
2037        }
2038
2039        (sql, params)
2040    }
2041
2042    pub fn build_count(&self) -> String {
2043        let table = self
2044            .table
2045            .clone()
2046            .unwrap_or_else(|| M::table_name().to_string());
2047
2048        let mut sql = format!(
2049            "SELECT COUNT(*) as total FROM {}",
2050            self.dialect.quote(&table)
2051        );
2052        sql.push_str(&self.build_where_clause());
2053        sql
2054    }
2055
2056    pub fn build_exists(&self) -> String {
2057        let table = self
2058            .table
2059            .clone()
2060            .unwrap_or_else(|| M::table_name().to_string());
2061
2062        let mut sql = format!("SELECT 1 FROM {}", self.dialect.quote(&table));
2063        sql.push_str(&self.build_where_clause());
2064        sql.push_str(" LIMIT 1");
2065        format!("SELECT EXISTS({})", sql)
2066    }
2067
2068    pub fn build_max(&self, field: &str) -> String {
2069        let table = self
2070            .table
2071            .clone()
2072            .unwrap_or_else(|| M::table_name().to_string());
2073
2074        let mut sql = format!(
2075            "SELECT MAX({}) as max_val FROM {}",
2076            self.dialect.quote(field),
2077            self.dialect.quote(&table)
2078        );
2079        sql.push_str(&self.build_where_clause());
2080        sql
2081    }
2082
2083    pub fn build_min(&self, field: &str) -> String {
2084        let table = self
2085            .table
2086            .clone()
2087            .unwrap_or_else(|| M::table_name().to_string());
2088
2089        let mut sql = format!(
2090            "SELECT MIN({}) as min_val FROM {}",
2091            self.dialect.quote(field),
2092            self.dialect.quote(&table)
2093        );
2094        sql.push_str(&self.build_where_clause());
2095        sql
2096    }
2097
2098    pub fn build_sum(&self, field: &str) -> String {
2099        let table = self
2100            .table
2101            .clone()
2102            .unwrap_or_else(|| M::table_name().to_string());
2103
2104        let mut sql = format!(
2105            "SELECT SUM({}) as sum_val FROM {}",
2106            self.dialect.quote(field),
2107            self.dialect.quote(&table)
2108        );
2109        sql.push_str(&self.build_where_clause());
2110        sql
2111    }
2112
2113    pub fn build_avg(&self, field: &str) -> String {
2114        let table = self
2115            .table
2116            .clone()
2117            .unwrap_or_else(|| M::table_name().to_string());
2118
2119        let mut sql = format!(
2120            "SELECT AVG({}) as avg_val FROM {}",
2121            self.dialect.quote(field),
2122            self.dialect.quote(&table)
2123        );
2124        sql.push_str(&self.build_where_clause());
2125        sql
2126    }
2127
2128    /// 校验生成的 SELECT SQL 语句
2129    /// 检查 SQL 语法、JOIN 列名、表名合法性
2130    pub fn validate(&self) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
2131        let sql = self.build_select();
2132        let mut errors = Vec::new();
2133
2134        if let Err(e) = sz_orm_sql_validator::validate_select(&sql) {
2135            errors.push(e);
2136        }
2137
2138        // 校验 JOIN 子句产生的 SQL 是否合法
2139        if !self.joins.is_empty() {
2140            for join in &self.joins {
2141                match join {
2142                    JoinClause::Inner(_, left, right)
2143                    | JoinClause::Left(_, left, right)
2144                    | JoinClause::Right(_, left, right) => {
2145                        if let Err(e) = sz_orm_sql_validator::validate_column_name(left) {
2146                            errors.push(e);
2147                        }
2148                        if let Err(e) = sz_orm_sql_validator::validate_column_name(right) {
2149                            errors.push(e);
2150                        }
2151                    }
2152                    _ => {}
2153                }
2154            }
2155        }
2156
2157        // 校验表名合法性
2158        let table = self
2159            .table
2160            .clone()
2161            .unwrap_or_else(|| M::table_name().to_string());
2162        if let Err(e) = sz_orm_sql_validator::validate_table_name(&table) {
2163            errors.push(e);
2164        }
2165
2166        if errors.is_empty() {
2167            Ok(())
2168        } else {
2169            Err(errors)
2170        }
2171    }
2172
2173    /// 校验生成的 INSERT SQL 语句
2174    /// 含空数据检测(EmptyInsertData 错误)
2175    pub fn validate_insert(
2176        &self,
2177        data: &std::collections::HashMap<String, Value>,
2178    ) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
2179        let sql = self.build_insert(data);
2180        let mut errors = Vec::new();
2181
2182        if sql.is_empty() {
2183            errors.push(sz_orm_sql_validator::SqlValidationError::EmptyInsertData);
2184            return Err(errors);
2185        }
2186
2187        if let Err(e) = sz_orm_sql_validator::validate_insert(&sql) {
2188            errors.push(e);
2189        }
2190
2191        if errors.is_empty() {
2192            Ok(())
2193        } else {
2194            Err(errors)
2195        }
2196    }
2197
2198    /// 校验生成的 UPDATE SQL 语句
2199    /// 含空数据检测(EmptyUpdateData 错误)
2200    pub fn validate_update(
2201        &self,
2202        data: &std::collections::HashMap<String, Value>,
2203    ) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
2204        let sql = self.build_update(data);
2205        let mut errors = Vec::new();
2206
2207        if sql.is_empty() {
2208            errors.push(sz_orm_sql_validator::SqlValidationError::EmptyUpdateData);
2209            return Err(errors);
2210        }
2211
2212        if let Err(e) = sz_orm_sql_validator::validate_update(&sql) {
2213            errors.push(e);
2214        }
2215
2216        if errors.is_empty() {
2217            Ok(())
2218        } else {
2219            Err(errors)
2220        }
2221    }
2222
2223    /// 校验生成的 DELETE SQL 语句
2224    pub fn validate_delete(&self) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
2225        let sql = self.build_delete();
2226        let mut errors = Vec::new();
2227
2228        if let Err(e) = sz_orm_sql_validator::validate_delete(&sql) {
2229            errors.push(e);
2230        }
2231
2232        if errors.is_empty() {
2233            Ok(())
2234        } else {
2235            Err(errors)
2236        }
2237    }
2238}
2239
2240impl<M: Model> fmt::Debug for QueryBuilder<M> {
2241    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2242        f.debug_struct("QueryBuilder")
2243            .field("table", &self.table)
2244            .field("select_columns", &self.select_columns)
2245            .field("where_conditions", &self.where_conditions.len())
2246            .field("limit", &self.limit_value)
2247            .finish()
2248    }
2249}
2250
2251#[cfg(test)]
2252#[allow(deprecated)]
2253mod tests {
2254    use super::*;
2255    use crate::db_type::DbType;
2256    use crate::dialect::get_dialect;
2257
2258    struct TestModel;
2259    impl Model for TestModel {
2260        type PrimaryKey = i64;
2261
2262        fn table_name() -> &'static str {
2263            "test_models"
2264        }
2265
2266        fn pk(&self) -> Self::PrimaryKey {
2267            1
2268        }
2269
2270        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2271    }
2272
2273    #[test]
2274    fn test_query_builder_select() -> Result<(), crate::DbError> {
2275        let dialect = get_dialect(DbType::MySQL)?;
2276        let builder = QueryBuilder::<TestModel>::new(dialect);
2277
2278        let sql = builder
2279            .table("users")
2280            .select(vec!["id", "name"])
2281            .build_select();
2282        assert!(sql.contains("SELECT id, name FROM"));
2283        assert!(sql.contains("`users`"));
2284        Ok(())
2285    }
2286
2287    #[test]
2288    fn test_query_builder_where() -> Result<(), crate::DbError> {
2289        let dialect = get_dialect(DbType::MySQL)?;
2290        let builder = QueryBuilder::<TestModel>::new(dialect);
2291
2292        let sql = builder
2293            .table("users")
2294            .where_eq("status", crate::value::Value::String("active".into()))
2295            .where_gt("age", crate::value::Value::I64(18))
2296            .build_select();
2297
2298        assert!(sql.contains("WHERE"));
2299        assert!(sql.contains("`status` = 'active'"));
2300        assert!(sql.contains("`age` > 18"));
2301        Ok(())
2302    }
2303
2304    #[test]
2305    fn test_query_builder_order_by() -> Result<(), crate::DbError> {
2306        let dialect = get_dialect(DbType::MySQL)?;
2307        let builder = QueryBuilder::<TestModel>::new(dialect);
2308
2309        let sql = builder
2310            .table("users")
2311            .order_by("created_at")
2312            .order_desc("id")
2313            .build_select();
2314
2315        assert!(sql.contains("ORDER BY"));
2316        assert!(sql.contains("`created_at` ASC"));
2317        assert!(sql.contains("`id` DESC"));
2318        Ok(())
2319    }
2320
2321    #[test]
2322    fn test_query_builder_limit_offset() -> Result<(), crate::DbError> {
2323        let dialect = get_dialect(DbType::MySQL)?;
2324        let builder = QueryBuilder::<TestModel>::new(dialect);
2325
2326        let sql = builder.table("users").limit(10).offset(20).build_select();
2327
2328        assert!(sql.contains("LIMIT 10"));
2329        assert!(sql.contains("OFFSET 20"));
2330        Ok(())
2331    }
2332
2333    #[test]
2334    fn test_query_builder_page() -> Result<(), crate::DbError> {
2335        let dialect = get_dialect(DbType::MySQL)?;
2336        let builder = QueryBuilder::<TestModel>::new(dialect);
2337
2338        let sql = builder.table("users").page(3, 20).build_select();
2339
2340        assert!(sql.contains("LIMIT 20"));
2341        assert!(sql.contains("OFFSET 40"));
2342        Ok(())
2343    }
2344
2345    #[test]
2346    fn test_query_builder_insert() -> Result<(), crate::DbError> {
2347        let dialect = get_dialect(DbType::MySQL)?;
2348        let builder = QueryBuilder::<TestModel>::new(dialect);
2349
2350        let mut data = std::collections::HashMap::new();
2351        data.insert("name".to_string(), Value::String("test".to_string()));
2352        data.insert("age".to_string(), Value::I64(25));
2353
2354        let sql = builder.table("users").build_insert(&data);
2355
2356        assert!(sql.contains("INSERT INTO"));
2357        assert!(sql.contains("`name`"));
2358        assert!(sql.contains("'test'"));
2359        Ok(())
2360    }
2361
2362    #[test]
2363    fn test_query_builder_update() -> Result<(), crate::DbError> {
2364        let dialect = get_dialect(DbType::MySQL)?;
2365        let builder = QueryBuilder::<TestModel>::new(dialect);
2366
2367        let mut data = std::collections::HashMap::new();
2368        data.insert("name".to_string(), Value::String("updated".to_string()));
2369
2370        let sql = builder
2371            .table("users")
2372            .where_eq("id", Value::I64(1))
2373            .build_update(&data);
2374
2375        assert!(sql.contains("UPDATE"));
2376        assert!(sql.contains("`name` = 'updated'"));
2377        assert!(sql.contains("WHERE"));
2378        Ok(())
2379    }
2380
2381    #[test]
2382    fn test_query_builder_delete() -> Result<(), crate::DbError> {
2383        let dialect = get_dialect(DbType::MySQL)?;
2384        let builder = QueryBuilder::<TestModel>::new(dialect);
2385
2386        let sql = builder
2387            .table("users")
2388            .where_eq("id", Value::I64(1))
2389            .build_delete();
2390
2391        assert!(sql.contains("DELETE FROM"));
2392        assert!(sql.contains("WHERE"));
2393        Ok(())
2394    }
2395
2396    #[test]
2397    fn test_query_builder_count() -> Result<(), crate::DbError> {
2398        let dialect = get_dialect(DbType::MySQL)?;
2399        let builder = QueryBuilder::<TestModel>::new(dialect);
2400
2401        let sql = builder.table("users").build_count();
2402
2403        assert!(sql.contains("SELECT COUNT(*)"));
2404        assert!(sql.contains("FROM"));
2405        Ok(())
2406    }
2407
2408    #[test]
2409    fn test_query_builder_where_in() -> Result<(), crate::DbError> {
2410        let dialect = get_dialect(DbType::MySQL)?;
2411        let builder = QueryBuilder::<TestModel>::new(dialect);
2412
2413        let sql = builder
2414            .table("users")
2415            .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
2416            .build_select();
2417
2418        assert!(sql.contains("IN ("));
2419        Ok(())
2420    }
2421
2422    #[test]
2423    fn test_query_builder_where_between() -> Result<(), crate::DbError> {
2424        let dialect = get_dialect(DbType::MySQL)?;
2425        let builder = QueryBuilder::<TestModel>::new(dialect);
2426
2427        let sql = builder
2428            .table("users")
2429            .where_between("age", Value::I64(18), Value::I64(30))
2430            .build_select();
2431
2432        assert!(sql.contains("BETWEEN"));
2433        Ok(())
2434    }
2435
2436    #[test]
2437    fn test_query_builder_where_null() -> Result<(), crate::DbError> {
2438        let dialect = get_dialect(DbType::MySQL)?;
2439        let builder = QueryBuilder::<TestModel>::new(dialect);
2440
2441        let sql = builder
2442            .table("users")
2443            .where_null("deleted_at")
2444            .build_select();
2445
2446        assert!(sql.contains("IS NULL"));
2447        Ok(())
2448    }
2449
2450    #[test]
2451    fn test_query_builder_join() -> Result<(), crate::DbError> {
2452        let dialect = get_dialect(DbType::MySQL)?;
2453        let builder = QueryBuilder::<TestModel>::new(dialect);
2454
2455        let sql = builder
2456            .table("users")
2457            .join_inner("posts", "users.id", "posts.user_id")
2458            .build_select();
2459
2460        assert!(sql.contains("INNER JOIN"));
2461        assert!(sql.contains("`posts`"));
2462        Ok(())
2463    }
2464
2465    #[test]
2466    fn test_query_builder_group_by() -> Result<(), crate::DbError> {
2467        let dialect = get_dialect(DbType::MySQL)?;
2468        let builder = QueryBuilder::<TestModel>::new(dialect);
2469
2470        let sql = builder.table("users").group_by("status").build_select();
2471
2472        assert!(sql.contains("GROUP BY"));
2473        assert!(sql.contains("`status`"));
2474        Ok(())
2475    }
2476
2477    #[test]
2478    fn test_query_builder_max() -> Result<(), crate::DbError> {
2479        let dialect = get_dialect(DbType::MySQL)?;
2480        let builder = QueryBuilder::<TestModel>::new(dialect);
2481
2482        let sql = builder.table("users").build_max("score");
2483
2484        assert!(sql.contains("MAX("));
2485        assert!(sql.contains("`score`"));
2486        Ok(())
2487    }
2488
2489    #[test]
2490    fn test_query_builder_min() -> Result<(), crate::DbError> {
2491        let dialect = get_dialect(DbType::MySQL)?;
2492        let builder = QueryBuilder::<TestModel>::new(dialect);
2493
2494        let sql = builder.table("users").build_min("price");
2495
2496        assert!(sql.contains("MIN("));
2497        assert!(sql.contains("`price`"));
2498        Ok(())
2499    }
2500
2501    #[test]
2502    fn test_query_builder_sum() -> Result<(), crate::DbError> {
2503        let dialect = get_dialect(DbType::MySQL)?;
2504        let builder = QueryBuilder::<TestModel>::new(dialect);
2505
2506        let sql = builder.table("orders").build_sum("amount");
2507
2508        assert!(sql.contains("SUM("));
2509        assert!(sql.contains("`amount`"));
2510        Ok(())
2511    }
2512
2513    #[test]
2514    fn test_query_builder_avg() -> Result<(), crate::DbError> {
2515        let dialect = get_dialect(DbType::MySQL)?;
2516        let builder = QueryBuilder::<TestModel>::new(dialect);
2517
2518        let sql = builder.table("scores").build_avg("value");
2519
2520        assert!(sql.contains("AVG("));
2521        assert!(sql.contains("`value`"));
2522        Ok(())
2523    }
2524
2525    #[test]
2526    fn test_validator_select() -> Result<(), crate::DbError> {
2527        let dialect = get_dialect(DbType::MySQL)?;
2528        let builder = QueryBuilder::<TestModel>::new(dialect);
2529
2530        let result = builder.table("users").select(vec!["id", "name"]).validate();
2531        assert!(result.is_ok());
2532        Ok(())
2533    }
2534
2535    #[test]
2536    fn test_validator_select_with_join() -> Result<(), crate::DbError> {
2537        let dialect = get_dialect(DbType::MySQL)?;
2538        let builder = QueryBuilder::<TestModel>::new(dialect);
2539
2540        let result = builder
2541            .table("users")
2542            .join_inner("posts", "users.id", "posts.user_id")
2543            .validate();
2544        assert!(result.is_ok());
2545        Ok(())
2546    }
2547
2548    #[test]
2549    fn test_validator_insert() -> Result<(), crate::DbError> {
2550        let dialect = get_dialect(DbType::MySQL)?;
2551        let builder = QueryBuilder::<TestModel>::new(dialect);
2552
2553        let mut data = std::collections::HashMap::new();
2554        data.insert("name".to_string(), Value::String("test".to_string()));
2555
2556        let result = builder.table("users").validate_insert(&data);
2557        assert!(result.is_ok());
2558        Ok(())
2559    }
2560
2561    #[test]
2562    fn test_validator_insert_empty_data() -> Result<(), crate::DbError> {
2563        let dialect = get_dialect(DbType::MySQL)?;
2564        let builder = QueryBuilder::<TestModel>::new(dialect);
2565
2566        let data = std::collections::HashMap::new();
2567        let result = builder.table("users").validate_insert(&data);
2568        assert!(result.is_err());
2569        Ok(())
2570    }
2571
2572    #[test]
2573    fn test_validator_update() -> Result<(), crate::DbError> {
2574        let dialect = get_dialect(DbType::MySQL)?;
2575        let builder = QueryBuilder::<TestModel>::new(dialect);
2576
2577        let mut data = std::collections::HashMap::new();
2578        data.insert("name".to_string(), Value::String("updated".to_string()));
2579
2580        let result = builder.table("users").validate_update(&data);
2581        assert!(result.is_ok());
2582        Ok(())
2583    }
2584
2585    #[test]
2586    fn test_validator_update_empty_data() -> Result<(), crate::DbError> {
2587        let dialect = get_dialect(DbType::MySQL)?;
2588        let builder = QueryBuilder::<TestModel>::new(dialect);
2589
2590        let data = std::collections::HashMap::new();
2591        let result = builder.table("users").validate_update(&data);
2592        assert!(result.is_err());
2593        Ok(())
2594    }
2595
2596    #[test]
2597    fn test_validator_delete() -> Result<(), crate::DbError> {
2598        let dialect = get_dialect(DbType::MySQL)?;
2599        let builder = QueryBuilder::<TestModel>::new(dialect);
2600
2601        let result = builder
2602            .table("users")
2603            .where_eq("id", Value::I64(1))
2604            .validate_delete();
2605        assert!(result.is_ok());
2606        Ok(())
2607    }
2608
2609    #[test]
2610    fn test_validator_delete_no_where() -> Result<(), crate::DbError> {
2611        let dialect = get_dialect(DbType::MySQL)?;
2612        let builder = QueryBuilder::<TestModel>::new(dialect);
2613
2614        // DELETE without WHERE still produces valid SQL (just no filter)
2615        let result = builder.table("users").validate_delete();
2616        assert!(result.is_ok());
2617        Ok(())
2618    }
2619
2620    // ==================== M-3 select_quoted 测试 ====================
2621
2622    #[test]
2623    fn test_m3_select_quoted_valid_columns() -> Result<(), crate::DbError> {
2624        let dialect = get_dialect(DbType::MySQL)?;
2625        let builder = QueryBuilder::<TestModel>::new(dialect);
2626        let builder = builder.table("users").select_quoted(vec!["id", "name"])?;
2627        let sql = builder.build_select();
2628        // 应自动 quote 列名
2629        assert!(sql.contains("SELECT `id`, `name` FROM"));
2630        assert!(sql.contains("`users`"));
2631        Ok(())
2632    }
2633
2634    #[test]
2635    fn test_m3_select_quoted_rejects_sql_injection() -> Result<(), crate::DbError> {
2636        let dialect = get_dialect(DbType::MySQL)?;
2637        let builder = QueryBuilder::<TestModel>::new(dialect);
2638
2639        // SQL 注入尝试:分号 + DROP TABLE
2640        let result = builder
2641            .table("users")
2642            .select_quoted(vec!["id; DROP TABLE users"]);
2643        assert!(result.is_err());
2644
2645        // 含引号
2646        let dialect = get_dialect(DbType::MySQL)?;
2647        let builder = QueryBuilder::<TestModel>::new(dialect);
2648        let result = builder.table("users").select_quoted(vec!["name'"]);
2649        assert!(result.is_err());
2650
2651        // 数字开头
2652        let dialect = get_dialect(DbType::MySQL)?;
2653        let builder = QueryBuilder::<TestModel>::new(dialect);
2654        let result = builder.table("users").select_quoted(vec!["1col"]);
2655        assert!(result.is_err());
2656
2657        // 含空格
2658        let dialect = get_dialect(DbType::MySQL)?;
2659        let builder = QueryBuilder::<TestModel>::new(dialect);
2660        let result = builder.table("users").select_quoted(vec!["col name"]);
2661        assert!(result.is_err());
2662        Ok(())
2663    }
2664
2665    #[test]
2666    fn test_m3_select_quoted_postgresql_dialect() -> Result<(), crate::DbError> {
2667        let dialect = get_dialect(DbType::PostgreSQL)?;
2668        let builder = QueryBuilder::<TestModel>::new(dialect);
2669        let builder = builder.table("users").select_quoted(vec!["id", "name"])?;
2670        let sql = builder.build_select();
2671        // PostgreSQL 使用双引号
2672        assert!(sql.contains("SELECT \"id\", \"name\" FROM"));
2673        assert!(sql.contains("\"users\""));
2674        Ok(())
2675    }
2676
2677    // ==================== P0-1 软删除集成行为测试 ====================
2678
2679    /// 软删除测试模型:实现 soft_delete_field() 返回 "deleted_at"
2680    struct SoftDeleteModel;
2681    impl Model for SoftDeleteModel {
2682        type PrimaryKey = i64;
2683
2684        fn table_name() -> &'static str {
2685            "soft_users"
2686        }
2687
2688        fn pk(&self) -> Self::PrimaryKey {
2689            1
2690        }
2691
2692        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2693
2694        fn soft_delete_field() -> Option<&'static str> {
2695            Some("deleted_at")
2696        }
2697    }
2698
2699    /// 行为级测试 L3-1:软删除模型 build_select 自动追加 `WHERE deleted_at IS NULL`
2700    ///
2701    /// 用户视角:查询软删除模型时,自动过滤已删除记录,无需手动写条件。
2702    #[test]
2703    fn test_p01_soft_delete_select_auto_filter() -> Result<(), crate::DbError> {
2704        let dialect = get_dialect(DbType::MySQL)?;
2705        let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
2706        let sql = builder.table("soft_users").build_select();
2707        // 必须自动追加软删除过滤
2708        assert!(
2709            sql.contains("`deleted_at` IS NULL"),
2710            "软删除模型 SELECT 必须自动追加 `deleted_at` IS NULL,实际: {}",
2711            sql
2712        );
2713        Ok(())
2714    }
2715
2716    /// 行为级测试 L3-2:软删除模型 + 用户 WHERE 条件,软删除条件以 AND 追加
2717    #[test]
2718    fn test_p01_soft_delete_select_with_user_where() -> Result<(), crate::DbError> {
2719        let dialect = get_dialect(DbType::MySQL)?;
2720        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2721            .table("soft_users")
2722            .where_eq("status", Value::String("active".into()))
2723            .build_select();
2724        // 用户条件 + 软删除条件 同时存在
2725        assert!(sql.contains("`status` = "), "用户条件应保留: {}", sql);
2726        assert!(
2727            sql.contains("`deleted_at` IS NULL"),
2728            "软删除条件应自动追加: {}",
2729            sql
2730        );
2731        Ok(())
2732    }
2733
2734    /// 行为级测试 L3-3:without_soft_delete() 临时禁用软删除过滤
2735    ///
2736    /// 用户视角:管理员查询已删除记录时,可禁用自动过滤。
2737    #[test]
2738    fn test_p01_soft_delete_without_soft_delete() -> Result<(), crate::DbError> {
2739        let dialect = get_dialect(DbType::MySQL)?;
2740        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2741            .table("soft_users")
2742            .without_soft_delete()
2743            .build_select();
2744        // 不应包含软删除过滤
2745        assert!(
2746            !sql.contains("`deleted_at` IS NULL"),
2747            "without_soft_delete 应禁用过滤,实际: {}",
2748            sql
2749        );
2750        // 也应无 WHERE 子句(因为用户未提供任何条件)
2751        assert!(
2752            !sql.contains("WHERE"),
2753            "无用户条件 + 禁用软删除应无 WHERE 子句: {}",
2754            sql
2755        );
2756        Ok(())
2757    }
2758
2759    /// 行为级测试 L3-4:软删除模型 build_delete 自动转为 UPDATE
2760    ///
2761    /// 用户视角:调用 delete 实际是软删除 UPDATE,不是物理 DELETE。
2762    #[test]
2763    fn test_p01_soft_delete_delete_becomes_update() -> Result<(), crate::DbError> {
2764        let dialect = get_dialect(DbType::MySQL)?;
2765        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2766            .table("soft_users")
2767            .where_eq("id", Value::I64(42))
2768            .build_delete();
2769        // 应生成 UPDATE 而非 DELETE
2770        assert!(
2771            sql.starts_with("UPDATE"),
2772            "软删除模型的 build_delete 应生成 UPDATE,实际: {}",
2773            sql
2774        );
2775        assert!(
2776            !sql.contains("DELETE FROM"),
2777            "不应生成 DELETE FROM: {}",
2778            sql
2779        );
2780        assert!(
2781            sql.contains("`deleted_at` = NOW()"),
2782            "应设置 deleted_at = NOW(): {}",
2783            sql
2784        );
2785        // 软删除条件应自动追加,防止更新已删除记录
2786        assert!(
2787            sql.contains("`deleted_at` IS NULL"),
2788            "软删除 UPDATE 应追加 deleted_at IS NULL 防止重复删除: {}",
2789            sql
2790        );
2791        Ok(())
2792    }
2793
2794    /// 行为级测试 L3-5:build_force_delete 物理删除,不追加软删除过滤
2795    ///
2796    /// 用户视角:管理员强制清除时使用 build_force_delete。
2797    #[test]
2798    fn test_p01_soft_delete_force_delete() -> Result<(), crate::DbError> {
2799        let dialect = get_dialect(DbType::MySQL)?;
2800        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2801            .table("soft_users")
2802            .where_eq("id", Value::I64(99))
2803            .build_force_delete();
2804        // 应生成 DELETE FROM
2805        assert!(
2806            sql.starts_with("DELETE FROM"),
2807            "build_force_delete 应生成 DELETE FROM,实际: {}",
2808            sql
2809        );
2810        // 不应追加软删除过滤
2811        assert!(
2812            !sql.contains("`deleted_at` IS NULL"),
2813            "物理删除不应追加软删除过滤: {}",
2814            sql
2815        );
2816        Ok(())
2817    }
2818
2819    /// 行为级测试 L3-6:build_select_with_params 自动追加软删除条件(参数化版本)
2820    #[test]
2821    fn test_p01_soft_delete_select_with_params() -> Result<(), crate::DbError> {
2822        let dialect = get_dialect(DbType::MySQL)?;
2823        let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2824            .table("soft_users")
2825            .where_eq("id", Value::I64(1))
2826            .build_select_with_params();
2827        assert!(
2828            sql.contains("`deleted_at` IS NULL"),
2829            "参数化版本也应自动追加软删除: {}",
2830            sql
2831        );
2832        assert_eq!(params.len(), 1, "参数应为 1 个(用户 where_eq 的值)");
2833        assert_eq!(params[0], Value::I64(1));
2834        Ok(())
2835    }
2836
2837    /// 行为级测试 L3-7:build_delete_with_params 自动转为 UPDATE
2838    #[test]
2839    fn test_p01_soft_delete_delete_with_params_becomes_update() -> Result<(), crate::DbError> {
2840        let dialect = get_dialect(DbType::MySQL)?;
2841        let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2842            .table("soft_users")
2843            .where_eq("id", Value::I64(7))
2844            .build_delete_with_params();
2845        assert!(sql.starts_with("UPDATE"), "应生成 UPDATE: {}", sql);
2846        assert!(
2847            sql.contains("`deleted_at` = NOW()"),
2848            "应设置 NOW(): {}",
2849            sql
2850        );
2851        assert_eq!(params.len(), 1, "参数应为 1 个(WHERE 的值)");
2852        Ok(())
2853    }
2854
2855    /// 行为级测试 L3-8:build_force_delete_with_params 物理删除(参数化版本)
2856    #[test]
2857    fn test_p01_soft_delete_force_delete_with_params() -> Result<(), crate::DbError> {
2858        let dialect = get_dialect(DbType::MySQL)?;
2859        let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2860            .table("soft_users")
2861            .where_eq("id", Value::I64(11))
2862            .build_force_delete_with_params();
2863        assert!(sql.starts_with("DELETE FROM"), "应生成 DELETE: {}", sql);
2864        assert!(
2865            !sql.contains("`deleted_at` IS NULL"),
2866            "不应追加软删除过滤: {}",
2867            sql
2868        );
2869        assert_eq!(params.len(), 1);
2870        Ok(())
2871    }
2872
2873    /// 行为级测试 L3-9:非软删除模型 TestModel 不追加软删除条件
2874    ///
2875    /// 用户视角:未启用软删除的模型行为不变。
2876    #[test]
2877    fn test_p01_non_soft_delete_model_unchanged() -> Result<(), crate::DbError> {
2878        let dialect = get_dialect(DbType::MySQL)?;
2879        let sql = QueryBuilder::<TestModel>::new(dialect)
2880            .table("users")
2881            .where_eq("id", Value::I64(1))
2882            .build_select();
2883        assert!(
2884            !sql.contains("deleted_at"),
2885            "非软删除模型不应追加 deleted_at: {}",
2886            sql
2887        );
2888        // build_delete 仍生成 DELETE FROM
2889        let dialect = get_dialect(DbType::MySQL)?;
2890        let del_sql = QueryBuilder::<TestModel>::new(dialect)
2891            .table("users")
2892            .where_eq("id", Value::I64(1))
2893            .build_delete();
2894        assert!(
2895            del_sql.starts_with("DELETE FROM"),
2896            "非软删除模型 build_delete 应生成 DELETE: {}",
2897            del_sql
2898        );
2899        Ok(())
2900    }
2901
2902    /// 行为级测试 L3-10:build_count 也应自动追加软删除条件
2903    #[test]
2904    fn test_p01_soft_delete_count_auto_filter() -> Result<(), crate::DbError> {
2905        let dialect = get_dialect(DbType::MySQL)?;
2906        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2907            .table("soft_users")
2908            .build_count();
2909        assert!(
2910            sql.contains("`deleted_at` IS NULL"),
2911            "build_count 也应追加软删除过滤: {}",
2912            sql
2913        );
2914        Ok(())
2915    }
2916
2917    // ==================== P0-2 参数化查询注入防护测试 ====================
2918
2919    /// 行为级测试 L3-11:where_eq 使用 `?` 占位符,值收集到 params
2920    ///
2921    /// 用户视角:参数化查询杜绝 SQL 注入。
2922    #[test]
2923    fn test_p02_where_eq_uses_placeholder() -> Result<(), crate::DbError> {
2924        let dialect = get_dialect(DbType::MySQL)?;
2925        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2926            .table("users")
2927            .where_eq("name", Value::String("alice".into()))
2928            .build_select_with_params();
2929        // SQL 中应含 `?` 占位符,不应内嵌值
2930        assert!(sql.contains("`name` = ?"), "应使用 ? 占位符: {}", sql);
2931        assert!(!sql.contains("'alice'"), "不应内嵌值到 SQL: {}", sql);
2932        assert_eq!(params.len(), 1);
2933        assert_eq!(params[0], Value::String("alice".into()));
2934        Ok(())
2935    }
2936
2937    /// 行为级测试 L3-12:where_like 使用 `?` 占位符
2938    #[test]
2939    fn test_p02_where_like_uses_placeholder() -> Result<(), crate::DbError> {
2940        let dialect = get_dialect(DbType::MySQL)?;
2941        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2942            .table("users")
2943            .where_like("name", Value::String("%alice%".into()))
2944            .build_select_with_params();
2945        assert!(sql.contains("`name` LIKE ?"), "应使用 LIKE ?: {}", sql);
2946        assert!(!sql.contains("%alice%"), "不应内嵌 pattern: {}", sql);
2947        assert_eq!(params.len(), 1);
2948        Ok(())
2949    }
2950
2951    /// 行为级测试 L3-12a:where_ne 使用 `?` 占位符
2952    ///
2953    /// 验证 P0-2 参数化 API where_ne 生成 `field != ?` 且值不内嵌。
2954    #[test]
2955    fn test_p02_where_ne_uses_placeholder() -> Result<(), crate::DbError> {
2956        let dialect = get_dialect(DbType::MySQL)?;
2957        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2958            .table("users")
2959            .where_ne("status", Value::I64(0))
2960            .build_select_with_params();
2961        assert!(sql.contains("`status` != ?"), "应使用 != ?: {}", sql);
2962        assert!(!sql.contains("!= 0"), "不应内嵌值: {}", sql);
2963        assert_eq!(params.len(), 1);
2964        assert_eq!(params[0], Value::I64(0));
2965        Ok(())
2966    }
2967
2968    /// 行为级测试 L3-12b:where_ge 使用 `?` 占位符
2969    ///
2970    /// 验证 P0-2 参数化 API where_ge 生成 `field >= ?` 且值不内嵌。
2971    #[test]
2972    fn test_p02_where_ge_uses_placeholder() -> Result<(), crate::DbError> {
2973        let dialect = get_dialect(DbType::MySQL)?;
2974        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2975            .table("users")
2976            .where_ge("age", Value::I64(18))
2977            .build_select_with_params();
2978        assert!(sql.contains("`age` >= ?"), "应使用 >= ?: {}", sql);
2979        assert!(!sql.contains(">= 18"), "不应内嵌值: {}", sql);
2980        assert_eq!(params.len(), 1);
2981        assert_eq!(params[0], Value::I64(18));
2982        Ok(())
2983    }
2984
2985    /// 行为级测试 L3-12c:where_lt 使用 `?` 占位符
2986    ///
2987    /// 验证 P0-2 参数化 API where_lt 生成 `field < ?` 且值不内嵌。
2988    #[test]
2989    fn test_p02_where_lt_uses_placeholder() -> Result<(), crate::DbError> {
2990        let dialect = get_dialect(DbType::MySQL)?;
2991        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2992            .table("users")
2993            .where_lt("score", Value::F64(60.0))
2994            .build_select_with_params();
2995        assert!(sql.contains("`score` < ?"), "应使用 < ?: {}", sql);
2996        assert!(!sql.contains("< 60"), "不应内嵌值: {}", sql);
2997        assert_eq!(params.len(), 1);
2998        assert_eq!(params[0], Value::F64(60.0));
2999        Ok(())
3000    }
3001
3002    /// 行为级测试 L3-13:注入攻击防护 - 值含 SQL 关键字也不会被解释执行
3003    ///
3004    /// 用户视角:即使用户输入 `'; DROP TABLE users; --`,也不会造成注入。
3005    #[test]
3006    fn test_p02_injection_protection_drop_table() -> Result<(), crate::DbError> {
3007        let dialect = get_dialect(DbType::MySQL)?;
3008        let evil_input = "'; DROP TABLE users; --".to_string();
3009        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3010            .table("users")
3011            .where_eq("name", Value::String(evil_input.clone()))
3012            .build_select_with_params();
3013        // SQL 中不应出现 DROP TABLE
3014        assert!(!sql.contains("DROP TABLE"), "SQL 注入未防护: {}", sql);
3015        // 整个恶意字符串应作为单一参数传递
3016        assert_eq!(params.len(), 1);
3017        assert_eq!(params[0], Value::String(evil_input));
3018        // SQL 中只有 1 个 `?`
3019        assert_eq!(sql.matches('?').count(), 1);
3020        Ok(())
3021    }
3022
3023    /// 行为级测试 L3-14:注入攻击防护 - OR 1=1 经典攻击
3024    #[test]
3025    fn test_p02_injection_protection_or_one_equals_one() -> Result<(), crate::DbError> {
3026        let dialect = get_dialect(DbType::MySQL)?;
3027        let evil = "' OR '1'='1".to_string();
3028        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3029            .table("users")
3030            .where_eq("name", Value::String(evil.clone()))
3031            .build_select_with_params();
3032        assert!(!sql.contains("OR '1'='1'"), "OR 1=1 注入未防护: {}", sql);
3033        assert_eq!(params.len(), 1);
3034        assert_eq!(params[0], Value::String(evil));
3035        Ok(())
3036    }
3037
3038    /// 行为级测试 L3-15:多参数顺序正确(WHERE a = ? AND b = ?)
3039    #[test]
3040    fn test_p02_multiple_params_order() -> Result<(), crate::DbError> {
3041        let dialect = get_dialect(DbType::MySQL)?;
3042        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3043            .table("users")
3044            .where_eq("name", Value::String("alice".into()))
3045            .where_gt("age", Value::I64(18))
3046            .where_le("score", Value::F64(99.5))
3047            .build_select_with_params();
3048        assert_eq!(sql.matches('?').count(), 3, "应有 3 个占位符: {}", sql);
3049        assert_eq!(params.len(), 3);
3050        // 参数顺序应与 WHERE 子句出现顺序一致
3051        assert_eq!(params[0], Value::String("alice".into()));
3052        assert_eq!(params[1], Value::I64(18));
3053        assert_eq!(params[2], Value::F64(99.5));
3054        Ok(())
3055    }
3056
3057    /// 行为级测试 L3-16:where_in 参数化
3058    #[test]
3059    fn test_p02_where_in_uses_placeholders() -> Result<(), crate::DbError> {
3060        let dialect = get_dialect(DbType::MySQL)?;
3061        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3062            .table("users")
3063            .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
3064            .build_select_with_params();
3065        assert!(
3066            sql.contains("`id` IN (?, ?, ?)"),
3067            "应使用 3 个占位符: {}",
3068            sql
3069        );
3070        assert_eq!(params.len(), 3);
3071        Ok(())
3072    }
3073
3074    /// 行为级测试 L3-17:where_between 参数化
3075    #[test]
3076    fn test_p02_where_between_uses_placeholders() -> Result<(), crate::DbError> {
3077        let dialect = get_dialect(DbType::MySQL)?;
3078        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3079            .table("users")
3080            .where_between("age", Value::I64(18), Value::I64(65))
3081            .build_select_with_params();
3082        assert!(
3083            sql.contains("`age` BETWEEN ? AND ?"),
3084            "应使用 2 个占位符: {}",
3085            sql
3086        );
3087        assert_eq!(params.len(), 2);
3088        assert_eq!(params[0], Value::I64(18));
3089        assert_eq!(params[1], Value::I64(65));
3090        Ok(())
3091    }
3092
3093    /// 行为级测试 L3-18:UPDATE 参数化版本 - SET 参数在前,WHERE 参数在后
3094    #[test]
3095    fn test_p02_update_params_order_set_before_where() -> Result<(), crate::DbError> {
3096        let dialect = get_dialect(DbType::MySQL)?;
3097        let mut data = std::collections::HashMap::new();
3098        data.insert("name".to_string(), Value::String("bob".into()));
3099        data.insert("age".to_string(), Value::I64(30));
3100        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3101            .table("users")
3102            .where_eq("id", Value::I64(99))
3103            .build_update_with_params(&data);
3104        // SET 子句应有 2 个占位符,WHERE 子句 1 个,共 3 个
3105        assert_eq!(sql.matches('?').count(), 3, "应有 3 个 ?: {}", sql);
3106        assert_eq!(params.len(), 3);
3107        // 前 2 个为 SET 参数,最后 1 个为 WHERE 参数
3108        // 注意:HashMap 迭代顺序未指定,仅校验 WHERE 参数在最后
3109        assert_eq!(params[2], Value::I64(99));
3110        Ok(())
3111    }
3112
3113    /// 行为级测试 L3-19:build_where_clause(无参数版本)参数化条件内嵌值
3114    ///
3115    /// 验证无参数版本(build_select)对参数化条件的处理:直接内嵌转义值。
3116    #[test]
3117    fn test_p02_build_where_clause_inlines_value() -> Result<(), crate::DbError> {
3118        let dialect = get_dialect(DbType::MySQL)?;
3119        let sql = QueryBuilder::<TestModel>::new(dialect)
3120            .table("users")
3121            .where_eq("name", Value::String("alice".into()))
3122            .build_select();
3123        // 无参数版本应内嵌值(依赖 to_param_with_dialect 转义)
3124        assert!(
3125            sql.contains("`name` = "),
3126            "无参数版本应含 WHERE 条件: {}",
3127            sql
3128        );
3129        // 不应含 `?`(无参数版本)
3130        assert!(
3131            !sql.contains("`name` = ?"),
3132            "无参数版本不应使用 ? 占位符: {}",
3133            sql
3134        );
3135        Ok(())
3136    }
3137
3138    /// 行为级测试 L3-20:is_soft_delete_disabled 反映状态
3139    #[test]
3140    fn test_p01_is_soft_delete_disabled_flag() -> Result<(), crate::DbError> {
3141        let dialect = get_dialect(DbType::MySQL)?;
3142        let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
3143        assert!(!builder.is_soft_delete_disabled(), "默认应启用软删除过滤");
3144        let builder =
3145            QueryBuilder::<SoftDeleteModel>::new(get_dialect(DbType::MySQL)?).without_soft_delete();
3146        assert!(
3147            builder.is_soft_delete_disabled(),
3148            "without_soft_delete 后应反映禁用状态"
3149        );
3150        Ok(())
3151    }
3152
3153    // ==================== P0-3 多租户自动过滤行为测试 ====================
3154
3155    /// 多租户测试模型:实现 tenant_field() 返回 "tenant_id"
3156    struct TenantModel;
3157    impl Model for TenantModel {
3158        type PrimaryKey = i64;
3159
3160        fn table_name() -> &'static str {
3161            "orders"
3162        }
3163
3164        fn pk(&self) -> Self::PrimaryKey {
3165            1
3166        }
3167
3168        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
3169
3170        fn tenant_field() -> Option<&'static str> {
3171            Some("tenant_id")
3172        }
3173    }
3174
3175    /// 同时实现软删除 + 多租户的模型
3176    struct SoftDeleteAndTenantModel;
3177    impl Model for SoftDeleteAndTenantModel {
3178        type PrimaryKey = i64;
3179
3180        fn table_name() -> &'static str {
3181            "documents"
3182        }
3183
3184        fn pk(&self) -> Self::PrimaryKey {
3185            1
3186        }
3187
3188        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
3189
3190        fn soft_delete_field() -> Option<&'static str> {
3191            Some("deleted_at")
3192        }
3193
3194        fn tenant_field() -> Option<&'static str> {
3195            Some("tenant_id")
3196        }
3197    }
3198
3199    /// 行为级测试 L3-21:多租户模型 + with_tenant_id 自动追加 WHERE tenant_id = ?
3200    ///
3201    /// 用户视角:设置租户 ID 后,查询自动过滤当前租户数据。
3202    #[test]
3203    fn test_p03_tenant_select_auto_filter() -> Result<(), crate::DbError> {
3204        let dialect = get_dialect(DbType::MySQL)?;
3205        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3206            .table("orders")
3207            .with_tenant_id(42)
3208            .build_select_with_params();
3209        assert!(
3210            sql.contains("`tenant_id` = ?"),
3211            "多租户模型应自动追加 tenant_id = ?: {}",
3212            sql
3213        );
3214        assert_eq!(params.len(), 1, "应有 1 个参数(tenant_id 值)");
3215        assert_eq!(params[0], Value::I64(42));
3216        Ok(())
3217    }
3218
3219    /// 行为级测试 L3-22:多租户模型 + 用户 WHERE 条件 + 租户条件
3220    #[test]
3221    fn test_p03_tenant_select_with_user_where() -> Result<(), crate::DbError> {
3222        let dialect = get_dialect(DbType::MySQL)?;
3223        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3224            .table("orders")
3225            .with_tenant_id(7)
3226            .where_eq("status", Value::String("active".into()))
3227            .build_select_with_params();
3228        assert!(sql.contains("`status` = ?"), "用户条件应保留: {}", sql);
3229        assert!(
3230            sql.contains("`tenant_id` = ?"),
3231            "租户条件应自动追加: {}",
3232            sql
3233        );
3234        assert_eq!(params.len(), 2, "应有 2 个参数");
3235        // 第 1 个为用户 where_eq 的值,第 2 个为 tenant_id
3236        assert_eq!(params[0], Value::String("active".into()));
3237        assert_eq!(params[1], Value::I64(7));
3238        Ok(())
3239    }
3240
3241    /// 行为级测试 L3-23:without_tenant() 临时禁用租户过滤
3242    ///
3243    /// 用户视角:管理员跨租户查询时禁用自动过滤。
3244    #[test]
3245    fn test_p03_tenant_without_tenant() -> Result<(), crate::DbError> {
3246        let dialect = get_dialect(DbType::MySQL)?;
3247        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3248            .table("orders")
3249            .with_tenant_id(42)
3250            .without_tenant()
3251            .build_select_with_params();
3252        assert!(
3253            !sql.contains("`tenant_id` = ?"),
3254            "without_tenant 应禁用过滤: {}",
3255            sql
3256        );
3257        assert_eq!(params.len(), 0, "不应有租户参数");
3258        Ok(())
3259    }
3260
3261    /// 行为级测试 L3-24:多租户模型 build_delete 自动追加租户条件
3262    ///
3263    /// 用户视角:删除操作自动限定在当前租户,防止跨租户删除。
3264    #[test]
3265    fn test_p03_tenant_delete_auto_filter() -> Result<(), crate::DbError> {
3266        let dialect = get_dialect(DbType::MySQL)?;
3267        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3268            .table("orders")
3269            .with_tenant_id(99)
3270            .where_eq("id", Value::I64(1))
3271            .build_delete_with_params();
3272        assert!(
3273            sql.contains("`tenant_id` = ?"),
3274            "删除应自动追加租户条件: {}",
3275            sql
3276        );
3277        // 2 个参数:where_eq(id=1) + tenant_id=99
3278        assert_eq!(params.len(), 2);
3279        assert_eq!(params[0], Value::I64(1));
3280        assert_eq!(params[1], Value::I64(99));
3281        Ok(())
3282    }
3283
3284    /// 行为级测试 L3-25:多租户模型 build_update 自动追加租户条件
3285    #[test]
3286    fn test_p03_tenant_update_auto_filter() -> Result<(), crate::DbError> {
3287        let dialect = get_dialect(DbType::MySQL)?;
3288        let mut data = std::collections::HashMap::new();
3289        data.insert("status".to_string(), Value::String("shipped".into()));
3290        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3291            .table("orders")
3292            .with_tenant_id(5)
3293            .where_eq("id", Value::I64(10))
3294            .build_update_with_params(&data);
3295        assert!(
3296            sql.contains("`tenant_id` = ?"),
3297            "更新应自动追加租户条件: {}",
3298            sql
3299        );
3300        // 3 个参数:SET status + WHERE id + tenant_id
3301        assert_eq!(params.len(), 3);
3302        // 最后一个应为 tenant_id
3303        assert_eq!(params[2], Value::I64(5));
3304        Ok(())
3305    }
3306
3307    /// 行为级测试 L3-26:多租户模型 build_count 自动追加租户条件
3308    #[test]
3309    fn test_p03_tenant_count_auto_filter() -> Result<(), crate::DbError> {
3310        let dialect = get_dialect(DbType::MySQL)?;
3311        let sql = QueryBuilder::<TenantModel>::new(dialect)
3312            .table("orders")
3313            .with_tenant_id(42)
3314            .build_count();
3315        assert!(
3316            sql.contains("`tenant_id` = 42"),
3317            "build_count 应追加租户条件(无参数版本内嵌值): {}",
3318            sql
3319        );
3320        Ok(())
3321    }
3322
3323    /// 行为级测试 L3-27:非多租户模型 TestModel 不追加租户条件
3324    ///
3325    /// 用户视角:未启用多租户的模型行为不变。
3326    #[test]
3327    fn test_p03_non_tenant_model_unchanged() -> Result<(), crate::DbError> {
3328        let dialect = get_dialect(DbType::MySQL)?;
3329        // 即使设置了 with_tenant_id,非多租户模型也不应追加
3330        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3331            .table("users")
3332            .with_tenant_id(42)
3333            .build_select_with_params();
3334        assert!(
3335            !sql.contains("tenant_id"),
3336            "非多租户模型不应追加 tenant_id: {}",
3337            sql
3338        );
3339        assert_eq!(params.len(), 0);
3340        Ok(())
3341    }
3342
3343    /// 行为级测试 L3-28:多租户模型未设置 tenant_id 时不追加条件
3344    ///
3345    /// 用户视角:未设置租户 ID 时,查询不追加租户过滤(允许跨租户,需调用方保证安全)。
3346    #[test]
3347    fn test_p03_tenant_no_id_no_filter() -> Result<(), crate::DbError> {
3348        let dialect = get_dialect(DbType::MySQL)?;
3349        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3350            .table("orders")
3351            .build_select_with_params();
3352        assert!(
3353            !sql.contains("tenant_id"),
3354            "未设置 tenant_id 时不应追加过滤: {}",
3355            sql
3356        );
3357        assert_eq!(params.len(), 0);
3358        Ok(())
3359    }
3360
3361    /// 行为级测试 L3-29:软删除 + 多租户组合,两个条件同时追加
3362    ///
3363    /// 用户视角:同时启用软删除和多租户时,查询自动追加两个条件。
3364    #[test]
3365    fn test_p03_soft_delete_and_tenant_combined() -> Result<(), crate::DbError> {
3366        let dialect = get_dialect(DbType::MySQL)?;
3367        let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3368            .table("documents")
3369            .with_tenant_id(100)
3370            .where_eq("title", Value::String("report".into()))
3371            .build_select_with_params();
3372        // 软删除条件
3373        assert!(
3374            sql.contains("`deleted_at` IS NULL"),
3375            "应追加软删除条件: {}",
3376            sql
3377        );
3378        // 租户条件
3379        assert!(sql.contains("`tenant_id` = ?"), "应追加租户条件: {}", sql);
3380        // 用户条件
3381        assert!(sql.contains("`title` = ?"), "用户条件应保留: {}", sql);
3382        // 2 个参数:where_eq(title) + tenant_id(软删除 IS NULL 无参数)
3383        assert_eq!(params.len(), 2);
3384        assert_eq!(params[0], Value::String("report".into()));
3385        assert_eq!(params[1], Value::I64(100));
3386        Ok(())
3387    }
3388
3389    /// 行为级测试 L3-30:without_tenant + without_soft_delete 同时禁用
3390    #[test]
3391    fn test_p03_without_tenant_and_soft_delete() -> Result<(), crate::DbError> {
3392        let dialect = get_dialect(DbType::MySQL)?;
3393        let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3394            .table("documents")
3395            .with_tenant_id(100)
3396            .without_tenant()
3397            .without_soft_delete()
3398            .build_select_with_params();
3399        assert!(
3400            !sql.contains("`deleted_at` IS NULL"),
3401            "应禁用软删除: {}",
3402            sql
3403        );
3404        assert!(!sql.contains("`tenant_id` = ?"), "应禁用租户: {}", sql);
3405        assert_eq!(params.len(), 0);
3406        Ok(())
3407    }
3408
3409    /// 行为级测试 L3-31:is_tenant_disabled 反映状态
3410    #[test]
3411    fn test_p03_is_tenant_disabled_flag() -> Result<(), crate::DbError> {
3412        let dialect = get_dialect(DbType::MySQL)?;
3413        let builder = QueryBuilder::<TenantModel>::new(dialect);
3414        assert!(!builder.is_tenant_disabled(), "默认应启用租户过滤");
3415        let builder = QueryBuilder::<TenantModel>::new(get_dialect(DbType::MySQL)?)
3416            .with_tenant_id(1)
3417            .without_tenant();
3418        assert!(
3419            builder.is_tenant_disabled(),
3420            "without_tenant 后应反映禁用状态"
3421        );
3422        Ok(())
3423    }
3424
3425    /// 行为级测试 L3-32:build_force_delete 保留租户条件(防止跨租户物理删除)
3426    ///
3427    /// 用户视角:物理删除也应受租户隔离约束,跨租户操作需显式 without_tenant()。
3428    #[test]
3429    fn test_p03_tenant_force_delete_keeps_tenant_filter() -> Result<(), crate::DbError> {
3430        let dialect = get_dialect(DbType::MySQL)?;
3431        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3432            .table("orders")
3433            .with_tenant_id(42)
3434            .where_eq("id", Value::I64(999))
3435            .build_force_delete_with_params();
3436        // 物理删除不应追加软删除(TenantModel 未实现软删除,无影响)
3437        // 但应保留租户条件
3438        assert!(
3439            sql.contains("`tenant_id` = ?"),
3440            "物理删除应保留租户条件: {}",
3441            sql
3442        );
3443        assert_eq!(params.len(), 2);
3444        assert_eq!(params[0], Value::I64(999));
3445        assert_eq!(params[1], Value::I64(42));
3446        Ok(())
3447    }
3448
3449    // ---- TypedColumn 类型安全方法测试 ----
3450
3451    struct TcUsersTable;
3452    impl crate::typed::TypedTable for TcUsersTable {
3453        const NAME: &'static str = "users";
3454    }
3455    struct TcColId;
3456    impl crate::typed::TypedColumn for TcColId {
3457        const NAME: &'static str = "id";
3458        type Table = TcUsersTable;
3459        type RustType = i64;
3460        type SqlType = crate::typed_ast::Untyped;
3461    }
3462    struct TcColName;
3463    impl crate::typed::TypedColumn for TcColName {
3464        const NAME: &'static str = "name";
3465        type Table = TcUsersTable;
3466        type RustType = String;
3467        type SqlType = crate::typed_ast::Untyped;
3468    }
3469
3470    #[test]
3471    fn test_where_eq_typed() -> Result<(), crate::DbError> {
3472        let dialect = get_dialect(DbType::MySQL)?;
3473        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3474            .where_eq_typed::<TcColId>(Value::I64(42))
3475            .build_select_with_params();
3476        assert!(sql.contains("`id` = ?"));
3477        assert_eq!(params[0], Value::I64(42));
3478        Ok(())
3479    }
3480
3481    #[test]
3482    fn test_order_by_typed() -> Result<(), crate::DbError> {
3483        let dialect = get_dialect(DbType::MySQL)?;
3484        let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3485            .order_by_typed::<TcColName>()
3486            .build_select_with_params();
3487        assert!(sql.contains("ORDER BY"));
3488        assert!(sql.contains("`name`"));
3489        Ok(())
3490    }
3491
3492    #[test]
3493    fn test_select_typed() -> Result<(), crate::DbError> {
3494        let dialect = get_dialect(DbType::MySQL)?;
3495        let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3496            .select_typed::<TcColId>()
3497            .select_typed::<TcColName>()
3498            .build_select_with_params();
3499        assert!(sql.contains("SELECT"));
3500        // select_columns are joined as-is (not quoted by build_select_with_params)
3501        assert!(sql.contains("id"));
3502        assert!(sql.contains("name"));
3503        Ok(())
3504    }
3505
3506    #[test]
3507    fn test_where_null_typed() -> Result<(), crate::DbError> {
3508        let dialect = get_dialect(DbType::MySQL)?;
3509        let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3510            .where_null_typed::<TcColName>()
3511            .build_select_with_params();
3512        assert!(sql.contains("`name` IS NULL"));
3513        Ok(())
3514    }
3515
3516    #[test]
3517    fn test_where_not_null_typed() -> Result<(), crate::DbError> {
3518        let dialect = get_dialect(DbType::MySQL)?;
3519        let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3520            .where_not_null_typed::<TcColName>()
3521            .build_select_with_params();
3522        assert!(sql.contains("`name` IS NOT NULL"));
3523        Ok(())
3524    }
3525
3526    #[test]
3527    fn test_group_by_typed() -> Result<(), crate::DbError> {
3528        let dialect = get_dialect(DbType::MySQL)?;
3529        let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3530            .group_by_typed::<TcColName>()
3531            .build_select_with_params();
3532        assert!(sql.contains("GROUP BY"));
3533        assert!(sql.contains("`name`"));
3534        Ok(())
3535    }
3536
3537    #[test]
3538    fn test_where_gt_typed() -> Result<(), crate::DbError> {
3539        let dialect = get_dialect(DbType::MySQL)?;
3540        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3541            .where_gt_typed::<TcColId>(Value::I64(10))
3542            .build_select_with_params();
3543        assert!(sql.contains("`id` > ?"));
3544        assert_eq!(params[0], Value::I64(10));
3545        Ok(())
3546    }
3547
3548    // ---- P2-3:行锁查询测试(TASK-025/026) ----
3549
3550    #[test]
3551    fn test_lock_for_update_mysql() -> Result<(), crate::DbError> {
3552        let dialect = get_dialect(DbType::MySQL)?;
3553        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3554            .table("users")
3555            .where_eq("id", Value::I64(1))
3556            .lock_for_update()?
3557            .build_select_with_params();
3558        assert!(sql.contains("SELECT * FROM `users`"));
3559        assert!(sql.contains("WHERE `id` = ?"));
3560        assert!(sql.contains("FOR UPDATE"));
3561        assert_eq!(params.len(), 1);
3562        assert_eq!(params[0], Value::I64(1));
3563        Ok(())
3564    }
3565
3566    #[test]
3567    fn test_lock_shared_mysql() -> Result<(), crate::DbError> {
3568        let dialect = get_dialect(DbType::MySQL)?;
3569        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3570            .table("users")
3571            .where_eq("id", Value::I64(1))
3572            .lock_shared()?
3573            .build_select_with_params();
3574        assert!(sql.contains("SELECT * FROM `users`"));
3575        assert!(sql.contains("WHERE `id` = ?"));
3576        assert!(sql.contains("LOCK IN SHARE MODE"));
3577        assert_eq!(params.len(), 1);
3578        assert_eq!(params[0], Value::I64(1));
3579        Ok(())
3580    }
3581
3582    #[test]
3583    fn test_lock_for_update_postgresql() -> Result<(), crate::DbError> {
3584        let dialect = get_dialect(DbType::PostgreSQL)?;
3585        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3586            .table("users")
3587            .where_eq("id", Value::I64(1))
3588            .lock_for_update()?
3589            .build_select_with_params();
3590        assert!(sql.contains("SELECT * FROM \"users\""));
3591        assert!(sql.contains("WHERE \"id\" = ?"));
3592        assert!(sql.contains("FOR UPDATE"));
3593        assert_eq!(params.len(), 1);
3594        assert_eq!(params[0], Value::I64(1));
3595        Ok(())
3596    }
3597
3598    #[test]
3599    fn test_lock_shared_postgresql() -> Result<(), crate::DbError> {
3600        let dialect = get_dialect(DbType::PostgreSQL)?;
3601        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3602            .table("users")
3603            .where_eq("id", Value::I64(1))
3604            .lock_shared()?
3605            .build_select_with_params();
3606        assert!(sql.contains("SELECT * FROM \"users\""));
3607        assert!(sql.contains("WHERE \"id\" = ?"));
3608        assert!(sql.contains("FOR SHARE"));
3609        assert_eq!(params.len(), 1);
3610        assert_eq!(params[0], Value::I64(1));
3611        Ok(())
3612    }
3613
3614    #[test]
3615    fn test_lock_for_update_sqlite_should_fail() {
3616        let dialect = get_dialect(DbType::Sqlite).unwrap();
3617        let result = QueryBuilder::<TestModel>::new(dialect)
3618            .table("users")
3619            .where_eq("id", Value::I64(1))
3620            .lock_for_update();
3621        assert!(result.is_err(), "SQLite 不应支持 FOR UPDATE 锁");
3622        let err = result.err().unwrap();
3623        assert!(
3624            format!("{:?}", err).contains("FOR UPDATE lock is not supported"),
3625            "错误信息应说明不支持行锁"
3626        );
3627    }
3628
3629    #[test]
3630    fn test_lock_shared_sqlite_should_fail() {
3631        let dialect = get_dialect(DbType::Sqlite).unwrap();
3632        let result = QueryBuilder::<TestModel>::new(dialect)
3633            .table("users")
3634            .where_eq("id", Value::I64(1))
3635            .lock_shared();
3636        assert!(result.is_err(), "SQLite 不应支持共享锁");
3637        let err = result.err().unwrap();
3638        assert!(
3639            format!("{:?}", err).contains("Shared lock is not supported"),
3640            "错误信息应说明不支持共享锁"
3641        );
3642    }
3643
3644    #[test]
3645    fn test_lock_with_limit_and_offset() -> Result<(), crate::DbError> {
3646        let dialect = get_dialect(DbType::MySQL)?;
3647        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3648            .table("users")
3649            .where_eq("status", Value::String("active".into()))
3650            .limit(10)
3651            .offset(20)
3652            .lock_for_update()?
3653            .build_select_with_params();
3654        assert!(sql.contains("WHERE `status` = ?"));
3655        assert!(sql.contains("LIMIT 10"));
3656        assert!(sql.contains("OFFSET 20"));
3657        assert!(sql.contains("FOR UPDATE"));
3658        assert_eq!(params.len(), 1);
3659        Ok(())
3660    }
3661
3662    // ---- P2-4:INSERT OR IGNORE 测试(TASK-027/028) ----
3663
3664    #[test]
3665    fn test_insert_or_ignore_mysql() -> Result<(), crate::DbError> {
3666        let dialect = get_dialect(DbType::MySQL)?;
3667        let mut data = std::collections::HashMap::new();
3668        data.insert("name".to_string(), Value::String("Alice".into()));
3669        data.insert("age".to_string(), Value::I64(30));
3670
3671        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3672            .table("users")
3673            .insert_or_ignore()
3674            .build_insert_with_params(&data);
3675        assert!(sql.contains("INSERT IGNORE INTO `users`"));
3676        // HashMap 迭代顺序不确定,检查列名都存在即可
3677        assert!(sql.contains("`name`"), "SQL 应包含 name 列: {}", sql);
3678        assert!(sql.contains("`age`"), "SQL 应包含 age 列: {}", sql);
3679        assert!(sql.contains("VALUES (?, ?)"));
3680        assert_eq!(params.len(), 2);
3681        Ok(())
3682    }
3683
3684    #[test]
3685    fn test_insert_or_ignore_postgresql() -> Result<(), crate::DbError> {
3686        let dialect = get_dialect(DbType::PostgreSQL)?;
3687        let mut data = std::collections::HashMap::new();
3688        data.insert("name".to_string(), Value::String("Bob".into()));
3689
3690        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3691            .table("users")
3692            .insert_or_ignore()
3693            .build_insert_with_params(&data);
3694        assert!(sql.contains("INSERT OR IGNORE INTO \"users\""));
3695        assert!(sql.contains("(\"name\")"));
3696        assert!(sql.contains("VALUES (?)"));
3697        assert_eq!(params.len(), 1);
3698        assert_eq!(params[0], Value::String("Bob".into()));
3699        Ok(())
3700    }
3701
3702    #[test]
3703    fn test_insert_or_ignore_sqlite() -> Result<(), crate::DbError> {
3704        let dialect = get_dialect(DbType::Sqlite)?;
3705        let mut data = std::collections::HashMap::new();
3706        data.insert("name".to_string(), Value::String("Charlie".into()));
3707
3708        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3709            .table("users")
3710            .insert_or_ignore()
3711            .build_insert_with_params(&data);
3712        assert!(sql.contains("INSERT OR IGNORE INTO \"users\""));
3713        assert!(sql.contains("(\"name\")"));
3714        assert!(sql.contains("VALUES (?)"));
3715        assert_eq!(params.len(), 1);
3716        assert_eq!(params[0], Value::String("Charlie".into()));
3717        Ok(())
3718    }
3719
3720    #[test]
3721    fn test_insert_normal_without_ignore() -> Result<(), crate::DbError> {
3722        let dialect = get_dialect(DbType::MySQL)?;
3723        let mut data = std::collections::HashMap::new();
3724        data.insert("name".to_string(), Value::String("Dave".into()));
3725
3726        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3727            .table("users")
3728            .build_insert_with_params(&data);
3729        assert!(sql.contains("INSERT INTO `users`"));
3730        assert!(!sql.contains("IGNORE"), "普通插入不应包含 IGNORE");
3731        assert_eq!(params.len(), 1);
3732        assert_eq!(params[0], Value::String("Dave".into()));
3733        Ok(())
3734    }
3735
3736    #[test]
3737    fn test_insert_or_ignore_empty_data() -> Result<(), crate::DbError> {
3738        let dialect = get_dialect(DbType::MySQL)?;
3739        let data = std::collections::HashMap::new();
3740
3741        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3742            .table("users")
3743            .insert_or_ignore()
3744            .build_insert_with_params(&data);
3745        assert!(sql.is_empty(), "空数据应返回空 SQL");
3746        assert!(params.is_empty());
3747        Ok(())
3748    }
3749
3750    // ---- P2-3:方言支持性测试(TASK-029) ----
3751
3752    #[test]
3753    fn test_dialect_supports_lock_for_update() -> Result<(), crate::DbError> {
3754        let mysql = get_dialect(DbType::MySQL)?;
3755        let pg = get_dialect(DbType::PostgreSQL)?;
3756        let sqlite = get_dialect(DbType::Sqlite)?;
3757        let clickhouse = get_dialect(DbType::ClickHouse)?;
3758        let duckdb = get_dialect(DbType::DuckDB)?;
3759
3760        assert!(mysql.supports_lock_for_update(), "MySQL 应支持 FOR UPDATE");
3761        assert!(
3762            pg.supports_lock_for_update(),
3763            "PostgreSQL 应支持 FOR UPDATE"
3764        );
3765        assert!(
3766            !sqlite.supports_lock_for_update(),
3767            "SQLite 不应支持 FOR UPDATE"
3768        );
3769        assert!(
3770            !clickhouse.supports_lock_for_update(),
3771            "ClickHouse 是列式 OLAP,不应支持 FOR UPDATE"
3772        );
3773        assert!(
3774            !duckdb.supports_lock_for_update(),
3775            "DuckDB 不应支持 FOR UPDATE"
3776        );
3777        Ok(())
3778    }
3779
3780    #[test]
3781    fn test_dialect_supports_lock_shared() -> Result<(), crate::DbError> {
3782        let mysql = get_dialect(DbType::MySQL)?;
3783        let pg = get_dialect(DbType::PostgreSQL)?;
3784        let sqlite = get_dialect(DbType::Sqlite)?;
3785        let clickhouse = get_dialect(DbType::ClickHouse)?;
3786        let duckdb = get_dialect(DbType::DuckDB)?;
3787
3788        assert!(mysql.supports_lock_shared(), "MySQL 应支持共享锁");
3789        assert!(pg.supports_lock_shared(), "PostgreSQL 应支持共享锁");
3790        assert!(!sqlite.supports_lock_shared(), "SQLite 不应支持共享锁");
3791        assert!(
3792            !clickhouse.supports_lock_shared(),
3793            "ClickHouse 是列式 OLAP,不应支持共享锁"
3794        );
3795        assert!(!duckdb.supports_lock_shared(), "DuckDB 不应支持共享锁");
3796        Ok(())
3797    }
3798
3799    #[test]
3800    fn test_get_lock_type_and_is_insert_or_ignore() -> Result<(), crate::DbError> {
3801        let dialect = get_dialect(DbType::MySQL)?;
3802
3803        // 默认值
3804        let builder = QueryBuilder::<TestModel>::new(dialect);
3805        assert!(builder.get_lock_type().is_none(), "默认无锁");
3806        assert!(!builder.is_insert_or_ignore(), "默认不忽略插入");
3807
3808        // 设置锁后
3809        let builder = QueryBuilder::<TestModel>::new(get_dialect(DbType::MySQL)?)
3810            .table("users")
3811            .lock_for_update()?;
3812        assert_eq!(builder.get_lock_type(), Some(LockType::ForUpdate));
3813
3814        // 设置忽略插入后
3815        let builder = QueryBuilder::<TestModel>::new(get_dialect(DbType::MySQL)?)
3816            .table("users")
3817            .insert_or_ignore();
3818        assert!(builder.is_insert_or_ignore());
3819
3820        Ok(())
3821    }
3822}