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