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
2383/// QueryBuilder 方法 requiring ModelExt(v2.2.0 B-3 select_exclude)
2384impl<M: Model + crate::model::ModelExt> QueryBuilder<M> {
2385    /// 排除指定字段查询(v2.2.0 B-3,与 `select_only` 互补)
2386    ///
2387    /// 从实体全部列中减去排除列,设置 Partial 模式查询保留列。
2388    ///
2389    /// # 错误
2390    ///
2391    /// - 排除不存在的字段 → `Err(DbError::InvalidInput)`
2392    /// - 排除所有字段 → `Err(DbError::InvalidInput)`
2393    ///
2394    /// # 示例
2395    ///
2396    /// ```ignore
2397    /// let sql = User::find()
2398    ///     .select_exclude(&["avatar", "blob_data"])?
2399    ///     .build_select();
2400    /// // SELECT id, name, email FROM users(排除 avatar 和 blob_data)
2401    /// ```
2402    pub fn select_exclude(mut self, fields: &[&str]) -> Result<Self, crate::DbError> {
2403        let all_columns = M::columns();
2404        let exclude_set: std::collections::HashSet<&str> = fields.iter().copied().collect();
2405
2406        for field in fields {
2407            if !all_columns.contains(field) {
2408                return Err(crate::DbError::InvalidInput(format!(
2409                    "排除的字段不存在: {}",
2410                    field
2411                )));
2412            }
2413        }
2414
2415        let retained: Vec<String> = all_columns
2416            .into_iter()
2417            .filter(|c| !exclude_set.contains(*c))
2418            .map(|s| s.to_string())
2419            .collect();
2420
2421        if retained.is_empty() {
2422            return Err(crate::DbError::InvalidInput("不能排除所有字段".to_string()));
2423        }
2424
2425        self.select_mode = crate::partial_model::SelectMode::Partial;
2426        self.select_columns = retained;
2427        Ok(self)
2428    }
2429}
2430
2431impl<M: Model> fmt::Debug for QueryBuilder<M> {
2432    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2433        f.debug_struct("QueryBuilder")
2434            .field("table", &self.table)
2435            .field("select_columns", &self.select_columns)
2436            .field("where_conditions", &self.where_conditions.len())
2437            .field("limit", &self.limit_value)
2438            .finish()
2439    }
2440}
2441
2442#[cfg(test)]
2443#[allow(deprecated)]
2444mod tests {
2445    use super::*;
2446    use crate::db_type::DbType;
2447    use crate::dialect::get_dialect;
2448
2449    struct TestModel;
2450    impl Model for TestModel {
2451        type PrimaryKey = i64;
2452
2453        fn table_name() -> &'static str {
2454            "test_models"
2455        }
2456
2457        fn pk(&self) -> Self::PrimaryKey {
2458            1
2459        }
2460
2461        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2462    }
2463
2464    #[test]
2465    fn test_query_builder_select() -> Result<(), crate::DbError> {
2466        let dialect = get_dialect(DbType::MySQL)?;
2467        let builder = QueryBuilder::<TestModel>::new(dialect);
2468
2469        let sql = builder
2470            .table("users")
2471            .select(vec!["id", "name"])
2472            .build_select();
2473        assert!(sql.contains("SELECT id, name FROM"));
2474        assert!(sql.contains("`users`"));
2475        Ok(())
2476    }
2477
2478    #[test]
2479    fn test_query_builder_where() -> Result<(), crate::DbError> {
2480        let dialect = get_dialect(DbType::MySQL)?;
2481        let builder = QueryBuilder::<TestModel>::new(dialect);
2482
2483        let sql = builder
2484            .table("users")
2485            .where_eq("status", crate::value::Value::String("active".into()))
2486            .where_gt("age", crate::value::Value::I64(18))
2487            .build_select();
2488
2489        assert!(sql.contains("WHERE"));
2490        assert!(sql.contains("`status` = 'active'"));
2491        assert!(sql.contains("`age` > 18"));
2492        Ok(())
2493    }
2494
2495    #[test]
2496    fn test_query_builder_order_by() -> Result<(), crate::DbError> {
2497        let dialect = get_dialect(DbType::MySQL)?;
2498        let builder = QueryBuilder::<TestModel>::new(dialect);
2499
2500        let sql = builder
2501            .table("users")
2502            .order_by("created_at")
2503            .order_desc("id")
2504            .build_select();
2505
2506        assert!(sql.contains("ORDER BY"));
2507        assert!(sql.contains("`created_at` ASC"));
2508        assert!(sql.contains("`id` DESC"));
2509        Ok(())
2510    }
2511
2512    #[test]
2513    fn test_query_builder_limit_offset() -> Result<(), crate::DbError> {
2514        let dialect = get_dialect(DbType::MySQL)?;
2515        let builder = QueryBuilder::<TestModel>::new(dialect);
2516
2517        let sql = builder.table("users").limit(10).offset(20).build_select();
2518
2519        assert!(sql.contains("LIMIT 10"));
2520        assert!(sql.contains("OFFSET 20"));
2521        Ok(())
2522    }
2523
2524    #[test]
2525    fn test_query_builder_page() -> Result<(), crate::DbError> {
2526        let dialect = get_dialect(DbType::MySQL)?;
2527        let builder = QueryBuilder::<TestModel>::new(dialect);
2528
2529        let sql = builder.table("users").page(3, 20).build_select();
2530
2531        assert!(sql.contains("LIMIT 20"));
2532        assert!(sql.contains("OFFSET 40"));
2533        Ok(())
2534    }
2535
2536    #[test]
2537    fn test_query_builder_insert() -> Result<(), crate::DbError> {
2538        let dialect = get_dialect(DbType::MySQL)?;
2539        let builder = QueryBuilder::<TestModel>::new(dialect);
2540
2541        let mut data = std::collections::HashMap::new();
2542        data.insert("name".to_string(), Value::String("test".to_string()));
2543        data.insert("age".to_string(), Value::I64(25));
2544
2545        let sql = builder.table("users").build_insert(&data);
2546
2547        assert!(sql.contains("INSERT INTO"));
2548        assert!(sql.contains("`name`"));
2549        assert!(sql.contains("'test'"));
2550        Ok(())
2551    }
2552
2553    #[test]
2554    fn test_query_builder_update() -> Result<(), crate::DbError> {
2555        let dialect = get_dialect(DbType::MySQL)?;
2556        let builder = QueryBuilder::<TestModel>::new(dialect);
2557
2558        let mut data = std::collections::HashMap::new();
2559        data.insert("name".to_string(), Value::String("updated".to_string()));
2560
2561        let sql = builder
2562            .table("users")
2563            .where_eq("id", Value::I64(1))
2564            .build_update(&data);
2565
2566        assert!(sql.contains("UPDATE"));
2567        assert!(sql.contains("`name` = 'updated'"));
2568        assert!(sql.contains("WHERE"));
2569        Ok(())
2570    }
2571
2572    #[test]
2573    fn test_query_builder_delete() -> Result<(), crate::DbError> {
2574        let dialect = get_dialect(DbType::MySQL)?;
2575        let builder = QueryBuilder::<TestModel>::new(dialect);
2576
2577        let sql = builder
2578            .table("users")
2579            .where_eq("id", Value::I64(1))
2580            .build_delete();
2581
2582        assert!(sql.contains("DELETE FROM"));
2583        assert!(sql.contains("WHERE"));
2584        Ok(())
2585    }
2586
2587    #[test]
2588    fn test_query_builder_count() -> Result<(), crate::DbError> {
2589        let dialect = get_dialect(DbType::MySQL)?;
2590        let builder = QueryBuilder::<TestModel>::new(dialect);
2591
2592        let sql = builder.table("users").build_count();
2593
2594        assert!(sql.contains("SELECT COUNT(*)"));
2595        assert!(sql.contains("FROM"));
2596        Ok(())
2597    }
2598
2599    #[test]
2600    fn test_query_builder_where_in() -> Result<(), crate::DbError> {
2601        let dialect = get_dialect(DbType::MySQL)?;
2602        let builder = QueryBuilder::<TestModel>::new(dialect);
2603
2604        let sql = builder
2605            .table("users")
2606            .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
2607            .build_select();
2608
2609        assert!(sql.contains("IN ("));
2610        Ok(())
2611    }
2612
2613    #[test]
2614    fn test_query_builder_where_between() -> Result<(), crate::DbError> {
2615        let dialect = get_dialect(DbType::MySQL)?;
2616        let builder = QueryBuilder::<TestModel>::new(dialect);
2617
2618        let sql = builder
2619            .table("users")
2620            .where_between("age", Value::I64(18), Value::I64(30))
2621            .build_select();
2622
2623        assert!(sql.contains("BETWEEN"));
2624        Ok(())
2625    }
2626
2627    #[test]
2628    fn test_query_builder_where_null() -> Result<(), crate::DbError> {
2629        let dialect = get_dialect(DbType::MySQL)?;
2630        let builder = QueryBuilder::<TestModel>::new(dialect);
2631
2632        let sql = builder
2633            .table("users")
2634            .where_null("deleted_at")
2635            .build_select();
2636
2637        assert!(sql.contains("IS NULL"));
2638        Ok(())
2639    }
2640
2641    #[test]
2642    fn test_query_builder_join() -> Result<(), crate::DbError> {
2643        let dialect = get_dialect(DbType::MySQL)?;
2644        let builder = QueryBuilder::<TestModel>::new(dialect);
2645
2646        let sql = builder
2647            .table("users")
2648            .join_inner("posts", "users.id", "posts.user_id")
2649            .build_select();
2650
2651        assert!(sql.contains("INNER JOIN"));
2652        assert!(sql.contains("`posts`"));
2653        Ok(())
2654    }
2655
2656    #[test]
2657    fn test_query_builder_group_by() -> Result<(), crate::DbError> {
2658        let dialect = get_dialect(DbType::MySQL)?;
2659        let builder = QueryBuilder::<TestModel>::new(dialect);
2660
2661        let sql = builder.table("users").group_by("status").build_select();
2662
2663        assert!(sql.contains("GROUP BY"));
2664        assert!(sql.contains("`status`"));
2665        Ok(())
2666    }
2667
2668    #[test]
2669    fn test_query_builder_max() -> Result<(), crate::DbError> {
2670        let dialect = get_dialect(DbType::MySQL)?;
2671        let builder = QueryBuilder::<TestModel>::new(dialect);
2672
2673        let sql = builder.table("users").build_max("score");
2674
2675        assert!(sql.contains("MAX("));
2676        assert!(sql.contains("`score`"));
2677        Ok(())
2678    }
2679
2680    #[test]
2681    fn test_query_builder_min() -> Result<(), crate::DbError> {
2682        let dialect = get_dialect(DbType::MySQL)?;
2683        let builder = QueryBuilder::<TestModel>::new(dialect);
2684
2685        let sql = builder.table("users").build_min("price");
2686
2687        assert!(sql.contains("MIN("));
2688        assert!(sql.contains("`price`"));
2689        Ok(())
2690    }
2691
2692    #[test]
2693    fn test_query_builder_sum() -> Result<(), crate::DbError> {
2694        let dialect = get_dialect(DbType::MySQL)?;
2695        let builder = QueryBuilder::<TestModel>::new(dialect);
2696
2697        let sql = builder.table("orders").build_sum("amount");
2698
2699        assert!(sql.contains("SUM("));
2700        assert!(sql.contains("`amount`"));
2701        Ok(())
2702    }
2703
2704    #[test]
2705    fn test_query_builder_avg() -> Result<(), crate::DbError> {
2706        let dialect = get_dialect(DbType::MySQL)?;
2707        let builder = QueryBuilder::<TestModel>::new(dialect);
2708
2709        let sql = builder.table("scores").build_avg("value");
2710
2711        assert!(sql.contains("AVG("));
2712        assert!(sql.contains("`value`"));
2713        Ok(())
2714    }
2715
2716    #[test]
2717    fn test_validator_select() -> Result<(), crate::DbError> {
2718        let dialect = get_dialect(DbType::MySQL)?;
2719        let builder = QueryBuilder::<TestModel>::new(dialect);
2720
2721        let result = builder.table("users").select(vec!["id", "name"]).validate();
2722        assert!(result.is_ok());
2723        Ok(())
2724    }
2725
2726    #[test]
2727    fn test_validator_select_with_join() -> Result<(), crate::DbError> {
2728        let dialect = get_dialect(DbType::MySQL)?;
2729        let builder = QueryBuilder::<TestModel>::new(dialect);
2730
2731        let result = builder
2732            .table("users")
2733            .join_inner("posts", "users.id", "posts.user_id")
2734            .validate();
2735        assert!(result.is_ok());
2736        Ok(())
2737    }
2738
2739    #[test]
2740    fn test_validator_insert() -> Result<(), crate::DbError> {
2741        let dialect = get_dialect(DbType::MySQL)?;
2742        let builder = QueryBuilder::<TestModel>::new(dialect);
2743
2744        let mut data = std::collections::HashMap::new();
2745        data.insert("name".to_string(), Value::String("test".to_string()));
2746
2747        let result = builder.table("users").validate_insert(&data);
2748        assert!(result.is_ok());
2749        Ok(())
2750    }
2751
2752    #[test]
2753    fn test_validator_insert_empty_data() -> Result<(), crate::DbError> {
2754        let dialect = get_dialect(DbType::MySQL)?;
2755        let builder = QueryBuilder::<TestModel>::new(dialect);
2756
2757        let data = std::collections::HashMap::new();
2758        let result = builder.table("users").validate_insert(&data);
2759        assert!(result.is_err());
2760        Ok(())
2761    }
2762
2763    #[test]
2764    fn test_validator_update() -> Result<(), crate::DbError> {
2765        let dialect = get_dialect(DbType::MySQL)?;
2766        let builder = QueryBuilder::<TestModel>::new(dialect);
2767
2768        let mut data = std::collections::HashMap::new();
2769        data.insert("name".to_string(), Value::String("updated".to_string()));
2770
2771        let result = builder.table("users").validate_update(&data);
2772        assert!(result.is_ok());
2773        Ok(())
2774    }
2775
2776    #[test]
2777    fn test_validator_update_empty_data() -> Result<(), crate::DbError> {
2778        let dialect = get_dialect(DbType::MySQL)?;
2779        let builder = QueryBuilder::<TestModel>::new(dialect);
2780
2781        let data = std::collections::HashMap::new();
2782        let result = builder.table("users").validate_update(&data);
2783        assert!(result.is_err());
2784        Ok(())
2785    }
2786
2787    #[test]
2788    fn test_validator_delete() -> Result<(), crate::DbError> {
2789        let dialect = get_dialect(DbType::MySQL)?;
2790        let builder = QueryBuilder::<TestModel>::new(dialect);
2791
2792        let result = builder
2793            .table("users")
2794            .where_eq("id", Value::I64(1))
2795            .validate_delete();
2796        assert!(result.is_ok());
2797        Ok(())
2798    }
2799
2800    #[test]
2801    fn test_validator_delete_no_where() -> Result<(), crate::DbError> {
2802        let dialect = get_dialect(DbType::MySQL)?;
2803        let builder = QueryBuilder::<TestModel>::new(dialect);
2804
2805        // DELETE without WHERE still produces valid SQL (just no filter)
2806        let result = builder.table("users").validate_delete();
2807        assert!(result.is_ok());
2808        Ok(())
2809    }
2810
2811    // ==================== M-3 select_quoted 测试 ====================
2812
2813    #[test]
2814    fn test_m3_select_quoted_valid_columns() -> Result<(), crate::DbError> {
2815        let dialect = get_dialect(DbType::MySQL)?;
2816        let builder = QueryBuilder::<TestModel>::new(dialect);
2817        let builder = builder.table("users").select_quoted(vec!["id", "name"])?;
2818        let sql = builder.build_select();
2819        // 应自动 quote 列名
2820        assert!(sql.contains("SELECT `id`, `name` FROM"));
2821        assert!(sql.contains("`users`"));
2822        Ok(())
2823    }
2824
2825    #[test]
2826    fn test_m3_select_quoted_rejects_sql_injection() -> Result<(), crate::DbError> {
2827        let dialect = get_dialect(DbType::MySQL)?;
2828        let builder = QueryBuilder::<TestModel>::new(dialect);
2829
2830        // SQL 注入尝试:分号 + DROP TABLE
2831        let result = builder
2832            .table("users")
2833            .select_quoted(vec!["id; DROP TABLE users"]);
2834        assert!(result.is_err());
2835
2836        // 含引号
2837        let dialect = get_dialect(DbType::MySQL)?;
2838        let builder = QueryBuilder::<TestModel>::new(dialect);
2839        let result = builder.table("users").select_quoted(vec!["name'"]);
2840        assert!(result.is_err());
2841
2842        // 数字开头
2843        let dialect = get_dialect(DbType::MySQL)?;
2844        let builder = QueryBuilder::<TestModel>::new(dialect);
2845        let result = builder.table("users").select_quoted(vec!["1col"]);
2846        assert!(result.is_err());
2847
2848        // 含空格
2849        let dialect = get_dialect(DbType::MySQL)?;
2850        let builder = QueryBuilder::<TestModel>::new(dialect);
2851        let result = builder.table("users").select_quoted(vec!["col name"]);
2852        assert!(result.is_err());
2853        Ok(())
2854    }
2855
2856    #[test]
2857    fn test_m3_select_quoted_postgresql_dialect() -> Result<(), crate::DbError> {
2858        let dialect = get_dialect(DbType::PostgreSQL)?;
2859        let builder = QueryBuilder::<TestModel>::new(dialect);
2860        let builder = builder.table("users").select_quoted(vec!["id", "name"])?;
2861        let sql = builder.build_select();
2862        // PostgreSQL 使用双引号
2863        assert!(sql.contains("SELECT \"id\", \"name\" FROM"));
2864        assert!(sql.contains("\"users\""));
2865        Ok(())
2866    }
2867
2868    // ==================== P0-1 软删除集成行为测试 ====================
2869
2870    /// 软删除测试模型:实现 soft_delete_field() 返回 "deleted_at"
2871    struct SoftDeleteModel;
2872    impl Model for SoftDeleteModel {
2873        type PrimaryKey = i64;
2874
2875        fn table_name() -> &'static str {
2876            "soft_users"
2877        }
2878
2879        fn pk(&self) -> Self::PrimaryKey {
2880            1
2881        }
2882
2883        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2884
2885        fn soft_delete_field() -> Option<&'static str> {
2886            Some("deleted_at")
2887        }
2888    }
2889
2890    /// 行为级测试 L3-1:软删除模型 build_select 自动追加 `WHERE deleted_at IS NULL`
2891    ///
2892    /// 用户视角:查询软删除模型时,自动过滤已删除记录,无需手动写条件。
2893    #[test]
2894    fn test_p01_soft_delete_select_auto_filter() -> Result<(), crate::DbError> {
2895        let dialect = get_dialect(DbType::MySQL)?;
2896        let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
2897        let sql = builder.table("soft_users").build_select();
2898        // 必须自动追加软删除过滤
2899        assert!(
2900            sql.contains("`deleted_at` IS NULL"),
2901            "软删除模型 SELECT 必须自动追加 `deleted_at` IS NULL,实际: {}",
2902            sql
2903        );
2904        Ok(())
2905    }
2906
2907    /// 行为级测试 L3-2:软删除模型 + 用户 WHERE 条件,软删除条件以 AND 追加
2908    #[test]
2909    fn test_p01_soft_delete_select_with_user_where() -> Result<(), crate::DbError> {
2910        let dialect = get_dialect(DbType::MySQL)?;
2911        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2912            .table("soft_users")
2913            .where_eq("status", Value::String("active".into()))
2914            .build_select();
2915        // 用户条件 + 软删除条件 同时存在
2916        assert!(sql.contains("`status` = "), "用户条件应保留: {}", sql);
2917        assert!(
2918            sql.contains("`deleted_at` IS NULL"),
2919            "软删除条件应自动追加: {}",
2920            sql
2921        );
2922        Ok(())
2923    }
2924
2925    /// 行为级测试 L3-3:without_soft_delete() 临时禁用软删除过滤
2926    ///
2927    /// 用户视角:管理员查询已删除记录时,可禁用自动过滤。
2928    #[test]
2929    fn test_p01_soft_delete_without_soft_delete() -> Result<(), crate::DbError> {
2930        let dialect = get_dialect(DbType::MySQL)?;
2931        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2932            .table("soft_users")
2933            .without_soft_delete()
2934            .build_select();
2935        // 不应包含软删除过滤
2936        assert!(
2937            !sql.contains("`deleted_at` IS NULL"),
2938            "without_soft_delete 应禁用过滤,实际: {}",
2939            sql
2940        );
2941        // 也应无 WHERE 子句(因为用户未提供任何条件)
2942        assert!(
2943            !sql.contains("WHERE"),
2944            "无用户条件 + 禁用软删除应无 WHERE 子句: {}",
2945            sql
2946        );
2947        Ok(())
2948    }
2949
2950    /// 行为级测试 L3-4:软删除模型 build_delete 自动转为 UPDATE
2951    ///
2952    /// 用户视角:调用 delete 实际是软删除 UPDATE,不是物理 DELETE。
2953    #[test]
2954    fn test_p01_soft_delete_delete_becomes_update() -> Result<(), crate::DbError> {
2955        let dialect = get_dialect(DbType::MySQL)?;
2956        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2957            .table("soft_users")
2958            .where_eq("id", Value::I64(42))
2959            .build_delete();
2960        // 应生成 UPDATE 而非 DELETE
2961        assert!(
2962            sql.starts_with("UPDATE"),
2963            "软删除模型的 build_delete 应生成 UPDATE,实际: {}",
2964            sql
2965        );
2966        assert!(
2967            !sql.contains("DELETE FROM"),
2968            "不应生成 DELETE FROM: {}",
2969            sql
2970        );
2971        assert!(
2972            sql.contains("`deleted_at` = NOW()"),
2973            "应设置 deleted_at = NOW(): {}",
2974            sql
2975        );
2976        // 软删除条件应自动追加,防止更新已删除记录
2977        assert!(
2978            sql.contains("`deleted_at` IS NULL"),
2979            "软删除 UPDATE 应追加 deleted_at IS NULL 防止重复删除: {}",
2980            sql
2981        );
2982        Ok(())
2983    }
2984
2985    /// 行为级测试 L3-5:build_force_delete 物理删除,不追加软删除过滤
2986    ///
2987    /// 用户视角:管理员强制清除时使用 build_force_delete。
2988    #[test]
2989    fn test_p01_soft_delete_force_delete() -> Result<(), crate::DbError> {
2990        let dialect = get_dialect(DbType::MySQL)?;
2991        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2992            .table("soft_users")
2993            .where_eq("id", Value::I64(99))
2994            .build_force_delete();
2995        // 应生成 DELETE FROM
2996        assert!(
2997            sql.starts_with("DELETE FROM"),
2998            "build_force_delete 应生成 DELETE FROM,实际: {}",
2999            sql
3000        );
3001        // 不应追加软删除过滤
3002        assert!(
3003            !sql.contains("`deleted_at` IS NULL"),
3004            "物理删除不应追加软删除过滤: {}",
3005            sql
3006        );
3007        Ok(())
3008    }
3009
3010    /// 行为级测试 L3-6:build_select_with_params 自动追加软删除条件(参数化版本)
3011    #[test]
3012    fn test_p01_soft_delete_select_with_params() -> Result<(), crate::DbError> {
3013        let dialect = get_dialect(DbType::MySQL)?;
3014        let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
3015            .table("soft_users")
3016            .where_eq("id", Value::I64(1))
3017            .build_select_with_params();
3018        assert!(
3019            sql.contains("`deleted_at` IS NULL"),
3020            "参数化版本也应自动追加软删除: {}",
3021            sql
3022        );
3023        assert_eq!(params.len(), 1, "参数应为 1 个(用户 where_eq 的值)");
3024        assert_eq!(params[0], Value::I64(1));
3025        Ok(())
3026    }
3027
3028    /// 行为级测试 L3-7:build_delete_with_params 自动转为 UPDATE
3029    #[test]
3030    fn test_p01_soft_delete_delete_with_params_becomes_update() -> Result<(), crate::DbError> {
3031        let dialect = get_dialect(DbType::MySQL)?;
3032        let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
3033            .table("soft_users")
3034            .where_eq("id", Value::I64(7))
3035            .build_delete_with_params();
3036        assert!(sql.starts_with("UPDATE"), "应生成 UPDATE: {}", sql);
3037        assert!(
3038            sql.contains("`deleted_at` = NOW()"),
3039            "应设置 NOW(): {}",
3040            sql
3041        );
3042        assert_eq!(params.len(), 1, "参数应为 1 个(WHERE 的值)");
3043        Ok(())
3044    }
3045
3046    /// 行为级测试 L3-8:build_force_delete_with_params 物理删除(参数化版本)
3047    #[test]
3048    fn test_p01_soft_delete_force_delete_with_params() -> Result<(), crate::DbError> {
3049        let dialect = get_dialect(DbType::MySQL)?;
3050        let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
3051            .table("soft_users")
3052            .where_eq("id", Value::I64(11))
3053            .build_force_delete_with_params();
3054        assert!(sql.starts_with("DELETE FROM"), "应生成 DELETE: {}", sql);
3055        assert!(
3056            !sql.contains("`deleted_at` IS NULL"),
3057            "不应追加软删除过滤: {}",
3058            sql
3059        );
3060        assert_eq!(params.len(), 1);
3061        Ok(())
3062    }
3063
3064    /// 行为级测试 L3-9:非软删除模型 TestModel 不追加软删除条件
3065    ///
3066    /// 用户视角:未启用软删除的模型行为不变。
3067    #[test]
3068    fn test_p01_non_soft_delete_model_unchanged() -> Result<(), crate::DbError> {
3069        let dialect = get_dialect(DbType::MySQL)?;
3070        let sql = QueryBuilder::<TestModel>::new(dialect)
3071            .table("users")
3072            .where_eq("id", Value::I64(1))
3073            .build_select();
3074        assert!(
3075            !sql.contains("deleted_at"),
3076            "非软删除模型不应追加 deleted_at: {}",
3077            sql
3078        );
3079        // build_delete 仍生成 DELETE FROM
3080        let dialect = get_dialect(DbType::MySQL)?;
3081        let del_sql = QueryBuilder::<TestModel>::new(dialect)
3082            .table("users")
3083            .where_eq("id", Value::I64(1))
3084            .build_delete();
3085        assert!(
3086            del_sql.starts_with("DELETE FROM"),
3087            "非软删除模型 build_delete 应生成 DELETE: {}",
3088            del_sql
3089        );
3090        Ok(())
3091    }
3092
3093    /// 行为级测试 L3-10:build_count 也应自动追加软删除条件
3094    #[test]
3095    fn test_p01_soft_delete_count_auto_filter() -> Result<(), crate::DbError> {
3096        let dialect = get_dialect(DbType::MySQL)?;
3097        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
3098            .table("soft_users")
3099            .build_count();
3100        assert!(
3101            sql.contains("`deleted_at` IS NULL"),
3102            "build_count 也应追加软删除过滤: {}",
3103            sql
3104        );
3105        Ok(())
3106    }
3107
3108    // ==================== P0-2 参数化查询注入防护测试 ====================
3109
3110    /// 行为级测试 L3-11:where_eq 使用 `?` 占位符,值收集到 params
3111    ///
3112    /// 用户视角:参数化查询杜绝 SQL 注入。
3113    #[test]
3114    fn test_p02_where_eq_uses_placeholder() -> Result<(), crate::DbError> {
3115        let dialect = get_dialect(DbType::MySQL)?;
3116        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3117            .table("users")
3118            .where_eq("name", Value::String("alice".into()))
3119            .build_select_with_params();
3120        // SQL 中应含 `?` 占位符,不应内嵌值
3121        assert!(sql.contains("`name` = ?"), "应使用 ? 占位符: {}", sql);
3122        assert!(!sql.contains("'alice'"), "不应内嵌值到 SQL: {}", sql);
3123        assert_eq!(params.len(), 1);
3124        assert_eq!(params[0], Value::String("alice".into()));
3125        Ok(())
3126    }
3127
3128    /// 行为级测试 L3-12:where_like 使用 `?` 占位符
3129    #[test]
3130    fn test_p02_where_like_uses_placeholder() -> Result<(), crate::DbError> {
3131        let dialect = get_dialect(DbType::MySQL)?;
3132        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3133            .table("users")
3134            .where_like("name", Value::String("%alice%".into()))
3135            .build_select_with_params();
3136        assert!(sql.contains("`name` LIKE ?"), "应使用 LIKE ?: {}", sql);
3137        assert!(!sql.contains("%alice%"), "不应内嵌 pattern: {}", sql);
3138        assert_eq!(params.len(), 1);
3139        Ok(())
3140    }
3141
3142    /// 行为级测试 L3-12a:where_ne 使用 `?` 占位符
3143    ///
3144    /// 验证 P0-2 参数化 API where_ne 生成 `field != ?` 且值不内嵌。
3145    #[test]
3146    fn test_p02_where_ne_uses_placeholder() -> Result<(), crate::DbError> {
3147        let dialect = get_dialect(DbType::MySQL)?;
3148        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3149            .table("users")
3150            .where_ne("status", Value::I64(0))
3151            .build_select_with_params();
3152        assert!(sql.contains("`status` != ?"), "应使用 != ?: {}", sql);
3153        assert!(!sql.contains("!= 0"), "不应内嵌值: {}", sql);
3154        assert_eq!(params.len(), 1);
3155        assert_eq!(params[0], Value::I64(0));
3156        Ok(())
3157    }
3158
3159    /// 行为级测试 L3-12b:where_ge 使用 `?` 占位符
3160    ///
3161    /// 验证 P0-2 参数化 API where_ge 生成 `field >= ?` 且值不内嵌。
3162    #[test]
3163    fn test_p02_where_ge_uses_placeholder() -> Result<(), crate::DbError> {
3164        let dialect = get_dialect(DbType::MySQL)?;
3165        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3166            .table("users")
3167            .where_ge("age", Value::I64(18))
3168            .build_select_with_params();
3169        assert!(sql.contains("`age` >= ?"), "应使用 >= ?: {}", sql);
3170        assert!(!sql.contains(">= 18"), "不应内嵌值: {}", sql);
3171        assert_eq!(params.len(), 1);
3172        assert_eq!(params[0], Value::I64(18));
3173        Ok(())
3174    }
3175
3176    /// 行为级测试 L3-12c:where_lt 使用 `?` 占位符
3177    ///
3178    /// 验证 P0-2 参数化 API where_lt 生成 `field < ?` 且值不内嵌。
3179    #[test]
3180    fn test_p02_where_lt_uses_placeholder() -> Result<(), crate::DbError> {
3181        let dialect = get_dialect(DbType::MySQL)?;
3182        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3183            .table("users")
3184            .where_lt("score", Value::F64(60.0))
3185            .build_select_with_params();
3186        assert!(sql.contains("`score` < ?"), "应使用 < ?: {}", sql);
3187        assert!(!sql.contains("< 60"), "不应内嵌值: {}", sql);
3188        assert_eq!(params.len(), 1);
3189        assert_eq!(params[0], Value::F64(60.0));
3190        Ok(())
3191    }
3192
3193    /// 行为级测试 L3-13:注入攻击防护 - 值含 SQL 关键字也不会被解释执行
3194    ///
3195    /// 用户视角:即使用户输入 `'; DROP TABLE users; --`,也不会造成注入。
3196    #[test]
3197    fn test_p02_injection_protection_drop_table() -> Result<(), crate::DbError> {
3198        let dialect = get_dialect(DbType::MySQL)?;
3199        let evil_input = "'; DROP TABLE users; --".to_string();
3200        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3201            .table("users")
3202            .where_eq("name", Value::String(evil_input.clone()))
3203            .build_select_with_params();
3204        // SQL 中不应出现 DROP TABLE
3205        assert!(!sql.contains("DROP TABLE"), "SQL 注入未防护: {}", sql);
3206        // 整个恶意字符串应作为单一参数传递
3207        assert_eq!(params.len(), 1);
3208        assert_eq!(params[0], Value::String(evil_input));
3209        // SQL 中只有 1 个 `?`
3210        assert_eq!(sql.matches('?').count(), 1);
3211        Ok(())
3212    }
3213
3214    /// 行为级测试 L3-14:注入攻击防护 - OR 1=1 经典攻击
3215    #[test]
3216    fn test_p02_injection_protection_or_one_equals_one() -> Result<(), crate::DbError> {
3217        let dialect = get_dialect(DbType::MySQL)?;
3218        let evil = "' OR '1'='1".to_string();
3219        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3220            .table("users")
3221            .where_eq("name", Value::String(evil.clone()))
3222            .build_select_with_params();
3223        assert!(!sql.contains("OR '1'='1'"), "OR 1=1 注入未防护: {}", sql);
3224        assert_eq!(params.len(), 1);
3225        assert_eq!(params[0], Value::String(evil));
3226        Ok(())
3227    }
3228
3229    /// 行为级测试 L3-15:多参数顺序正确(WHERE a = ? AND b = ?)
3230    #[test]
3231    fn test_p02_multiple_params_order() -> Result<(), crate::DbError> {
3232        let dialect = get_dialect(DbType::MySQL)?;
3233        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3234            .table("users")
3235            .where_eq("name", Value::String("alice".into()))
3236            .where_gt("age", Value::I64(18))
3237            .where_le("score", Value::F64(99.5))
3238            .build_select_with_params();
3239        assert_eq!(sql.matches('?').count(), 3, "应有 3 个占位符: {}", sql);
3240        assert_eq!(params.len(), 3);
3241        // 参数顺序应与 WHERE 子句出现顺序一致
3242        assert_eq!(params[0], Value::String("alice".into()));
3243        assert_eq!(params[1], Value::I64(18));
3244        assert_eq!(params[2], Value::F64(99.5));
3245        Ok(())
3246    }
3247
3248    /// 行为级测试 L3-16:where_in 参数化
3249    #[test]
3250    fn test_p02_where_in_uses_placeholders() -> Result<(), crate::DbError> {
3251        let dialect = get_dialect(DbType::MySQL)?;
3252        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3253            .table("users")
3254            .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
3255            .build_select_with_params();
3256        assert!(
3257            sql.contains("`id` IN (?, ?, ?)"),
3258            "应使用 3 个占位符: {}",
3259            sql
3260        );
3261        assert_eq!(params.len(), 3);
3262        Ok(())
3263    }
3264
3265    /// 行为级测试 L3-17:where_between 参数化
3266    #[test]
3267    fn test_p02_where_between_uses_placeholders() -> Result<(), crate::DbError> {
3268        let dialect = get_dialect(DbType::MySQL)?;
3269        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3270            .table("users")
3271            .where_between("age", Value::I64(18), Value::I64(65))
3272            .build_select_with_params();
3273        assert!(
3274            sql.contains("`age` BETWEEN ? AND ?"),
3275            "应使用 2 个占位符: {}",
3276            sql
3277        );
3278        assert_eq!(params.len(), 2);
3279        assert_eq!(params[0], Value::I64(18));
3280        assert_eq!(params[1], Value::I64(65));
3281        Ok(())
3282    }
3283
3284    /// 行为级测试 L3-18:UPDATE 参数化版本 - SET 参数在前,WHERE 参数在后
3285    #[test]
3286    fn test_p02_update_params_order_set_before_where() -> Result<(), crate::DbError> {
3287        let dialect = get_dialect(DbType::MySQL)?;
3288        let mut data = std::collections::HashMap::new();
3289        data.insert("name".to_string(), Value::String("bob".into()));
3290        data.insert("age".to_string(), Value::I64(30));
3291        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3292            .table("users")
3293            .where_eq("id", Value::I64(99))
3294            .build_update_with_params(&data);
3295        // SET 子句应有 2 个占位符,WHERE 子句 1 个,共 3 个
3296        assert_eq!(sql.matches('?').count(), 3, "应有 3 个 ?: {}", sql);
3297        assert_eq!(params.len(), 3);
3298        // 前 2 个为 SET 参数,最后 1 个为 WHERE 参数
3299        // 注意:HashMap 迭代顺序未指定,仅校验 WHERE 参数在最后
3300        assert_eq!(params[2], Value::I64(99));
3301        Ok(())
3302    }
3303
3304    /// 行为级测试 L3-19:build_where_clause(无参数版本)参数化条件内嵌值
3305    ///
3306    /// 验证无参数版本(build_select)对参数化条件的处理:直接内嵌转义值。
3307    #[test]
3308    fn test_p02_build_where_clause_inlines_value() -> Result<(), crate::DbError> {
3309        let dialect = get_dialect(DbType::MySQL)?;
3310        let sql = QueryBuilder::<TestModel>::new(dialect)
3311            .table("users")
3312            .where_eq("name", Value::String("alice".into()))
3313            .build_select();
3314        // 无参数版本应内嵌值(依赖 to_param_with_dialect 转义)
3315        assert!(
3316            sql.contains("`name` = "),
3317            "无参数版本应含 WHERE 条件: {}",
3318            sql
3319        );
3320        // 不应含 `?`(无参数版本)
3321        assert!(
3322            !sql.contains("`name` = ?"),
3323            "无参数版本不应使用 ? 占位符: {}",
3324            sql
3325        );
3326        Ok(())
3327    }
3328
3329    /// 行为级测试 L3-20:is_soft_delete_disabled 反映状态
3330    #[test]
3331    fn test_p01_is_soft_delete_disabled_flag() -> Result<(), crate::DbError> {
3332        let dialect = get_dialect(DbType::MySQL)?;
3333        let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
3334        assert!(!builder.is_soft_delete_disabled(), "默认应启用软删除过滤");
3335        let builder =
3336            QueryBuilder::<SoftDeleteModel>::new(get_dialect(DbType::MySQL)?).without_soft_delete();
3337        assert!(
3338            builder.is_soft_delete_disabled(),
3339            "without_soft_delete 后应反映禁用状态"
3340        );
3341        Ok(())
3342    }
3343
3344    // ==================== P0-3 多租户自动过滤行为测试 ====================
3345
3346    /// 多租户测试模型:实现 tenant_field() 返回 "tenant_id"
3347    struct TenantModel;
3348    impl Model for TenantModel {
3349        type PrimaryKey = i64;
3350
3351        fn table_name() -> &'static str {
3352            "orders"
3353        }
3354
3355        fn pk(&self) -> Self::PrimaryKey {
3356            1
3357        }
3358
3359        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
3360
3361        fn tenant_field() -> Option<&'static str> {
3362            Some("tenant_id")
3363        }
3364    }
3365
3366    /// 同时实现软删除 + 多租户的模型
3367    struct SoftDeleteAndTenantModel;
3368    impl Model for SoftDeleteAndTenantModel {
3369        type PrimaryKey = i64;
3370
3371        fn table_name() -> &'static str {
3372            "documents"
3373        }
3374
3375        fn pk(&self) -> Self::PrimaryKey {
3376            1
3377        }
3378
3379        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
3380
3381        fn soft_delete_field() -> Option<&'static str> {
3382            Some("deleted_at")
3383        }
3384
3385        fn tenant_field() -> Option<&'static str> {
3386            Some("tenant_id")
3387        }
3388    }
3389
3390    /// 行为级测试 L3-21:多租户模型 + with_tenant_id 自动追加 WHERE tenant_id = ?
3391    ///
3392    /// 用户视角:设置租户 ID 后,查询自动过滤当前租户数据。
3393    #[test]
3394    fn test_p03_tenant_select_auto_filter() -> Result<(), crate::DbError> {
3395        let dialect = get_dialect(DbType::MySQL)?;
3396        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3397            .table("orders")
3398            .with_tenant_id(42)
3399            .build_select_with_params();
3400        assert!(
3401            sql.contains("`tenant_id` = ?"),
3402            "多租户模型应自动追加 tenant_id = ?: {}",
3403            sql
3404        );
3405        assert_eq!(params.len(), 1, "应有 1 个参数(tenant_id 值)");
3406        assert_eq!(params[0], Value::I64(42));
3407        Ok(())
3408    }
3409
3410    /// 行为级测试 L3-22:多租户模型 + 用户 WHERE 条件 + 租户条件
3411    #[test]
3412    fn test_p03_tenant_select_with_user_where() -> Result<(), crate::DbError> {
3413        let dialect = get_dialect(DbType::MySQL)?;
3414        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3415            .table("orders")
3416            .with_tenant_id(7)
3417            .where_eq("status", Value::String("active".into()))
3418            .build_select_with_params();
3419        assert!(sql.contains("`status` = ?"), "用户条件应保留: {}", sql);
3420        assert!(
3421            sql.contains("`tenant_id` = ?"),
3422            "租户条件应自动追加: {}",
3423            sql
3424        );
3425        assert_eq!(params.len(), 2, "应有 2 个参数");
3426        // 第 1 个为用户 where_eq 的值,第 2 个为 tenant_id
3427        assert_eq!(params[0], Value::String("active".into()));
3428        assert_eq!(params[1], Value::I64(7));
3429        Ok(())
3430    }
3431
3432    /// 行为级测试 L3-23:without_tenant() 临时禁用租户过滤
3433    ///
3434    /// 用户视角:管理员跨租户查询时禁用自动过滤。
3435    #[test]
3436    fn test_p03_tenant_without_tenant() -> Result<(), crate::DbError> {
3437        let dialect = get_dialect(DbType::MySQL)?;
3438        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3439            .table("orders")
3440            .with_tenant_id(42)
3441            .without_tenant()
3442            .build_select_with_params();
3443        assert!(
3444            !sql.contains("`tenant_id` = ?"),
3445            "without_tenant 应禁用过滤: {}",
3446            sql
3447        );
3448        assert_eq!(params.len(), 0, "不应有租户参数");
3449        Ok(())
3450    }
3451
3452    /// 行为级测试 L3-24:多租户模型 build_delete 自动追加租户条件
3453    ///
3454    /// 用户视角:删除操作自动限定在当前租户,防止跨租户删除。
3455    #[test]
3456    fn test_p03_tenant_delete_auto_filter() -> Result<(), crate::DbError> {
3457        let dialect = get_dialect(DbType::MySQL)?;
3458        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3459            .table("orders")
3460            .with_tenant_id(99)
3461            .where_eq("id", Value::I64(1))
3462            .build_delete_with_params();
3463        assert!(
3464            sql.contains("`tenant_id` = ?"),
3465            "删除应自动追加租户条件: {}",
3466            sql
3467        );
3468        // 2 个参数:where_eq(id=1) + tenant_id=99
3469        assert_eq!(params.len(), 2);
3470        assert_eq!(params[0], Value::I64(1));
3471        assert_eq!(params[1], Value::I64(99));
3472        Ok(())
3473    }
3474
3475    /// 行为级测试 L3-25:多租户模型 build_update 自动追加租户条件
3476    #[test]
3477    fn test_p03_tenant_update_auto_filter() -> Result<(), crate::DbError> {
3478        let dialect = get_dialect(DbType::MySQL)?;
3479        let mut data = std::collections::HashMap::new();
3480        data.insert("status".to_string(), Value::String("shipped".into()));
3481        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3482            .table("orders")
3483            .with_tenant_id(5)
3484            .where_eq("id", Value::I64(10))
3485            .build_update_with_params(&data);
3486        assert!(
3487            sql.contains("`tenant_id` = ?"),
3488            "更新应自动追加租户条件: {}",
3489            sql
3490        );
3491        // 3 个参数:SET status + WHERE id + tenant_id
3492        assert_eq!(params.len(), 3);
3493        // 最后一个应为 tenant_id
3494        assert_eq!(params[2], Value::I64(5));
3495        Ok(())
3496    }
3497
3498    /// 行为级测试 L3-26:多租户模型 build_count 自动追加租户条件
3499    #[test]
3500    fn test_p03_tenant_count_auto_filter() -> Result<(), crate::DbError> {
3501        let dialect = get_dialect(DbType::MySQL)?;
3502        let sql = QueryBuilder::<TenantModel>::new(dialect)
3503            .table("orders")
3504            .with_tenant_id(42)
3505            .build_count();
3506        assert!(
3507            sql.contains("`tenant_id` = 42"),
3508            "build_count 应追加租户条件(无参数版本内嵌值): {}",
3509            sql
3510        );
3511        Ok(())
3512    }
3513
3514    /// 行为级测试 L3-27:非多租户模型 TestModel 不追加租户条件
3515    ///
3516    /// 用户视角:未启用多租户的模型行为不变。
3517    #[test]
3518    fn test_p03_non_tenant_model_unchanged() -> Result<(), crate::DbError> {
3519        let dialect = get_dialect(DbType::MySQL)?;
3520        // 即使设置了 with_tenant_id,非多租户模型也不应追加
3521        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3522            .table("users")
3523            .with_tenant_id(42)
3524            .build_select_with_params();
3525        assert!(
3526            !sql.contains("tenant_id"),
3527            "非多租户模型不应追加 tenant_id: {}",
3528            sql
3529        );
3530        assert_eq!(params.len(), 0);
3531        Ok(())
3532    }
3533
3534    /// 行为级测试 L3-28:多租户模型未设置 tenant_id 时不追加条件
3535    ///
3536    /// 用户视角:未设置租户 ID 时,查询不追加租户过滤(允许跨租户,需调用方保证安全)。
3537    #[test]
3538    fn test_p03_tenant_no_id_no_filter() -> Result<(), crate::DbError> {
3539        let dialect = get_dialect(DbType::MySQL)?;
3540        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3541            .table("orders")
3542            .build_select_with_params();
3543        assert!(
3544            !sql.contains("tenant_id"),
3545            "未设置 tenant_id 时不应追加过滤: {}",
3546            sql
3547        );
3548        assert_eq!(params.len(), 0);
3549        Ok(())
3550    }
3551
3552    /// 行为级测试 L3-29:软删除 + 多租户组合,两个条件同时追加
3553    ///
3554    /// 用户视角:同时启用软删除和多租户时,查询自动追加两个条件。
3555    #[test]
3556    fn test_p03_soft_delete_and_tenant_combined() -> Result<(), crate::DbError> {
3557        let dialect = get_dialect(DbType::MySQL)?;
3558        let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3559            .table("documents")
3560            .with_tenant_id(100)
3561            .where_eq("title", Value::String("report".into()))
3562            .build_select_with_params();
3563        // 软删除条件
3564        assert!(
3565            sql.contains("`deleted_at` IS NULL"),
3566            "应追加软删除条件: {}",
3567            sql
3568        );
3569        // 租户条件
3570        assert!(sql.contains("`tenant_id` = ?"), "应追加租户条件: {}", sql);
3571        // 用户条件
3572        assert!(sql.contains("`title` = ?"), "用户条件应保留: {}", sql);
3573        // 2 个参数:where_eq(title) + tenant_id(软删除 IS NULL 无参数)
3574        assert_eq!(params.len(), 2);
3575        assert_eq!(params[0], Value::String("report".into()));
3576        assert_eq!(params[1], Value::I64(100));
3577        Ok(())
3578    }
3579
3580    /// 行为级测试 L3-30:without_tenant + without_soft_delete 同时禁用
3581    #[test]
3582    fn test_p03_without_tenant_and_soft_delete() -> Result<(), crate::DbError> {
3583        let dialect = get_dialect(DbType::MySQL)?;
3584        let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3585            .table("documents")
3586            .with_tenant_id(100)
3587            .without_tenant()
3588            .without_soft_delete()
3589            .build_select_with_params();
3590        assert!(
3591            !sql.contains("`deleted_at` IS NULL"),
3592            "应禁用软删除: {}",
3593            sql
3594        );
3595        assert!(!sql.contains("`tenant_id` = ?"), "应禁用租户: {}", sql);
3596        assert_eq!(params.len(), 0);
3597        Ok(())
3598    }
3599
3600    /// 行为级测试 L3-31:is_tenant_disabled 反映状态
3601    #[test]
3602    fn test_p03_is_tenant_disabled_flag() -> Result<(), crate::DbError> {
3603        let dialect = get_dialect(DbType::MySQL)?;
3604        let builder = QueryBuilder::<TenantModel>::new(dialect);
3605        assert!(!builder.is_tenant_disabled(), "默认应启用租户过滤");
3606        let builder = QueryBuilder::<TenantModel>::new(get_dialect(DbType::MySQL)?)
3607            .with_tenant_id(1)
3608            .without_tenant();
3609        assert!(
3610            builder.is_tenant_disabled(),
3611            "without_tenant 后应反映禁用状态"
3612        );
3613        Ok(())
3614    }
3615
3616    /// 行为级测试 L3-32:build_force_delete 保留租户条件(防止跨租户物理删除)
3617    ///
3618    /// 用户视角:物理删除也应受租户隔离约束,跨租户操作需显式 without_tenant()。
3619    #[test]
3620    fn test_p03_tenant_force_delete_keeps_tenant_filter() -> Result<(), crate::DbError> {
3621        let dialect = get_dialect(DbType::MySQL)?;
3622        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3623            .table("orders")
3624            .with_tenant_id(42)
3625            .where_eq("id", Value::I64(999))
3626            .build_force_delete_with_params();
3627        // 物理删除不应追加软删除(TenantModel 未实现软删除,无影响)
3628        // 但应保留租户条件
3629        assert!(
3630            sql.contains("`tenant_id` = ?"),
3631            "物理删除应保留租户条件: {}",
3632            sql
3633        );
3634        assert_eq!(params.len(), 2);
3635        assert_eq!(params[0], Value::I64(999));
3636        assert_eq!(params[1], Value::I64(42));
3637        Ok(())
3638    }
3639
3640    // ---- TypedColumn 类型安全方法测试 ----
3641
3642    struct TcUsersTable;
3643    impl crate::typed::TypedTable for TcUsersTable {
3644        const NAME: &'static str = "users";
3645    }
3646    struct TcColId;
3647    impl crate::typed::TypedColumn for TcColId {
3648        const NAME: &'static str = "id";
3649        type Table = TcUsersTable;
3650        type RustType = i64;
3651        type SqlType = crate::typed_ast::Untyped;
3652    }
3653    struct TcColName;
3654    impl crate::typed::TypedColumn for TcColName {
3655        const NAME: &'static str = "name";
3656        type Table = TcUsersTable;
3657        type RustType = String;
3658        type SqlType = crate::typed_ast::Untyped;
3659    }
3660
3661    #[test]
3662    fn test_where_eq_typed() -> Result<(), crate::DbError> {
3663        let dialect = get_dialect(DbType::MySQL)?;
3664        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3665            .where_eq_typed::<TcColId>(Value::I64(42))
3666            .build_select_with_params();
3667        assert!(sql.contains("`id` = ?"));
3668        assert_eq!(params[0], Value::I64(42));
3669        Ok(())
3670    }
3671
3672    #[test]
3673    fn test_order_by_typed() -> Result<(), crate::DbError> {
3674        let dialect = get_dialect(DbType::MySQL)?;
3675        let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3676            .order_by_typed::<TcColName>()
3677            .build_select_with_params();
3678        assert!(sql.contains("ORDER BY"));
3679        assert!(sql.contains("`name`"));
3680        Ok(())
3681    }
3682
3683    #[test]
3684    fn test_select_typed() -> Result<(), crate::DbError> {
3685        let dialect = get_dialect(DbType::MySQL)?;
3686        let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3687            .select_typed::<TcColId>()
3688            .select_typed::<TcColName>()
3689            .build_select_with_params();
3690        assert!(sql.contains("SELECT"));
3691        // select_columns are joined as-is (not quoted by build_select_with_params)
3692        assert!(sql.contains("id"));
3693        assert!(sql.contains("name"));
3694        Ok(())
3695    }
3696
3697    #[test]
3698    fn test_where_null_typed() -> Result<(), crate::DbError> {
3699        let dialect = get_dialect(DbType::MySQL)?;
3700        let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3701            .where_null_typed::<TcColName>()
3702            .build_select_with_params();
3703        assert!(sql.contains("`name` IS NULL"));
3704        Ok(())
3705    }
3706
3707    #[test]
3708    fn test_where_not_null_typed() -> Result<(), crate::DbError> {
3709        let dialect = get_dialect(DbType::MySQL)?;
3710        let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3711            .where_not_null_typed::<TcColName>()
3712            .build_select_with_params();
3713        assert!(sql.contains("`name` IS NOT NULL"));
3714        Ok(())
3715    }
3716
3717    #[test]
3718    fn test_group_by_typed() -> Result<(), crate::DbError> {
3719        let dialect = get_dialect(DbType::MySQL)?;
3720        let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3721            .group_by_typed::<TcColName>()
3722            .build_select_with_params();
3723        assert!(sql.contains("GROUP BY"));
3724        assert!(sql.contains("`name`"));
3725        Ok(())
3726    }
3727
3728    #[test]
3729    fn test_where_gt_typed() -> Result<(), crate::DbError> {
3730        let dialect = get_dialect(DbType::MySQL)?;
3731        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3732            .where_gt_typed::<TcColId>(Value::I64(10))
3733            .build_select_with_params();
3734        assert!(sql.contains("`id` > ?"));
3735        assert_eq!(params[0], Value::I64(10));
3736        Ok(())
3737    }
3738
3739    // ---- P2-3:行锁查询测试(TASK-025/026) ----
3740
3741    #[test]
3742    fn test_lock_for_update_mysql() -> Result<(), crate::DbError> {
3743        let dialect = get_dialect(DbType::MySQL)?;
3744        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3745            .table("users")
3746            .where_eq("id", Value::I64(1))
3747            .lock_for_update()?
3748            .build_select_with_params();
3749        assert!(sql.contains("SELECT * FROM `users`"));
3750        assert!(sql.contains("WHERE `id` = ?"));
3751        assert!(sql.contains("FOR UPDATE"));
3752        assert_eq!(params.len(), 1);
3753        assert_eq!(params[0], Value::I64(1));
3754        Ok(())
3755    }
3756
3757    #[test]
3758    fn test_lock_shared_mysql() -> Result<(), crate::DbError> {
3759        let dialect = get_dialect(DbType::MySQL)?;
3760        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3761            .table("users")
3762            .where_eq("id", Value::I64(1))
3763            .lock_shared()?
3764            .build_select_with_params();
3765        assert!(sql.contains("SELECT * FROM `users`"));
3766        assert!(sql.contains("WHERE `id` = ?"));
3767        assert!(sql.contains("LOCK IN SHARE MODE"));
3768        assert_eq!(params.len(), 1);
3769        assert_eq!(params[0], Value::I64(1));
3770        Ok(())
3771    }
3772
3773    #[test]
3774    fn test_lock_for_update_postgresql() -> Result<(), crate::DbError> {
3775        let dialect = get_dialect(DbType::PostgreSQL)?;
3776        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3777            .table("users")
3778            .where_eq("id", Value::I64(1))
3779            .lock_for_update()?
3780            .build_select_with_params();
3781        assert!(sql.contains("SELECT * FROM \"users\""));
3782        assert!(sql.contains("WHERE \"id\" = ?"));
3783        assert!(sql.contains("FOR UPDATE"));
3784        assert_eq!(params.len(), 1);
3785        assert_eq!(params[0], Value::I64(1));
3786        Ok(())
3787    }
3788
3789    #[test]
3790    fn test_lock_shared_postgresql() -> Result<(), crate::DbError> {
3791        let dialect = get_dialect(DbType::PostgreSQL)?;
3792        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3793            .table("users")
3794            .where_eq("id", Value::I64(1))
3795            .lock_shared()?
3796            .build_select_with_params();
3797        assert!(sql.contains("SELECT * FROM \"users\""));
3798        assert!(sql.contains("WHERE \"id\" = ?"));
3799        assert!(sql.contains("FOR SHARE"));
3800        assert_eq!(params.len(), 1);
3801        assert_eq!(params[0], Value::I64(1));
3802        Ok(())
3803    }
3804
3805    #[test]
3806    fn test_lock_for_update_sqlite_should_fail() {
3807        let dialect = get_dialect(DbType::Sqlite).unwrap();
3808        let result = QueryBuilder::<TestModel>::new(dialect)
3809            .table("users")
3810            .where_eq("id", Value::I64(1))
3811            .lock_for_update();
3812        assert!(result.is_err(), "SQLite 不应支持 FOR UPDATE 锁");
3813        let err = result.err().unwrap();
3814        assert!(
3815            format!("{:?}", err).contains("FOR UPDATE lock is not supported"),
3816            "错误信息应说明不支持行锁"
3817        );
3818    }
3819
3820    #[test]
3821    fn test_lock_shared_sqlite_should_fail() {
3822        let dialect = get_dialect(DbType::Sqlite).unwrap();
3823        let result = QueryBuilder::<TestModel>::new(dialect)
3824            .table("users")
3825            .where_eq("id", Value::I64(1))
3826            .lock_shared();
3827        assert!(result.is_err(), "SQLite 不应支持共享锁");
3828        let err = result.err().unwrap();
3829        assert!(
3830            format!("{:?}", err).contains("Shared lock is not supported"),
3831            "错误信息应说明不支持共享锁"
3832        );
3833    }
3834
3835    #[test]
3836    fn test_lock_with_limit_and_offset() -> Result<(), crate::DbError> {
3837        let dialect = get_dialect(DbType::MySQL)?;
3838        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3839            .table("users")
3840            .where_eq("status", Value::String("active".into()))
3841            .limit(10)
3842            .offset(20)
3843            .lock_for_update()?
3844            .build_select_with_params();
3845        assert!(sql.contains("WHERE `status` = ?"));
3846        assert!(sql.contains("LIMIT 10"));
3847        assert!(sql.contains("OFFSET 20"));
3848        assert!(sql.contains("FOR UPDATE"));
3849        assert_eq!(params.len(), 1);
3850        Ok(())
3851    }
3852
3853    // ---- P2-4:INSERT OR IGNORE 测试(TASK-027/028) ----
3854
3855    #[test]
3856    fn test_insert_or_ignore_mysql() -> Result<(), crate::DbError> {
3857        let dialect = get_dialect(DbType::MySQL)?;
3858        let mut data = std::collections::HashMap::new();
3859        data.insert("name".to_string(), Value::String("Alice".into()));
3860        data.insert("age".to_string(), Value::I64(30));
3861
3862        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3863            .table("users")
3864            .insert_or_ignore()
3865            .build_insert_with_params(&data);
3866        assert!(sql.contains("INSERT IGNORE INTO `users`"));
3867        // HashMap 迭代顺序不确定,检查列名都存在即可
3868        assert!(sql.contains("`name`"), "SQL 应包含 name 列: {}", sql);
3869        assert!(sql.contains("`age`"), "SQL 应包含 age 列: {}", sql);
3870        assert!(sql.contains("VALUES (?, ?)"));
3871        assert_eq!(params.len(), 2);
3872        Ok(())
3873    }
3874
3875    #[test]
3876    fn test_insert_or_ignore_postgresql() -> Result<(), crate::DbError> {
3877        let dialect = get_dialect(DbType::PostgreSQL)?;
3878        let mut data = std::collections::HashMap::new();
3879        data.insert("name".to_string(), Value::String("Bob".into()));
3880
3881        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3882            .table("users")
3883            .insert_or_ignore()
3884            .build_insert_with_params(&data);
3885        assert!(sql.contains("INSERT OR IGNORE INTO \"users\""));
3886        assert!(sql.contains("(\"name\")"));
3887        assert!(sql.contains("VALUES (?)"));
3888        assert_eq!(params.len(), 1);
3889        assert_eq!(params[0], Value::String("Bob".into()));
3890        Ok(())
3891    }
3892
3893    #[test]
3894    fn test_insert_or_ignore_sqlite() -> Result<(), crate::DbError> {
3895        let dialect = get_dialect(DbType::Sqlite)?;
3896        let mut data = std::collections::HashMap::new();
3897        data.insert("name".to_string(), Value::String("Charlie".into()));
3898
3899        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3900            .table("users")
3901            .insert_or_ignore()
3902            .build_insert_with_params(&data);
3903        assert!(sql.contains("INSERT OR IGNORE INTO \"users\""));
3904        assert!(sql.contains("(\"name\")"));
3905        assert!(sql.contains("VALUES (?)"));
3906        assert_eq!(params.len(), 1);
3907        assert_eq!(params[0], Value::String("Charlie".into()));
3908        Ok(())
3909    }
3910
3911    #[test]
3912    fn test_insert_normal_without_ignore() -> Result<(), crate::DbError> {
3913        let dialect = get_dialect(DbType::MySQL)?;
3914        let mut data = std::collections::HashMap::new();
3915        data.insert("name".to_string(), Value::String("Dave".into()));
3916
3917        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3918            .table("users")
3919            .build_insert_with_params(&data);
3920        assert!(sql.contains("INSERT INTO `users`"));
3921        assert!(!sql.contains("IGNORE"), "普通插入不应包含 IGNORE");
3922        assert_eq!(params.len(), 1);
3923        assert_eq!(params[0], Value::String("Dave".into()));
3924        Ok(())
3925    }
3926
3927    #[test]
3928    fn test_insert_or_ignore_empty_data() -> Result<(), crate::DbError> {
3929        let dialect = get_dialect(DbType::MySQL)?;
3930        let data = std::collections::HashMap::new();
3931
3932        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3933            .table("users")
3934            .insert_or_ignore()
3935            .build_insert_with_params(&data);
3936        assert!(sql.is_empty(), "空数据应返回空 SQL");
3937        assert!(params.is_empty());
3938        Ok(())
3939    }
3940
3941    // ---- P2-3:方言支持性测试(TASK-029) ----
3942
3943    #[test]
3944    fn test_dialect_supports_lock_for_update() -> Result<(), crate::DbError> {
3945        let mysql = get_dialect(DbType::MySQL)?;
3946        let pg = get_dialect(DbType::PostgreSQL)?;
3947        let sqlite = get_dialect(DbType::Sqlite)?;
3948        let clickhouse = get_dialect(DbType::ClickHouse)?;
3949        let duckdb = get_dialect(DbType::DuckDB)?;
3950
3951        assert!(mysql.supports_lock_for_update(), "MySQL 应支持 FOR UPDATE");
3952        assert!(
3953            pg.supports_lock_for_update(),
3954            "PostgreSQL 应支持 FOR UPDATE"
3955        );
3956        assert!(
3957            !sqlite.supports_lock_for_update(),
3958            "SQLite 不应支持 FOR UPDATE"
3959        );
3960        assert!(
3961            !clickhouse.supports_lock_for_update(),
3962            "ClickHouse 是列式 OLAP,不应支持 FOR UPDATE"
3963        );
3964        assert!(
3965            !duckdb.supports_lock_for_update(),
3966            "DuckDB 不应支持 FOR UPDATE"
3967        );
3968        Ok(())
3969    }
3970
3971    #[test]
3972    fn test_dialect_supports_lock_shared() -> Result<(), crate::DbError> {
3973        let mysql = get_dialect(DbType::MySQL)?;
3974        let pg = get_dialect(DbType::PostgreSQL)?;
3975        let sqlite = get_dialect(DbType::Sqlite)?;
3976        let clickhouse = get_dialect(DbType::ClickHouse)?;
3977        let duckdb = get_dialect(DbType::DuckDB)?;
3978
3979        assert!(mysql.supports_lock_shared(), "MySQL 应支持共享锁");
3980        assert!(pg.supports_lock_shared(), "PostgreSQL 应支持共享锁");
3981        assert!(!sqlite.supports_lock_shared(), "SQLite 不应支持共享锁");
3982        assert!(
3983            !clickhouse.supports_lock_shared(),
3984            "ClickHouse 是列式 OLAP,不应支持共享锁"
3985        );
3986        assert!(!duckdb.supports_lock_shared(), "DuckDB 不应支持共享锁");
3987        Ok(())
3988    }
3989
3990    #[test]
3991    fn test_get_lock_type_and_is_insert_or_ignore() -> Result<(), crate::DbError> {
3992        let dialect = get_dialect(DbType::MySQL)?;
3993
3994        // 默认值
3995        let builder = QueryBuilder::<TestModel>::new(dialect);
3996        assert!(builder.get_lock_type().is_none(), "默认无锁");
3997        assert!(!builder.is_insert_or_ignore(), "默认不忽略插入");
3998
3999        // 设置锁后
4000        let builder = QueryBuilder::<TestModel>::new(get_dialect(DbType::MySQL)?)
4001            .table("users")
4002            .lock_for_update()?;
4003        assert_eq!(builder.get_lock_type(), Some(LockType::ForUpdate));
4004
4005        // 设置忽略插入后
4006        let builder = QueryBuilder::<TestModel>::new(get_dialect(DbType::MySQL)?)
4007            .table("users")
4008            .insert_or_ignore();
4009        assert!(builder.is_insert_or_ignore());
4010
4011        Ok(())
4012    }
4013
4014    struct TestModelWithColumns;
4015    impl Model for TestModelWithColumns {
4016        type PrimaryKey = i64;
4017        fn table_name() -> &'static str {
4018            "test_with_cols"
4019        }
4020        fn pk(&self) -> Self::PrimaryKey {
4021            0
4022        }
4023        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
4024    }
4025    impl crate::model::ModelExt for TestModelWithColumns {
4026        fn columns() -> Vec<&'static str> {
4027            vec!["id", "name", "email", "avatar", "blob_data"]
4028        }
4029        fn fillable() -> Vec<&'static str> {
4030            vec!["name", "email", "avatar", "blob_data"]
4031        }
4032        fn guarded() -> Vec<&'static str> {
4033            vec!["id"]
4034        }
4035        fn hidden() -> Vec<&'static str> {
4036            vec!["blob_data"]
4037        }
4038        fn relations() -> std::collections::HashMap<&'static str, crate::model::Relation> {
4039            std::collections::HashMap::new()
4040        }
4041        fn fill(&mut self, _data: std::collections::HashMap<String, crate::value::Value>) {}
4042        fn to_json(&self) -> serde_json::Value {
4043            serde_json::Value::Null
4044        }
4045    }
4046
4047    #[test]
4048    fn test_select_exclude_basic() -> Result<(), crate::DbError> {
4049        let dialect = get_dialect(DbType::MySQL)?;
4050        let builder = QueryBuilder::<TestModelWithColumns>::new(dialect)
4051            .table("users")
4052            .select_exclude(&["avatar", "blob_data"])?;
4053        let sql = builder.build_select();
4054        assert!(sql.contains("id"));
4055        assert!(sql.contains("name"));
4056        assert!(sql.contains("email"));
4057        assert!(!sql.contains("avatar"));
4058        assert!(!sql.contains("blob_data"));
4059        Ok(())
4060    }
4061
4062    #[test]
4063    fn test_select_exclude_nonexistent_field() {
4064        let dialect = get_dialect(DbType::MySQL).unwrap();
4065        let result = QueryBuilder::<TestModelWithColumns>::new(dialect)
4066            .table("users")
4067            .select_exclude(&["nonexistent"]);
4068        assert!(result.is_err());
4069    }
4070
4071    #[test]
4072    fn test_select_exclude_all_fields() {
4073        let dialect = get_dialect(DbType::MySQL).unwrap();
4074        let result = QueryBuilder::<TestModelWithColumns>::new(dialect)
4075            .table("users")
4076            .select_exclude(&["id", "name", "email", "avatar", "blob_data"]);
4077        assert!(result.is_err());
4078        let err = result.unwrap_err();
4079        assert!(matches!(err, crate::DbError::InvalidInput(_)));
4080    }
4081}