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