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//! v5.0.0 已移除 `where_cond(condition: impl Into<String>)` 字符串拼接 API(注入风险)。
24
25use crate::db_type::DbType;
26use crate::dialect::Dialect;
27use crate::dialect::LockType;
28use crate::model::Model;
29use crate::typed::TypedColumn;
30use crate::value::Value;
31use std::fmt;
32use std::fmt::Write as _;
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_gt("age", Value::I64(18))
1450    ///     .order_by("id DESC")
1451    ///     .limit(10)
1452    ///     .sql();
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 sql(&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                    let _ = write!(
1478                        sql,
1479                        " INNER JOIN {} ON {} = {}",
1480                        self.dialect.quote(t),
1481                        self.dialect.quote(l),
1482                        self.dialect.quote(r)
1483                    );
1484                }
1485                JoinClause::Left(t, l, r) => {
1486                    let _ = write!(
1487                        sql,
1488                        " LEFT JOIN {} ON {} = {}",
1489                        self.dialect.quote(t),
1490                        self.dialect.quote(l),
1491                        self.dialect.quote(r)
1492                    );
1493                }
1494                JoinClause::Right(t, l, r) => {
1495                    let _ = write!(
1496                        sql,
1497                        " RIGHT JOIN {} ON {} = {}",
1498                        self.dialect.quote(t),
1499                        self.dialect.quote(l),
1500                        self.dialect.quote(r)
1501                    );
1502                }
1503                JoinClause::Cross(t, on) => {
1504                    let _ = write!(
1505                        sql,
1506                        " CROSS JOIN {} ON {}",
1507                        self.dialect.quote(t),
1508                        self.dialect.quote(on)
1509                    );
1510                }
1511                JoinClause::Relation(kind, ft, fk, tt, tk) => {
1512                    let _ = write!(
1513                        sql,
1514                        " {} {} ON {}.{} = {}.{}",
1515                        kind.as_sql(),
1516                        self.dialect.quote(tt),
1517                        self.dialect.quote(ft),
1518                        self.dialect.quote(fk),
1519                        self.dialect.quote(tt),
1520                        self.dialect.quote(tk)
1521                    );
1522                }
1523            }
1524        }
1525
1526        // P0-1:build_where_clause 内部已处理软删除条件,即使 where_conditions 为空也可能返回非空
1527        let where_clause = self.build_where_clause();
1528        if !where_clause.is_empty() {
1529            sql.push_str(&where_clause);
1530        }
1531
1532        if !self.group_by.is_empty() {
1533            let cols: Vec<String> = self
1534                .group_by
1535                .iter()
1536                .map(|c| self.dialect.quote(c))
1537                .collect();
1538            sql.push_str(" GROUP BY ");
1539            sql.push_str(&cols.join(", "));
1540        }
1541
1542        if !self.having_conditions.is_empty() {
1543            sql.push_str(" HAVING ");
1544            for (i, cond) in self.having_conditions.iter().enumerate() {
1545                if i > 0 {
1546                    sql.push_str(" AND ");
1547                }
1548                match cond {
1549                    WhereCondition::And(c) => sql.push_str(c),
1550                    // M-5:无参数版本将值经方言转义内联(与参数化 where 条件同模式)
1551                    WhereCondition::Having(agg, op, value) => {
1552                        sql.push_str(&format!(
1553                            "{} {} {}",
1554                            agg.render(&*self.dialect),
1555                            op.as_sql(),
1556                            value.to_param_with_dialect(&*self.dialect)
1557                        ));
1558                    }
1559                    _ => {}
1560                }
1561            }
1562        }
1563
1564        if !self.order_by.is_empty() {
1565            let order_cols: Vec<String> = self
1566                .order_by
1567                .iter()
1568                .map(|o| {
1569                    let dir = match o.direction {
1570                        OrderDirection::Asc => " ASC",
1571                        OrderDirection::Desc => " DESC",
1572                    };
1573                    format!("{}{}", self.dialect.quote(&o.field), dir)
1574                })
1575                .collect();
1576            sql.push_str(" ORDER BY ");
1577            sql.push_str(&order_cols.join(", "));
1578        }
1579
1580        if let Some(limit) = self.limit_value {
1581            let _ = write!(sql, " LIMIT {}", limit);
1582        }
1583
1584        if let Some(offset) = self.offset_value {
1585            let _ = write!(sql, " OFFSET {}", offset);
1586        }
1587
1588        sql.into_string()
1589    }
1590
1591    /// 构建 WHERE 子句(处理所有条件类型:And/Or/In/NotIn/Between/Null/Eq/Ne/Gt/Lt/Like 等)
1592    ///
1593    /// P0-1:自动追加软删除过滤条件(`AND {soft_delete_field} IS NULL`)
1594    ///
1595    /// 返回空字符串表示无 WHERE 子句
1596    fn build_where_clause(&self) -> String {
1597        self.build_where_clause_with_options(true)
1598    }
1599
1600    /// 构建 WHERE 子句(可控制是否追加软删除过滤)。
1601    ///
1602    /// `include_soft_delete = true`:追加 `AND {soft_delete_field} IS NULL`(默认行为)
1603    /// `include_soft_delete = false`:不追加软删除过滤(用于 `build_force_delete`)
1604    ///
1605    /// P0-3:租户条件总是追加(若启用),不受 `include_soft_delete` 控制。
1606    /// 物理删除也应受租户隔离约束,跨租户操作需显式 `without_tenant()`。
1607    fn build_where_clause_with_options(&self, include_soft_delete: bool) -> String {
1608        // P0-1:构造软删除条件(若有且启用)
1609        let soft_delete_cond = if include_soft_delete {
1610            self.build_soft_delete_condition()
1611        } else {
1612            None
1613        };
1614
1615        // P0-3:构造租户条件(若有且启用)— 无参数版本内嵌转义值
1616        let tenant_cond = self.build_tenant_condition().map(|(sql, value)| {
1617            // sql 形如 "`tenant_id` = ?",将 ? 替换为内嵌值
1618            sql.replacen('?', &value.to_param_with_dialect(&*self.dialect), 1)
1619        });
1620
1621        // 无用户条件且无软删除条件且无租户条件且无 keyset 游标 → 空 WHERE
1622        if self.where_conditions.is_empty()
1623            && soft_delete_cond.is_none()
1624            && tenant_cond.is_none()
1625            && self.keyset_cursor.is_none()
1626        {
1627            return String::new();
1628        }
1629
1630        // 将每个条件转换为字符串,OR 条件标记前缀
1631        let mut conditions: Vec<String> = self
1632            .where_conditions
1633            .iter()
1634            .map(|cond| match cond {
1635                WhereCondition::And(c) => c.clone(),
1636                WhereCondition::Or(c) => format!("OR {}", c),
1637                // P0-2:参数化条件在无参数版本中直接 inline 值(用于 build_select 等无参数绑定场景)
1638                WhereCondition::Eq(f, v) => format!(
1639                    "{} = {}",
1640                    self.dialect.quote(f),
1641                    v.to_param_with_dialect(&*self.dialect)
1642                ),
1643                WhereCondition::Ne(f, v) => format!(
1644                    "{} != {}",
1645                    self.dialect.quote(f),
1646                    v.to_param_with_dialect(&*self.dialect)
1647                ),
1648                WhereCondition::Gt(f, v) => format!(
1649                    "{} > {}",
1650                    self.dialect.quote(f),
1651                    v.to_param_with_dialect(&*self.dialect)
1652                ),
1653                WhereCondition::Ge(f, v) => format!(
1654                    "{} >= {}",
1655                    self.dialect.quote(f),
1656                    v.to_param_with_dialect(&*self.dialect)
1657                ),
1658                WhereCondition::Lt(f, v) => format!(
1659                    "{} < {}",
1660                    self.dialect.quote(f),
1661                    v.to_param_with_dialect(&*self.dialect)
1662                ),
1663                WhereCondition::Le(f, v) => format!(
1664                    "{} <= {}",
1665                    self.dialect.quote(f),
1666                    v.to_param_with_dialect(&*self.dialect)
1667                ),
1668                WhereCondition::Like(f, v) => format!(
1669                    "{} LIKE {}",
1670                    self.dialect.quote(f),
1671                    v.to_param_with_dialect(&*self.dialect)
1672                ),
1673                WhereCondition::OrEq(f, v) => format!(
1674                    "OR {} = {}",
1675                    self.dialect.quote(f),
1676                    v.to_param_with_dialect(&*self.dialect)
1677                ),
1678                WhereCondition::OrNe(f, v) => format!(
1679                    "OR {} != {}",
1680                    self.dialect.quote(f),
1681                    v.to_param_with_dialect(&*self.dialect)
1682                ),
1683                WhereCondition::OrGt(f, v) => format!(
1684                    "OR {} > {}",
1685                    self.dialect.quote(f),
1686                    v.to_param_with_dialect(&*self.dialect)
1687                ),
1688                WhereCondition::OrGe(f, v) => format!(
1689                    "OR {} >= {}",
1690                    self.dialect.quote(f),
1691                    v.to_param_with_dialect(&*self.dialect)
1692                ),
1693                WhereCondition::OrLt(f, v) => format!(
1694                    "OR {} < {}",
1695                    self.dialect.quote(f),
1696                    v.to_param_with_dialect(&*self.dialect)
1697                ),
1698                WhereCondition::OrLe(f, v) => format!(
1699                    "OR {} <= {}",
1700                    self.dialect.quote(f),
1701                    v.to_param_with_dialect(&*self.dialect)
1702                ),
1703                WhereCondition::OrLike(f, v) => format!(
1704                    "OR {} LIKE {}",
1705                    self.dialect.quote(f),
1706                    v.to_param_with_dialect(&*self.dialect)
1707                ),
1708                WhereCondition::In(f, vals) => {
1709                    // v0.2.2 修复 H-1:使用方言感知的转义
1710                    let vals_str: Vec<String> = vals
1711                        .iter()
1712                        .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1713                        .collect();
1714                    format!("{} IN ({})", self.dialect.quote(f), vals_str.join(", "))
1715                }
1716                WhereCondition::NotIn(f, vals) => {
1717                    let vals_str: Vec<String> = vals
1718                        .iter()
1719                        .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1720                        .collect();
1721                    format!("{} NOT IN ({})", self.dialect.quote(f), vals_str.join(", "))
1722                }
1723                WhereCondition::Between(f, start, end) => {
1724                    format!(
1725                        "{} BETWEEN {} AND {}",
1726                        self.dialect.quote(f),
1727                        start.to_param_with_dialect(&*self.dialect),
1728                        end.to_param_with_dialect(&*self.dialect)
1729                    )
1730                }
1731                WhereCondition::NotBetween(f, start, end) => {
1732                    format!(
1733                        "{} NOT BETWEEN {} AND {}",
1734                        self.dialect.quote(f),
1735                        start.to_param_with_dialect(&*self.dialect),
1736                        end.to_param_with_dialect(&*self.dialect)
1737                    )
1738                }
1739                WhereCondition::Null(f) => format!("{} IS NULL", self.dialect.quote(f)),
1740                WhereCondition::NotNull(f) => format!("{} IS NOT NULL", self.dialect.quote(f)),
1741                WhereCondition::Exists(s) => format!("EXISTS ({})", s),
1742                WhereCondition::NotExists(s) => format!("NOT EXISTS ({})", s),
1743                WhereCondition::TypedExpr(sql, _) => sql.clone(),
1744                // M-5:Having 仅出现在 HAVING 子句,防御性空串
1745                WhereCondition::Having(..) => String::new(),
1746            })
1747            .collect();
1748
1749        // P0-1:追加软删除条件(作为最后一个 AND 条件)
1750        if let Some(sd_cond) = soft_delete_cond {
1751            conditions.push(sd_cond);
1752        }
1753
1754        // P0-3:追加租户条件(在软删除之后,作为 AND 条件)
1755        if let Some(t_cond) = tenant_cond {
1756            conditions.push(t_cond);
1757        }
1758
1759        // P2-5:追加 keyset 游标条件(在租户条件之后,内嵌转义值)
1760        if let Some(ref cursor) = self.keyset_cursor {
1761            let op = match cursor.direction {
1762                KeysetDirection::After => ">",
1763                KeysetDirection::Before => "<",
1764            };
1765            conditions.push(format!(
1766                "{} {} {}",
1767                self.dialect.quote(&cursor.field),
1768                op,
1769                cursor.value.to_param_with_dialect(&*self.dialect)
1770            ));
1771        }
1772
1773        if conditions.is_empty() {
1774            return String::new();
1775        }
1776
1777        // OR 分组逻辑:将相邻的 OR 条件组合成 (cond1 OR cond2) 形式
1778        // 边界处理:如果第一个条件就是 OR(不合理但需防御),当作 AND 处理
1779        let mut groups: Vec<Vec<String>> = Vec::new();
1780        let mut current_group: Vec<String> = Vec::new();
1781        for cond in conditions.iter() {
1782            if let Some(stripped) = cond.strip_prefix("OR ") {
1783                // OR 条件:无论是否首个,都把 OR 前缀去掉当作普通条件加入当前组
1784                current_group.push(stripped.to_string());
1785            } else {
1786                // AND 条件:如果当前组非空,先保存
1787                if !current_group.is_empty() {
1788                    groups.push(std::mem::take(&mut current_group));
1789                }
1790                current_group.push(cond.clone());
1791            }
1792        }
1793        if !current_group.is_empty() {
1794            groups.push(current_group);
1795        }
1796
1797        let group_strs: Vec<String> = groups
1798            .iter()
1799            .map(|g| {
1800                if g.len() == 1 {
1801                    g[0].clone()
1802                } else {
1803                    format!("({})", g.join(" OR "))
1804                }
1805            })
1806            .collect();
1807
1808        // SAFETY: group_strs 来自 WhereCondition::Eq/Gt 等参数化变体渲染,值已绑定为 ? 占位符或内联常量
1809        format!(" WHERE {}", group_strs.join(" AND "))
1810    }
1811
1812    /// 构建 SELECT SQL + 参数绑定(v5.0.0 统一签名,返回 `(String, Vec<Value>)`)
1813    ///
1814    /// v5.0.0 Breaking Change:`build_select()` 返回类型从 `String` 改为 `(String, Vec<Value>)`。
1815    /// 如需纯 SQL 字符串(参数内联渲染),请使用 [`sql()`](Self::sql) 方法。
1816    #[tracing::instrument(skip(self), fields(op = "select"))]
1817    pub fn build_select(&self) -> (String, Vec<Value>) {
1818        self.build_select_with_params()
1819    }
1820
1821    /// 构建 INSERT SQL + 参数绑定(v5.0.0 统一签名)
1822    #[tracing::instrument(skip(self, data), fields(op = "insert"))]
1823    pub fn build_insert(
1824        &self,
1825        data: &std::collections::HashMap<String, Value>,
1826    ) -> (String, Vec<Value>) {
1827        self.build_insert_with_params(data)
1828    }
1829
1830    /// 构建 INSERT SQL(纯 SQL,参数内联渲染)
1831
1832    #[tracing::instrument(skip(self, data), fields(op = "insert"))]
1833    pub fn sql_insert(&self, data: &std::collections::HashMap<String, Value>) -> String {
1834        let table = self
1835            .table
1836            .clone()
1837            .unwrap_or_else(|| M::table_name().to_string());
1838
1839        if data.is_empty() {
1840            return String::new();
1841        }
1842
1843        let columns: Vec<String> = data.keys().map(|k| self.dialect.quote(k)).collect();
1844        // v0.2.2 修复 H-1:使用方言感知的转义
1845        let values: Vec<String> = data
1846            .values()
1847            .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1848            .collect();
1849
1850        crate::sql_buffer::SqlBuffer::from_str(&format!(
1851            "INSERT INTO {} ({}) VALUES ({})",
1852            self.dialect.quote(&table),
1853            columns.join(", "),
1854            values.join(", ")
1855        ))
1856        .into_string()
1857    }
1858
1859    /// 构建 UPDATE SQL + 参数绑定(v5.0.0 统一签名)
1860    #[tracing::instrument(skip(self, data), fields(op = "update"))]
1861    pub fn build_update(
1862        &self,
1863        data: &std::collections::HashMap<String, Value>,
1864    ) -> (String, Vec<Value>) {
1865        self.build_update_with_params(data)
1866    }
1867
1868    /// 构建 UPDATE SQL(纯 SQL,参数内联渲染)
1869
1870    #[tracing::instrument(skip(self, data), fields(op = "update"))]
1871    pub fn sql_update(&self, data: &std::collections::HashMap<String, Value>) -> String {
1872        let table = self
1873            .table
1874            .clone()
1875            .unwrap_or_else(|| M::table_name().to_string());
1876
1877        if data.is_empty() {
1878            return String::new();
1879        }
1880
1881        let set_clauses: Vec<String> = data
1882            .iter()
1883            .map(|(k, v)| {
1884                format!(
1885                    "{} = {}",
1886                    self.dialect.quote(k),
1887                    v.to_param_with_dialect(&*self.dialect)
1888                )
1889            })
1890            .collect();
1891
1892        let mut sql = crate::sql_buffer::SqlBuffer::from_str(&format!(
1893            "UPDATE {} SET {}",
1894            self.dialect.quote(&table),
1895            set_clauses.join(", ")
1896        ));
1897
1898        sql.push_str(&self.build_where_clause());
1899        sql.into_string()
1900    }
1901
1902    /// 构建 DELETE SQL 语句。
1903    ///
1904    /// **P0-1 软删除集成(v1.3.0+)**:当 `M: Model` 实现了 `soft_delete_field()`
1905    /// 返回 `Some(field)` 且未调用 `without_soft_delete()` 时,本方法自动生成
1906    /// `UPDATE {table} SET {field} = NOW() WHERE ...` 而非 `DELETE FROM ...`。
1907    ///
1908    /// 这与 SeaORM 的 `ActiveModelBehavior::after_delete` + `ActiveValue::Set`
1909    /// 行为对齐:删除操作实际是软删除 UPDATE。
1910    ///
1911    /// 若需物理删除,请使用 [`build_force_delete`](Self::build_force_delete)。
1912    /// 构建 DELETE SQL + 参数绑定(v5.0.0 统一签名)
1913    #[tracing::instrument(skip(self), fields(op = "delete"))]
1914    pub fn build_delete(&self) -> (String, Vec<Value>) {
1915        self.build_delete_with_params()
1916    }
1917
1918    /// 构建 DELETE SQL(纯 SQL,参数内联渲染)
1919
1920    #[tracing::instrument(skip(self), fields(op = "delete"))]
1921    pub fn sql_delete(&self) -> String {
1922        let table = self
1923            .table
1924            .clone()
1925            .unwrap_or_else(|| M::table_name().to_string());
1926
1927        // P0-1:软删除启用时转为 UPDATE SET {field} = NOW()
1928        if let Some(field) = self.soft_delete_field() {
1929            let where_clause = self.build_where_clause();
1930            return format!(
1931                "UPDATE {} SET {} = NOW(){}",
1932                self.dialect.quote(&table),
1933                self.dialect.quote(field),
1934                where_clause
1935            );
1936        }
1937
1938        let mut sql = crate::sql_buffer::SqlBuffer::from_str(&format!(
1939            "DELETE FROM {}",
1940            self.dialect.quote(&table)
1941        ));
1942        sql.push_str(&self.build_where_clause());
1943        sql.into_string()
1944    }
1945
1946    /// 构建物理 DELETE SQL 语句(绕过软删除)。
1947    ///
1948    /// 即使 Model 实现了 `soft_delete_field()`,也生成 `DELETE FROM ...`,
1949    /// 且**不追加** `WHERE deleted_at IS NULL` 过滤(保留用户指定的 WHERE 条件)。
1950    /// 用于管理员强制清除场景。
1951    ///
1952    /// # 安全警告
1953    ///
1954    /// 物理删除不可恢复,请谨慎使用。
1955    pub fn build_force_delete(&self) -> String {
1956        let table = self
1957            .table
1958            .clone()
1959            .unwrap_or_else(|| M::table_name().to_string());
1960
1961        let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1962        // P0-1:物理删除不追加软删除过滤(include_soft_delete = false)
1963        sql.push_str(&self.build_where_clause_with_options(false));
1964        sql
1965    }
1966
1967    // ===================== 参数绑定版本(v1.1.0 新增) =====================
1968
1969    /// 构建 WHERE 子句(参数绑定版本)。
1970    ///
1971    /// P0-1:自动追加软删除过滤条件(`AND {soft_delete_field} IS NULL`,无参数)
1972    ///
1973    /// 将 `In`/`NotIn`/`Between`/`NotBetween`/`Eq`/`Ne`/`Gt`/`Ge`/`Lt`/`Le`/`Like`
1974    /// 条件中的值替换为 `?` 占位符,值收集到 `params` 向量中。
1975    /// `And`/`Or`/`Exists` 等原始字符串条件不提取参数(调用方负责安全)。
1976    fn build_where_clause_with_params(&self) -> (String, Vec<Value>) {
1977        // 默认包含软删除条件
1978        self.build_where_clause_with_params_options(true)
1979    }
1980
1981    /// 构建参数化 WHERE 子句(可控是否包含软删除条件)
1982    ///
1983    /// # 参数
1984    ///
1985    /// - `include_soft_delete`:true 时追加软删除条件;false 时跳过(用于物理删除等场景)
1986    ///
1987    /// P0-3:租户条件总是追加(若启用),不受 `include_soft_delete` 控制。
1988    /// 租户值通过 `?` 占位符绑定,加入 `params` 列表末尾。
1989    fn build_where_clause_with_params_options(
1990        &self,
1991        include_soft_delete: bool,
1992    ) -> (String, Vec<Value>) {
1993        // P0-1:构造软删除条件(若有且启用,无参数)
1994        let soft_delete_cond = if include_soft_delete {
1995            self.build_soft_delete_condition()
1996        } else {
1997            None
1998        };
1999
2000        // P0-3:构造租户条件(若有且启用)— 参数化版本保留 (sql, value)
2001        let tenant_cond = self.build_tenant_condition();
2002
2003        // v4.7.0 REQ-V47-006:构造增强 RLS 条件(启用 tenant-quota-rls-enhanced feature 时生效)
2004        #[cfg(feature = "tenant-quota-rls-enhanced")]
2005        let rls_cond: Option<(String, Vec<Value>)> = {
2006            if let Some(ref enhancer) = self.rls_enhancer {
2007                if let Some(ref table) = self.table {
2008                    if let Some(tid) = self.tenant_id_value() {
2009                        match enhancer.enhance_query(table, &tid.to_string()) {
2010                            Ok(Some(c)) => Some((c.sql_fragment, c.params)),
2011                            Ok(None) => None,
2012                            Err(_) => None,
2013                        }
2014                    } else {
2015                        None
2016                    }
2017                } else {
2018                    None
2019                }
2020            } else {
2021                None
2022            }
2023        };
2024        #[cfg(not(feature = "tenant-quota-rls-enhanced"))]
2025        let rls_cond: Option<(String, Vec<Value>)> = None;
2026
2027        // 无用户条件且无软删除条件且无租户条件且无 keyset 游标且无 RLS 条件 → 空 WHERE
2028        if self.where_conditions.is_empty()
2029            && soft_delete_cond.is_none()
2030            && tenant_cond.is_none()
2031            && self.keyset_cursor.is_none()
2032            && rls_cond.is_none()
2033        {
2034            return (String::new(), Vec::new());
2035        }
2036
2037        let mut params = Vec::new();
2038
2039        let mut conditions: Vec<String> = self
2040            .where_conditions
2041            .iter()
2042            .map(|cond| match cond {
2043                WhereCondition::And(c) => c.clone(),
2044                WhereCondition::Or(c) => format!("OR {}", c),
2045                // P0-2:参数化条件使用 `?` 占位符
2046                WhereCondition::Eq(f, v) => {
2047                    params.push(v.clone());
2048                    format!("{} = ?", self.dialect.quote(f))
2049                }
2050                WhereCondition::Ne(f, v) => {
2051                    params.push(v.clone());
2052                    format!("{} != ?", self.dialect.quote(f))
2053                }
2054                WhereCondition::Gt(f, v) => {
2055                    params.push(v.clone());
2056                    format!("{} > ?", self.dialect.quote(f))
2057                }
2058                WhereCondition::Ge(f, v) => {
2059                    params.push(v.clone());
2060                    format!("{} >= ?", self.dialect.quote(f))
2061                }
2062                WhereCondition::Lt(f, v) => {
2063                    params.push(v.clone());
2064                    format!("{} < ?", self.dialect.quote(f))
2065                }
2066                WhereCondition::Le(f, v) => {
2067                    params.push(v.clone());
2068                    format!("{} <= ?", self.dialect.quote(f))
2069                }
2070                WhereCondition::Like(f, v) => {
2071                    params.push(v.clone());
2072                    format!("{} LIKE ?", self.dialect.quote(f))
2073                }
2074                WhereCondition::OrEq(f, v) => {
2075                    params.push(v.clone());
2076                    format!("OR {} = ?", self.dialect.quote(f))
2077                }
2078                WhereCondition::OrNe(f, v) => {
2079                    params.push(v.clone());
2080                    format!("OR {} != ?", self.dialect.quote(f))
2081                }
2082                WhereCondition::OrGt(f, v) => {
2083                    params.push(v.clone());
2084                    format!("OR {} > ?", self.dialect.quote(f))
2085                }
2086                WhereCondition::OrGe(f, v) => {
2087                    params.push(v.clone());
2088                    format!("OR {} >= ?", self.dialect.quote(f))
2089                }
2090                WhereCondition::OrLt(f, v) => {
2091                    params.push(v.clone());
2092                    format!("OR {} < ?", self.dialect.quote(f))
2093                }
2094                WhereCondition::OrLe(f, v) => {
2095                    params.push(v.clone());
2096                    format!("OR {} <= ?", self.dialect.quote(f))
2097                }
2098                WhereCondition::OrLike(f, v) => {
2099                    params.push(v.clone());
2100                    format!("OR {} LIKE ?", self.dialect.quote(f))
2101                }
2102                WhereCondition::In(f, vals) => {
2103                    let placeholders: Vec<&str> = vals.iter().map(|_| "?").collect();
2104                    params.extend(vals.iter().cloned());
2105                    format!("{} IN ({})", self.dialect.quote(f), placeholders.join(", "))
2106                }
2107                WhereCondition::NotIn(f, vals) => {
2108                    let placeholders: Vec<&str> = vals.iter().map(|_| "?").collect();
2109                    params.extend(vals.iter().cloned());
2110                    format!(
2111                        "{} NOT IN ({})",
2112                        self.dialect.quote(f),
2113                        placeholders.join(", ")
2114                    )
2115                }
2116                WhereCondition::Between(f, start, end) => {
2117                    params.push(start.clone());
2118                    params.push(end.clone());
2119                    format!("{} BETWEEN ? AND ?", self.dialect.quote(f))
2120                }
2121                WhereCondition::NotBetween(f, start, end) => {
2122                    params.push(start.clone());
2123                    params.push(end.clone());
2124                    format!("{} NOT BETWEEN ? AND ?", self.dialect.quote(f))
2125                }
2126                WhereCondition::Null(f) => format!("{} IS NULL", self.dialect.quote(f)),
2127                WhereCondition::NotNull(f) => format!("{} IS NOT NULL", self.dialect.quote(f)),
2128                WhereCondition::Exists(s) => format!("EXISTS ({})", s),
2129                WhereCondition::NotExists(s) => format!("NOT EXISTS ({})", s),
2130                WhereCondition::TypedExpr(sql, expr_params) => {
2131                    params.extend(expr_params.iter().cloned());
2132                    sql.clone()
2133                }
2134                // M-5:Having 仅出现在 HAVING 子句,防御性空串
2135                WhereCondition::Having(..) => String::new(),
2136            })
2137            .collect();
2138
2139        // P0-1:追加软删除条件(作为最后一个 AND 条件,无参数)
2140        if let Some(sd_cond) = soft_delete_cond {
2141            conditions.push(sd_cond);
2142        }
2143
2144        // P0-3:追加租户条件(在软删除之后,参数化绑定)
2145        if let Some((t_sql, t_value)) = tenant_cond {
2146            conditions.push(t_sql);
2147            params.push(t_value);
2148        }
2149
2150        // v4.7.0 REQ-V47-006:追加增强 RLS 条件(在租户条件之后,参数化绑定)
2151        if let Some((rls_sql, rls_params)) = rls_cond {
2152            conditions.push(rls_sql);
2153            params.extend(rls_params);
2154        }
2155
2156        // P2-5:追加 keyset 游标条件(在租户条件之后,参数化绑定)
2157        if let Some(ref cursor) = self.keyset_cursor {
2158            let op = match cursor.direction {
2159                KeysetDirection::After => ">",
2160                KeysetDirection::Before => "<",
2161            };
2162            conditions.push(format!("{} {} ?", self.dialect.quote(&cursor.field), op));
2163            params.push(cursor.value.clone());
2164        }
2165
2166        if conditions.is_empty() {
2167            return (String::new(), params);
2168        }
2169
2170        // OR 分组逻辑:与 build_where_clause 相同
2171        let mut groups: Vec<Vec<String>> = Vec::new();
2172        let mut current_group: Vec<String> = Vec::new();
2173        for cond in conditions.iter() {
2174            if let Some(stripped) = cond.strip_prefix("OR ") {
2175                current_group.push(stripped.to_string());
2176            } else {
2177                if !current_group.is_empty() {
2178                    groups.push(std::mem::take(&mut current_group));
2179                }
2180                current_group.push(cond.clone());
2181            }
2182        }
2183        if !current_group.is_empty() {
2184            groups.push(current_group);
2185        }
2186
2187        let group_strs: Vec<String> = groups
2188            .iter()
2189            .map(|g| {
2190                if g.len() == 1 {
2191                    g[0].clone()
2192                } else {
2193                    format!("({})", g.join(" OR "))
2194                }
2195            })
2196            .collect();
2197
2198        // SAFETY: group_strs 来自 WhereCondition::Eq/Gt 等参数化变体渲染,值已绑定为 ? 占位符
2199        (format!(" WHERE {}", group_strs.join(" AND ")), params)
2200    }
2201
2202    /// 构建 SELECT SQL(参数绑定版本)。
2203    ///
2204    /// WHERE 子句中的值使用 `?` 占位符,值通过 `params` 返回。
2205    /// 适用于 `Connection::query_with_params()`。
2206    pub fn build_select_with_params(&self) -> (String, Vec<Value>) {
2207        let table = self
2208            .table
2209            .clone()
2210            .unwrap_or_else(|| M::table_name().to_string());
2211        let columns = if self.select_columns.is_empty() {
2212            "*".to_string()
2213        } else {
2214            self.select_columns.join(", ")
2215        };
2216
2217        let mut sql = format!("SELECT {} FROM {}", columns, self.dialect.quote(&table));
2218
2219        for join in &self.joins {
2220            match join {
2221                JoinClause::Inner(t, l, r) => {
2222                    let _ = write!(
2223                        sql,
2224                        " INNER JOIN {} ON {} = {}",
2225                        self.dialect.quote(t),
2226                        self.dialect.quote(l),
2227                        self.dialect.quote(r)
2228                    );
2229                }
2230                JoinClause::Left(t, l, r) => {
2231                    let _ = write!(
2232                        sql,
2233                        " LEFT JOIN {} ON {} = {}",
2234                        self.dialect.quote(t),
2235                        self.dialect.quote(l),
2236                        self.dialect.quote(r)
2237                    );
2238                }
2239                JoinClause::Right(t, l, r) => {
2240                    let _ = write!(
2241                        sql,
2242                        " RIGHT JOIN {} ON {} = {}",
2243                        self.dialect.quote(t),
2244                        self.dialect.quote(l),
2245                        self.dialect.quote(r)
2246                    );
2247                }
2248                JoinClause::Cross(t, on) => {
2249                    let _ = write!(
2250                        sql,
2251                        " CROSS JOIN {} ON {}",
2252                        self.dialect.quote(t),
2253                        self.dialect.quote(on)
2254                    );
2255                }
2256                JoinClause::Relation(kind, ft, fk, tt, tk) => {
2257                    let _ = write!(
2258                        sql,
2259                        " {} {} ON {}.{} = {}.{}",
2260                        kind.as_sql(),
2261                        self.dialect.quote(tt),
2262                        self.dialect.quote(ft),
2263                        self.dialect.quote(fk),
2264                        self.dialect.quote(tt),
2265                        self.dialect.quote(tk)
2266                    );
2267                }
2268            }
2269        }
2270
2271        let mut params = Vec::new();
2272        // P0-1:build_where_clause_with_params 内部已处理软删除条件
2273        let (where_clause, where_params) = self.build_where_clause_with_params();
2274        if !where_clause.is_empty() {
2275            sql.push_str(&where_clause);
2276            params = where_params;
2277        }
2278
2279        if !self.group_by.is_empty() {
2280            let cols: Vec<String> = self
2281                .group_by
2282                .iter()
2283                .map(|c| self.dialect.quote(c))
2284                .collect();
2285            sql.push_str(" GROUP BY ");
2286            sql.push_str(&cols.join(", "));
2287        }
2288
2289        if !self.having_conditions.is_empty() {
2290            sql.push_str(" HAVING ");
2291            for (i, cond) in self.having_conditions.iter().enumerate() {
2292                if i > 0 {
2293                    sql.push_str(" AND ");
2294                }
2295                match cond {
2296                    WhereCondition::And(c) => sql.push_str(c),
2297                    // M-5:参数化 HAVING,值走绑定参数
2298                    WhereCondition::Having(agg, op, value) => {
2299                        params.push(value.clone());
2300                        sql.push_str(&format!("{} {} ?", agg.render(&*self.dialect), op.as_sql()));
2301                    }
2302                    _ => {}
2303                }
2304            }
2305        }
2306
2307        if !self.order_by.is_empty() {
2308            let order_cols: Vec<String> = self
2309                .order_by
2310                .iter()
2311                .map(|o| {
2312                    let dir = match o.direction {
2313                        OrderDirection::Asc => " ASC",
2314                        OrderDirection::Desc => " DESC",
2315                    };
2316                    format!("{}{}", self.dialect.quote(&o.field), dir)
2317                })
2318                .collect();
2319            sql.push_str(" ORDER BY ");
2320            sql.push_str(&order_cols.join(", "));
2321        }
2322
2323        if let Some(limit) = self.limit_value {
2324            let _ = write!(sql, " LIMIT {}", limit);
2325        }
2326        if let Some(offset) = self.offset_value {
2327            let _ = write!(sql, " OFFSET {}", offset);
2328        }
2329
2330        // P2-3:追加行锁子句(TASK-025/026)
2331        if let Some(lock_type) = &self.lock_type {
2332            if let Some(lock_clause) = self.dialect.build_lock_clause(*lock_type) {
2333                sql.push(' ');
2334                sql.push_str(&lock_clause);
2335            }
2336        }
2337
2338        (sql, params)
2339    }
2340
2341    /// 构建 INSERT SQL(参数绑定版本)。
2342    pub fn build_insert_with_params(
2343        &self,
2344        data: &std::collections::HashMap<String, Value>,
2345    ) -> (String, Vec<Value>) {
2346        let table = self
2347            .table
2348            .clone()
2349            .unwrap_or_else(|| M::table_name().to_string());
2350        if data.is_empty() {
2351            return (String::new(), Vec::new());
2352        }
2353
2354        let mut columns = Vec::with_capacity(data.len());
2355        let mut params = Vec::with_capacity(data.len());
2356        let placeholders: Vec<&str> = data.iter().map(|_| "?").collect();
2357        for (k, v) in data.iter() {
2358            columns.push(self.dialect.quote(k));
2359            params.push(v.clone());
2360        }
2361
2362        // P2-4:INSERT OR IGNORE 前缀(TASK-027/028)
2363        let insert_clause = if self.insert_or_ignore {
2364            self.dialect.build_insert_or_ignore_prefix(&table)
2365        } else {
2366            format!("INSERT INTO {}", self.dialect.quote(&table))
2367        };
2368
2369        let sql = format!(
2370            "{} ({}) VALUES ({})",
2371            insert_clause,
2372            columns.join(", "),
2373            placeholders.join(", ")
2374        );
2375        (sql, params)
2376    }
2377
2378    /// P2-6:构建批量 INSERT SQL(参数绑定版本)。
2379    ///
2380    /// 生成 `INSERT INTO t (c1, c2) VALUES (?, ?), (?, ?), ...` 形式的多行插入 SQL。
2381    /// 所有行的列必须一致(取第一行的列顺序);空行列表返回空 SQL。
2382    ///
2383    /// **L3 实现深度**:使用参数化占位符 `?`,所有值通过 `params` 绑定,杜绝 SQL 注入。
2384    pub fn build_batch_insert_with_params(
2385        &self,
2386        rows: &[std::collections::HashMap<String, Value>],
2387    ) -> (String, Vec<Value>) {
2388        let table = self
2389            .table
2390            .clone()
2391            .unwrap_or_else(|| M::table_name().to_string());
2392        if rows.is_empty() {
2393            return (String::new(), Vec::new());
2394        }
2395
2396        // 取第一行的列作为列顺序(所有行必须一致)
2397        let first_row = &rows[0];
2398        let columns: Vec<String> = first_row.keys().cloned().collect();
2399        let quoted_columns: Vec<String> = columns.iter().map(|c| self.dialect.quote(c)).collect();
2400
2401        let mut params = Vec::with_capacity(rows.len() * columns.len());
2402        let mut value_groups: Vec<String> = Vec::with_capacity(rows.len());
2403        let is_pg = self.dialect.db_type() == DbType::PostgreSQL;
2404        let mut param_idx = 1usize;
2405        for row in rows {
2406            let placeholders: Vec<String> = columns
2407                .iter()
2408                .map(|col| match row.get(col) {
2409                    Some(v) => {
2410                        params.push(v.clone());
2411                        if is_pg {
2412                            let p = format!("${}", param_idx);
2413                            param_idx += 1;
2414                            p
2415                        } else {
2416                            "?".to_string()
2417                        }
2418                    }
2419                    None => "NULL".to_string(),
2420                })
2421                .collect();
2422            value_groups.push(format!("({})", placeholders.join(", ")));
2423        }
2424
2425        let sql = format!(
2426            "INSERT INTO {} ({}) VALUES {}",
2427            self.dialect.quote(&table),
2428            quoted_columns.join(", "),
2429            value_groups.join(", ")
2430        );
2431        (sql, params)
2432    }
2433
2434    /// P2-6:构建批量 Upsert SQL(参数绑定版本)。
2435    ///
2436    /// 在 `build_batch_insert_with_params` 基础上追加冲突处理子句:
2437    /// - MySQL: `ON DUPLICATE KEY UPDATE col=VALUES(col), ...`
2438    /// - PostgreSQL/SQLite: `ON CONFLICT (conflict_cols) DO UPDATE SET col=EXCLUDED.col, ...`
2439    /// - Oracle/SQL Server/ClickHouse/Db2: 返回 `Err(DbError::InvalidInput)`(不支持)
2440    ///
2441    /// # 参数
2442    /// - `rows`: 批量数据行(所有行的列必须一致)
2443    /// - `conflict_columns`: 冲突检测列(主键/唯一键);MySQL 自动检测可传空
2444    /// - `update_columns`: 冲突时更新的列;空切片表示更新所有非冲突列
2445    ///
2446    /// # 返回
2447    /// - `Ok((sql, params))`: 生成的 SQL 和参数列表
2448    /// - `Err(DbError::InvalidInput)`: 方言不支持 upsert 或 rows 为空
2449    ///
2450    /// **L3 实现深度**:
2451    /// 1. SQL 下推:冲突处理由数据库执行,非内存判断
2452    /// 2. 参数化:所有值通过 `?` 占位符绑定,不拼接用户值
2453    /// 3. 实际执行:生成标准 INSERT...ON CONFLICT/ON DUPLICATE KEY SQL
2454    pub fn build_batch_upsert_with_params(
2455        &self,
2456        rows: &[std::collections::HashMap<String, Value>],
2457        conflict_columns: &[&str],
2458        update_columns: &[&str],
2459    ) -> Result<(String, Vec<Value>), crate::DbError> {
2460        if rows.is_empty() {
2461            return Err(crate::DbError::InvalidInput(
2462                "build_batch_upsert_with_params: rows cannot be empty".to_string(),
2463            ));
2464        }
2465
2466        // 构建批量 INSERT 部分
2467        let (insert_sql, params) = self.build_batch_insert_with_params(rows);
2468        if insert_sql.is_empty() {
2469            return Err(crate::DbError::InvalidInput(
2470                "build_batch_upsert_with_params: failed to build INSERT part".to_string(),
2471            ));
2472        }
2473
2474        // 取所有列名(原始未 quote)
2475        let all_columns: Vec<String> = rows[0].keys().cloned().collect();
2476
2477        // 调用方言生成冲突处理子句
2478        let conflict_clause = self
2479            .dialect
2480            .build_upsert_on_conflict(conflict_columns, update_columns, &all_columns)
2481            .ok_or_else(|| {
2482                crate::DbError::InvalidInput(format!(
2483                    "build_batch_upsert_with_params: dialect {:?} does not support upsert (ON CONFLICT / ON DUPLICATE KEY UPDATE). Consider using MERGE statement or individual upserts instead.",
2484                    self.dialect.db_type()
2485                ))
2486            })?;
2487
2488        let sql = format!("{} {}", insert_sql, conflict_clause);
2489        Ok((sql, params))
2490    }
2491
2492    /// 构建 UPDATE SQL(参数绑定版本)。
2493    /// 参数顺序:SET 参数在前,WHERE 参数在后。
2494    pub fn build_update_with_params(
2495        &self,
2496        data: &std::collections::HashMap<String, Value>,
2497    ) -> (String, Vec<Value>) {
2498        let table = self
2499            .table
2500            .clone()
2501            .unwrap_or_else(|| M::table_name().to_string());
2502        if data.is_empty() {
2503            return (String::new(), Vec::new());
2504        }
2505
2506        let mut set_clauses = Vec::with_capacity(data.len());
2507        let mut params = Vec::with_capacity(data.len());
2508        for (k, v) in data.iter() {
2509            set_clauses.push(format!("{} = ?", self.dialect.quote(k)));
2510            params.push(v.clone());
2511        }
2512
2513        let mut sql = format!(
2514            "UPDATE {} SET {}",
2515            self.dialect.quote(&table),
2516            set_clauses.join(", ")
2517        );
2518
2519        // P0-1:build_where_clause_with_params 内部已处理软删除条件
2520        let (where_clause, where_params) = self.build_where_clause_with_params();
2521        if !where_clause.is_empty() {
2522            sql.push_str(&where_clause);
2523            params.extend(where_params);
2524        }
2525
2526        (sql, params)
2527    }
2528
2529    /// 构建 DELETE SQL(参数绑定版本)。
2530    ///
2531    /// **P0-1 软删除集成(v1.3.0+)**:当 Model 启用软删除时,自动生成
2532    /// `UPDATE {table} SET {field} = NOW() WHERE ...` 而非 `DELETE FROM ...`。
2533    /// 参数列表为空(NOW() 由数据库填充)。
2534    pub fn build_delete_with_params(&self) -> (String, Vec<Value>) {
2535        let table = self
2536            .table
2537            .clone()
2538            .unwrap_or_else(|| M::table_name().to_string());
2539
2540        // P0-1:软删除启用时转为 UPDATE SET {field} = NOW()
2541        if let Some(field) = self.soft_delete_field() {
2542            let (where_clause, where_params) = self.build_where_clause_with_params();
2543            let sql = format!(
2544                "UPDATE {} SET {} = NOW(){}",
2545                self.dialect.quote(&table),
2546                self.dialect.quote(field),
2547                where_clause
2548            );
2549            return (sql, where_params);
2550        }
2551
2552        let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
2553        let mut params = Vec::new();
2554
2555        let (where_clause, where_params) = self.build_where_clause_with_params();
2556        if !where_clause.is_empty() {
2557            sql.push_str(&where_clause);
2558            params = where_params;
2559        }
2560
2561        (sql, params)
2562    }
2563
2564    /// 构建物理 DELETE SQL(参数绑定版本,绕过软删除)。
2565    ///
2566    /// 即使 Model 启用软删除,也生成 `DELETE FROM ...`,且不追加软删除过滤。
2567    pub fn build_force_delete_with_params(&self) -> (String, Vec<Value>) {
2568        let table = self
2569            .table
2570            .clone()
2571            .unwrap_or_else(|| M::table_name().to_string());
2572
2573        let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
2574        let mut params = Vec::new();
2575
2576        // P0-1:物理删除不追加软删除过滤,使用 build_where_clause_with_params_no_soft_delete
2577        let (where_clause, where_params) = self.build_where_clause_with_params_options(false);
2578        if !where_clause.is_empty() {
2579            sql.push_str(&where_clause);
2580            params = where_params;
2581        }
2582
2583        (sql, params)
2584    }
2585
2586    /// 构建 COUNT 查询 SQL,返回 `SELECT COUNT(*) as total FROM <table> <where>`
2587    pub fn build_count(&self) -> String {
2588        let table = self
2589            .table
2590            .clone()
2591            .unwrap_or_else(|| M::table_name().to_string());
2592
2593        let mut sql = format!(
2594            "SELECT COUNT(*) as total FROM {}",
2595            self.dialect.quote(&table)
2596        );
2597        sql.push_str(&self.build_where_clause());
2598        sql
2599    }
2600
2601    /// 构建 EXISTS 查询 SQL,返回 `SELECT EXISTS(SELECT 1 FROM <table> <where> LIMIT 1)`
2602    pub fn build_exists(&self) -> String {
2603        let table = self
2604            .table
2605            .clone()
2606            .unwrap_or_else(|| M::table_name().to_string());
2607
2608        let mut sql = format!("SELECT 1 FROM {}", self.dialect.quote(&table));
2609        sql.push_str(&self.build_where_clause());
2610        sql.push_str(" LIMIT 1");
2611        format!("SELECT EXISTS({})", sql)
2612    }
2613
2614    /// 构建 MAX 聚合查询 SQL,返回 `SELECT MAX(<field>) as max_val FROM <table> <where>`
2615    pub fn build_max(&self, field: &str) -> String {
2616        let table = self
2617            .table
2618            .clone()
2619            .unwrap_or_else(|| M::table_name().to_string());
2620
2621        let mut sql = format!(
2622            "SELECT MAX({}) as max_val FROM {}",
2623            self.dialect.quote(field),
2624            self.dialect.quote(&table)
2625        );
2626        sql.push_str(&self.build_where_clause());
2627        sql
2628    }
2629
2630    /// 构建 MIN 聚合查询 SQL,返回 `SELECT MIN(<field>) as min_val FROM <table> <where>`
2631    pub fn build_min(&self, field: &str) -> String {
2632        let table = self
2633            .table
2634            .clone()
2635            .unwrap_or_else(|| M::table_name().to_string());
2636
2637        let mut sql = format!(
2638            "SELECT MIN({}) as min_val FROM {}",
2639            self.dialect.quote(field),
2640            self.dialect.quote(&table)
2641        );
2642        sql.push_str(&self.build_where_clause());
2643        sql
2644    }
2645
2646    /// 构建 SUM 聚合查询 SQL,返回 `SELECT SUM(<field>) as sum_val FROM <table> <where>`
2647    pub fn build_sum(&self, field: &str) -> String {
2648        let table = self
2649            .table
2650            .clone()
2651            .unwrap_or_else(|| M::table_name().to_string());
2652
2653        let mut sql = format!(
2654            "SELECT SUM({}) as sum_val FROM {}",
2655            self.dialect.quote(field),
2656            self.dialect.quote(&table)
2657        );
2658        sql.push_str(&self.build_where_clause());
2659        sql
2660    }
2661
2662    /// 构建 AVG 聚合查询 SQL,返回 `SELECT AVG(<field>) as avg_val FROM <table> <where>`
2663    pub fn build_avg(&self, field: &str) -> String {
2664        let table = self
2665            .table
2666            .clone()
2667            .unwrap_or_else(|| M::table_name().to_string());
2668
2669        let mut sql = format!(
2670            "SELECT AVG({}) as avg_val FROM {}",
2671            self.dialect.quote(field),
2672            self.dialect.quote(&table)
2673        );
2674        sql.push_str(&self.build_where_clause());
2675        sql
2676    }
2677
2678    /// 校验生成的 SELECT SQL 语句
2679    /// 检查 SQL 语法、JOIN 列名、表名合法性
2680    pub fn validate(&self) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
2681        let sql = self.sql();
2682        let mut errors = Vec::new();
2683
2684        if let Err(e) = sz_orm_sql_validator::validate_select(&sql) {
2685            errors.push(e);
2686        }
2687
2688        // 校验 JOIN 子句产生的 SQL 是否合法
2689        if !self.joins.is_empty() {
2690            for join in &self.joins {
2691                match join {
2692                    JoinClause::Inner(_, left, right)
2693                    | JoinClause::Left(_, left, right)
2694                    | JoinClause::Right(_, left, right) => {
2695                        if let Err(e) = sz_orm_sql_validator::validate_column_name(left) {
2696                            errors.push(e);
2697                        }
2698                        if let Err(e) = sz_orm_sql_validator::validate_column_name(right) {
2699                            errors.push(e);
2700                        }
2701                    }
2702                    JoinClause::Relation(_, ft, fk, tt, tk) => {
2703                        for ident in [ft.as_str(), fk.as_str(), tt.as_str(), tk.as_str()] {
2704                            if let Err(e) = sz_orm_sql_validator::validate_column_name(ident) {
2705                                errors.push(e);
2706                            }
2707                        }
2708                    }
2709                    _ => {}
2710                }
2711            }
2712        }
2713
2714        // 校验表名合法性
2715        let table = self
2716            .table
2717            .clone()
2718            .unwrap_or_else(|| M::table_name().to_string());
2719        if let Err(e) = sz_orm_sql_validator::validate_table_name(&table) {
2720            errors.push(e);
2721        }
2722
2723        if errors.is_empty() {
2724            Ok(())
2725        } else {
2726            Err(errors)
2727        }
2728    }
2729
2730    /// 校验生成的 INSERT SQL 语句
2731    /// 含空数据检测(EmptyInsertData 错误)
2732    pub fn validate_insert(
2733        &self,
2734        data: &std::collections::HashMap<String, Value>,
2735    ) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
2736        let sql = self.sql_insert(data);
2737        let mut errors = Vec::new();
2738
2739        if sql.is_empty() {
2740            errors.push(sz_orm_sql_validator::SqlValidationError::EmptyInsertData);
2741            return Err(errors);
2742        }
2743
2744        if let Err(e) = sz_orm_sql_validator::validate_insert(&sql) {
2745            errors.push(e);
2746        }
2747
2748        if errors.is_empty() {
2749            Ok(())
2750        } else {
2751            Err(errors)
2752        }
2753    }
2754
2755    /// 校验生成的 UPDATE SQL 语句
2756    /// 含空数据检测(EmptyUpdateData 错误)
2757    pub fn validate_update(
2758        &self,
2759        data: &std::collections::HashMap<String, Value>,
2760    ) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
2761        let sql = self.sql_update(data);
2762        let mut errors = Vec::new();
2763
2764        if sql.is_empty() {
2765            errors.push(sz_orm_sql_validator::SqlValidationError::EmptyUpdateData);
2766            return Err(errors);
2767        }
2768
2769        if let Err(e) = sz_orm_sql_validator::validate_update(&sql) {
2770            errors.push(e);
2771        }
2772
2773        if errors.is_empty() {
2774            Ok(())
2775        } else {
2776            Err(errors)
2777        }
2778    }
2779
2780    /// 校验生成的 DELETE SQL 语句
2781    pub fn validate_delete(&self) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
2782        let sql = self.sql_delete();
2783        let mut errors = Vec::new();
2784
2785        if let Err(e) = sz_orm_sql_validator::validate_delete(&sql) {
2786            errors.push(e);
2787        }
2788
2789        if errors.is_empty() {
2790            Ok(())
2791        } else {
2792            Err(errors)
2793        }
2794    }
2795}
2796
2797/// QueryBuilder 方法 requiring ModelExt(v2.2.0 B-3 select_exclude)
2798impl<M: Model + crate::model::ModelExt> QueryBuilder<M> {
2799    /// 排除指定字段查询(v2.2.0 B-3,与 `select_only` 互补)
2800    ///
2801    /// 从实体全部列中减去排除列,设置 Partial 模式查询保留列。
2802    ///
2803    /// # 错误
2804    ///
2805    /// - 排除不存在的字段 → `Err(DbError::InvalidInput)`
2806    /// - 排除所有字段 → `Err(DbError::InvalidInput)`
2807    ///
2808    /// # 示例
2809    ///
2810    /// ```ignore
2811    /// let sql = User::find()
2812    ///     .select_exclude(&["avatar", "blob_data"])?
2813    ///     .build_select();
2814    /// // SELECT id, name, email FROM users(排除 avatar 和 blob_data)
2815    /// ```
2816    pub fn select_exclude(mut self, fields: &[&str]) -> Result<Self, crate::DbError> {
2817        let all_columns = M::columns();
2818        let exclude_set: std::collections::HashSet<&str> = fields.iter().copied().collect();
2819
2820        for field in fields {
2821            if !all_columns.contains(field) {
2822                return Err(crate::DbError::InvalidInput(format!(
2823                    "排除的字段不存在: {}",
2824                    field
2825                )));
2826            }
2827        }
2828
2829        let retained: Vec<String> = all_columns
2830            .into_iter()
2831            .filter(|c| !exclude_set.contains(*c))
2832            .map(|s| s.to_string())
2833            .collect();
2834
2835        if retained.is_empty() {
2836            return Err(crate::DbError::InvalidInput("不能排除所有字段".to_string()));
2837        }
2838
2839        self.select_mode = crate::partial_model::SelectMode::Partial;
2840        self.select_columns = retained;
2841        Ok(self)
2842    }
2843}
2844
2845impl<M: Model> fmt::Debug for QueryBuilder<M> {
2846    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2847        f.debug_struct("QueryBuilder")
2848            .field("table", &self.table)
2849            .field("select_columns", &self.select_columns)
2850            .field("where_conditions", &self.where_conditions.len())
2851            .field("limit", &self.limit_value)
2852            .finish()
2853    }
2854}
2855
2856#[cfg(test)]
2857#[allow(deprecated)]
2858mod tests {
2859    use super::*;
2860    use crate::db_type::DbType;
2861    use crate::dialect::get_dialect;
2862
2863    struct TestModel;
2864    impl Model for TestModel {
2865        type PrimaryKey = i64;
2866
2867        fn table_name() -> &'static str {
2868            "test_models"
2869        }
2870
2871        fn pk(&self) -> Self::PrimaryKey {
2872            1
2873        }
2874
2875        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2876    }
2877
2878    #[test]
2879    fn test_query_builder_select() -> Result<(), crate::DbError> {
2880        let dialect = get_dialect(DbType::MySQL)?;
2881        let builder = QueryBuilder::<TestModel>::new(dialect);
2882
2883        let sql = builder.table("users").select(vec!["id", "name"])?.sql();
2884        assert!(sql.contains("SELECT `id`, `name` FROM"));
2885        assert!(sql.contains("`users`"));
2886        Ok(())
2887    }
2888
2889    #[test]
2890    fn test_query_builder_where() -> Result<(), crate::DbError> {
2891        let dialect = get_dialect(DbType::MySQL)?;
2892        let builder = QueryBuilder::<TestModel>::new(dialect);
2893
2894        let sql = builder
2895            .table("users")
2896            .where_eq("status", crate::value::Value::String("active".into()))
2897            .where_gt("age", crate::value::Value::I64(18))
2898            .sql();
2899
2900        assert!(sql.contains("WHERE"));
2901        assert!(sql.contains("`status` = 'active'"));
2902        assert!(sql.contains("`age` > 18"));
2903        Ok(())
2904    }
2905
2906    #[test]
2907    fn test_query_builder_order_by() -> Result<(), crate::DbError> {
2908        let dialect = get_dialect(DbType::MySQL)?;
2909        let builder = QueryBuilder::<TestModel>::new(dialect);
2910
2911        let sql = builder
2912            .table("users")
2913            .order_by("created_at")
2914            .order_desc("id")
2915            .sql();
2916
2917        assert!(sql.contains("ORDER BY"));
2918        assert!(sql.contains("`created_at` ASC"));
2919        assert!(sql.contains("`id` DESC"));
2920        Ok(())
2921    }
2922
2923    #[test]
2924    fn test_query_builder_limit_offset() -> Result<(), crate::DbError> {
2925        let dialect = get_dialect(DbType::MySQL)?;
2926        let builder = QueryBuilder::<TestModel>::new(dialect);
2927
2928        let sql = builder.table("users").limit(10).offset(20).sql();
2929
2930        assert!(sql.contains("LIMIT 10"));
2931        assert!(sql.contains("OFFSET 20"));
2932        Ok(())
2933    }
2934
2935    #[test]
2936    fn test_query_builder_page() -> Result<(), crate::DbError> {
2937        let dialect = get_dialect(DbType::MySQL)?;
2938        let builder = QueryBuilder::<TestModel>::new(dialect);
2939
2940        let sql = builder.table("users").page(3, 20).sql();
2941
2942        assert!(sql.contains("LIMIT 20"));
2943        assert!(sql.contains("OFFSET 40"));
2944        Ok(())
2945    }
2946
2947    #[test]
2948    fn test_query_builder_insert() -> Result<(), crate::DbError> {
2949        let dialect = get_dialect(DbType::MySQL)?;
2950        let builder = QueryBuilder::<TestModel>::new(dialect);
2951
2952        let mut data = std::collections::HashMap::new();
2953        data.insert("name".to_string(), Value::String("test".to_string()));
2954        data.insert("age".to_string(), Value::I64(25));
2955
2956        let sql = builder.table("users").sql_insert(&data);
2957
2958        assert!(sql.contains("INSERT INTO"));
2959        assert!(sql.contains("`name`"));
2960        assert!(sql.contains("'test'"));
2961        Ok(())
2962    }
2963
2964    #[test]
2965    fn test_query_builder_update() -> Result<(), crate::DbError> {
2966        let dialect = get_dialect(DbType::MySQL)?;
2967        let builder = QueryBuilder::<TestModel>::new(dialect);
2968
2969        let mut data = std::collections::HashMap::new();
2970        data.insert("name".to_string(), Value::String("updated".to_string()));
2971
2972        let sql = builder
2973            .table("users")
2974            .where_eq("id", Value::I64(1))
2975            .sql_update(&data);
2976
2977        assert!(sql.contains("UPDATE"));
2978        assert!(sql.contains("`name` = 'updated'"));
2979        assert!(sql.contains("WHERE"));
2980        Ok(())
2981    }
2982
2983    #[test]
2984    fn test_query_builder_delete() -> Result<(), crate::DbError> {
2985        let dialect = get_dialect(DbType::MySQL)?;
2986        let builder = QueryBuilder::<TestModel>::new(dialect);
2987
2988        let sql = builder
2989            .table("users")
2990            .where_eq("id", Value::I64(1))
2991            .sql_delete();
2992
2993        assert!(sql.contains("DELETE FROM"));
2994        assert!(sql.contains("WHERE"));
2995        Ok(())
2996    }
2997
2998    #[test]
2999    fn test_query_builder_count() -> Result<(), crate::DbError> {
3000        let dialect = get_dialect(DbType::MySQL)?;
3001        let builder = QueryBuilder::<TestModel>::new(dialect);
3002
3003        let sql = builder.table("users").build_count();
3004
3005        assert!(sql.contains("SELECT COUNT(*)"));
3006        assert!(sql.contains("FROM"));
3007        Ok(())
3008    }
3009
3010    #[test]
3011    fn test_query_builder_where_in() -> Result<(), crate::DbError> {
3012        let dialect = get_dialect(DbType::MySQL)?;
3013        let builder = QueryBuilder::<TestModel>::new(dialect);
3014
3015        let sql = builder
3016            .table("users")
3017            .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
3018            .sql();
3019
3020        assert!(sql.contains("IN ("));
3021        Ok(())
3022    }
3023
3024    #[test]
3025    fn test_query_builder_where_between() -> Result<(), crate::DbError> {
3026        let dialect = get_dialect(DbType::MySQL)?;
3027        let builder = QueryBuilder::<TestModel>::new(dialect);
3028
3029        let sql = builder
3030            .table("users")
3031            .where_between("age", Value::I64(18), Value::I64(30))
3032            .sql();
3033
3034        assert!(sql.contains("BETWEEN"));
3035        Ok(())
3036    }
3037
3038    #[test]
3039    fn test_query_builder_where_null() -> Result<(), crate::DbError> {
3040        let dialect = get_dialect(DbType::MySQL)?;
3041        let builder = QueryBuilder::<TestModel>::new(dialect);
3042
3043        let sql = builder.table("users").where_null("deleted_at").sql();
3044
3045        assert!(sql.contains("IS NULL"));
3046        Ok(())
3047    }
3048
3049    #[test]
3050    fn test_query_builder_join() -> Result<(), crate::DbError> {
3051        let dialect = get_dialect(DbType::MySQL)?;
3052        let builder = QueryBuilder::<TestModel>::new(dialect);
3053
3054        let sql = builder
3055            .table("users")
3056            .join_inner("posts", "users.id", "posts.user_id")
3057            .sql();
3058
3059        assert!(sql.contains("INNER JOIN"));
3060        assert!(sql.contains("`posts`"));
3061        Ok(())
3062    }
3063
3064    #[test]
3065    fn test_query_builder_group_by() -> Result<(), crate::DbError> {
3066        let dialect = get_dialect(DbType::MySQL)?;
3067        let builder = QueryBuilder::<TestModel>::new(dialect);
3068
3069        let sql = builder.table("users").group_by("status").sql();
3070
3071        assert!(sql.contains("GROUP BY"));
3072        assert!(sql.contains("`status`"));
3073        Ok(())
3074    }
3075
3076    #[test]
3077    fn test_query_builder_max() -> Result<(), crate::DbError> {
3078        let dialect = get_dialect(DbType::MySQL)?;
3079        let builder = QueryBuilder::<TestModel>::new(dialect);
3080
3081        let sql = builder.table("users").build_max("score");
3082
3083        assert!(sql.contains("MAX("));
3084        assert!(sql.contains("`score`"));
3085        Ok(())
3086    }
3087
3088    #[test]
3089    fn test_query_builder_min() -> Result<(), crate::DbError> {
3090        let dialect = get_dialect(DbType::MySQL)?;
3091        let builder = QueryBuilder::<TestModel>::new(dialect);
3092
3093        let sql = builder.table("users").build_min("price");
3094
3095        assert!(sql.contains("MIN("));
3096        assert!(sql.contains("`price`"));
3097        Ok(())
3098    }
3099
3100    #[test]
3101    fn test_query_builder_sum() -> Result<(), crate::DbError> {
3102        let dialect = get_dialect(DbType::MySQL)?;
3103        let builder = QueryBuilder::<TestModel>::new(dialect);
3104
3105        let sql = builder.table("orders").build_sum("amount");
3106
3107        assert!(sql.contains("SUM("));
3108        assert!(sql.contains("`amount`"));
3109        Ok(())
3110    }
3111
3112    #[test]
3113    fn test_query_builder_avg() -> Result<(), crate::DbError> {
3114        let dialect = get_dialect(DbType::MySQL)?;
3115        let builder = QueryBuilder::<TestModel>::new(dialect);
3116
3117        let sql = builder.table("scores").build_avg("value");
3118
3119        assert!(sql.contains("AVG("));
3120        assert!(sql.contains("`value`"));
3121        Ok(())
3122    }
3123
3124    #[test]
3125    fn test_validator_select() -> Result<(), crate::DbError> {
3126        let dialect = get_dialect(DbType::MySQL)?;
3127        let builder = QueryBuilder::<TestModel>::new(dialect);
3128
3129        let result = builder
3130            .table("users")
3131            .select(vec!["id", "name"])?
3132            .validate();
3133        assert!(result.is_ok());
3134        Ok(())
3135    }
3136
3137    #[test]
3138    fn test_validator_select_with_join() -> Result<(), crate::DbError> {
3139        let dialect = get_dialect(DbType::MySQL)?;
3140        let builder = QueryBuilder::<TestModel>::new(dialect);
3141
3142        let result = builder
3143            .table("users")
3144            .join_inner("posts", "users.id", "posts.user_id")
3145            .validate();
3146        assert!(result.is_ok());
3147        Ok(())
3148    }
3149
3150    #[test]
3151    fn test_validator_insert() -> Result<(), crate::DbError> {
3152        let dialect = get_dialect(DbType::MySQL)?;
3153        let builder = QueryBuilder::<TestModel>::new(dialect);
3154
3155        let mut data = std::collections::HashMap::new();
3156        data.insert("name".to_string(), Value::String("test".to_string()));
3157
3158        let result = builder.table("users").validate_insert(&data);
3159        assert!(result.is_ok());
3160        Ok(())
3161    }
3162
3163    #[test]
3164    fn test_validator_insert_empty_data() -> Result<(), crate::DbError> {
3165        let dialect = get_dialect(DbType::MySQL)?;
3166        let builder = QueryBuilder::<TestModel>::new(dialect);
3167
3168        let data = std::collections::HashMap::new();
3169        let result = builder.table("users").validate_insert(&data);
3170        assert!(result.is_err());
3171        Ok(())
3172    }
3173
3174    #[test]
3175    fn test_validator_update() -> Result<(), crate::DbError> {
3176        let dialect = get_dialect(DbType::MySQL)?;
3177        let builder = QueryBuilder::<TestModel>::new(dialect);
3178
3179        let mut data = std::collections::HashMap::new();
3180        data.insert("name".to_string(), Value::String("updated".to_string()));
3181
3182        let result = builder.table("users").validate_update(&data);
3183        assert!(result.is_ok());
3184        Ok(())
3185    }
3186
3187    #[test]
3188    fn test_validator_update_empty_data() -> Result<(), crate::DbError> {
3189        let dialect = get_dialect(DbType::MySQL)?;
3190        let builder = QueryBuilder::<TestModel>::new(dialect);
3191
3192        let data = std::collections::HashMap::new();
3193        let result = builder.table("users").validate_update(&data);
3194        assert!(result.is_err());
3195        Ok(())
3196    }
3197
3198    #[test]
3199    fn test_validator_delete() -> Result<(), crate::DbError> {
3200        let dialect = get_dialect(DbType::MySQL)?;
3201        let builder = QueryBuilder::<TestModel>::new(dialect);
3202
3203        let result = builder
3204            .table("users")
3205            .where_eq("id", Value::I64(1))
3206            .validate_delete();
3207        assert!(result.is_ok());
3208        Ok(())
3209    }
3210
3211    #[test]
3212    fn test_validator_delete_no_where() -> Result<(), crate::DbError> {
3213        let dialect = get_dialect(DbType::MySQL)?;
3214        let builder = QueryBuilder::<TestModel>::new(dialect);
3215
3216        // DELETE without WHERE still produces valid SQL (just no filter)
3217        let result = builder.table("users").validate_delete();
3218        assert!(result.is_ok());
3219        Ok(())
3220    }
3221
3222    // ==================== M-3 select_quoted 测试 ====================
3223
3224    #[test]
3225    fn test_m3_select_quoted_valid_columns() -> Result<(), crate::DbError> {
3226        let dialect = get_dialect(DbType::MySQL)?;
3227        let builder = QueryBuilder::<TestModel>::new(dialect);
3228        let builder = builder.table("users").select_quoted(vec!["id", "name"])?;
3229        let sql = builder.sql();
3230        // 应自动 quote 列名
3231        assert!(sql.contains("SELECT `id`, `name` FROM"));
3232        assert!(sql.contains("`users`"));
3233        Ok(())
3234    }
3235
3236    #[test]
3237    fn test_m3_select_quoted_rejects_sql_injection() -> Result<(), crate::DbError> {
3238        let dialect = get_dialect(DbType::MySQL)?;
3239        let builder = QueryBuilder::<TestModel>::new(dialect);
3240
3241        // SQL 注入尝试:分号 + DROP TABLE
3242        let result = builder
3243            .table("users")
3244            .select_quoted(vec!["id; DROP TABLE users"]);
3245        assert!(result.is_err());
3246
3247        // 含引号
3248        let dialect = get_dialect(DbType::MySQL)?;
3249        let builder = QueryBuilder::<TestModel>::new(dialect);
3250        let result = builder.table("users").select_quoted(vec!["name'"]);
3251        assert!(result.is_err());
3252
3253        // 数字开头
3254        let dialect = get_dialect(DbType::MySQL)?;
3255        let builder = QueryBuilder::<TestModel>::new(dialect);
3256        let result = builder.table("users").select_quoted(vec!["1col"]);
3257        assert!(result.is_err());
3258
3259        // 含空格
3260        let dialect = get_dialect(DbType::MySQL)?;
3261        let builder = QueryBuilder::<TestModel>::new(dialect);
3262        let result = builder.table("users").select_quoted(vec!["col name"]);
3263        assert!(result.is_err());
3264        Ok(())
3265    }
3266
3267    #[test]
3268    fn test_m3_select_quoted_postgresql_dialect() -> Result<(), crate::DbError> {
3269        let dialect = get_dialect(DbType::PostgreSQL)?;
3270        let builder = QueryBuilder::<TestModel>::new(dialect);
3271        let builder = builder.table("users").select_quoted(vec!["id", "name"])?;
3272        let sql = builder.sql();
3273        // PostgreSQL 使用双引号
3274        assert!(sql.contains("SELECT \"id\", \"name\" FROM"));
3275        assert!(sql.contains("\"users\""));
3276        Ok(())
3277    }
3278
3279    // ==================== P0-1 软删除集成行为测试 ====================
3280
3281    /// 软删除测试模型:实现 soft_delete_field() 返回 "deleted_at"
3282    struct SoftDeleteModel;
3283    impl Model for SoftDeleteModel {
3284        type PrimaryKey = i64;
3285
3286        fn table_name() -> &'static str {
3287            "soft_users"
3288        }
3289
3290        fn pk(&self) -> Self::PrimaryKey {
3291            1
3292        }
3293
3294        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
3295
3296        fn soft_delete_field() -> Option<&'static str> {
3297            Some("deleted_at")
3298        }
3299    }
3300
3301    /// 行为级测试 L3-1:软删除模型 build_select 自动追加 `WHERE deleted_at IS NULL`
3302    ///
3303    /// 用户视角:查询软删除模型时,自动过滤已删除记录,无需手动写条件。
3304    #[test]
3305    fn test_p01_soft_delete_select_auto_filter() -> Result<(), crate::DbError> {
3306        let dialect = get_dialect(DbType::MySQL)?;
3307        let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
3308        let sql = builder.table("soft_users").sql();
3309        // 必须自动追加软删除过滤
3310        assert!(
3311            sql.contains("`deleted_at` IS NULL"),
3312            "软删除模型 SELECT 必须自动追加 `deleted_at` IS NULL,实际: {}",
3313            sql
3314        );
3315        Ok(())
3316    }
3317
3318    /// 行为级测试 L3-2:软删除模型 + 用户 WHERE 条件,软删除条件以 AND 追加
3319    #[test]
3320    fn test_p01_soft_delete_select_with_user_where() -> Result<(), crate::DbError> {
3321        let dialect = get_dialect(DbType::MySQL)?;
3322        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
3323            .table("soft_users")
3324            .where_eq("status", Value::String("active".into()))
3325            .sql();
3326        // 用户条件 + 软删除条件 同时存在
3327        assert!(sql.contains("`status` = "), "用户条件应保留: {}", sql);
3328        assert!(
3329            sql.contains("`deleted_at` IS NULL"),
3330            "软删除条件应自动追加: {}",
3331            sql
3332        );
3333        Ok(())
3334    }
3335
3336    /// 行为级测试 L3-3:without_soft_delete() 临时禁用软删除过滤
3337    ///
3338    /// 用户视角:管理员查询已删除记录时,可禁用自动过滤。
3339    #[test]
3340    fn test_p01_soft_delete_without_soft_delete() -> Result<(), crate::DbError> {
3341        let dialect = get_dialect(DbType::MySQL)?;
3342        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
3343            .table("soft_users")
3344            .without_soft_delete()
3345            .sql();
3346        // 不应包含软删除过滤
3347        assert!(
3348            !sql.contains("`deleted_at` IS NULL"),
3349            "without_soft_delete 应禁用过滤,实际: {}",
3350            sql
3351        );
3352        // 也应无 WHERE 子句(因为用户未提供任何条件)
3353        assert!(
3354            !sql.contains("WHERE"),
3355            "无用户条件 + 禁用软删除应无 WHERE 子句: {}",
3356            sql
3357        );
3358        Ok(())
3359    }
3360
3361    /// 行为级测试 L3-4:软删除模型 build_delete 自动转为 UPDATE
3362    ///
3363    /// 用户视角:调用 delete 实际是软删除 UPDATE,不是物理 DELETE。
3364    #[test]
3365    fn test_p01_soft_delete_delete_becomes_update() -> Result<(), crate::DbError> {
3366        let dialect = get_dialect(DbType::MySQL)?;
3367        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
3368            .table("soft_users")
3369            .where_eq("id", Value::I64(42))
3370            .sql_delete();
3371        // 应生成 UPDATE 而非 DELETE
3372        assert!(
3373            sql.starts_with("UPDATE"),
3374            "软删除模型的 build_delete 应生成 UPDATE,实际: {}",
3375            sql
3376        );
3377        assert!(
3378            !sql.contains("DELETE FROM"),
3379            "不应生成 DELETE FROM: {}",
3380            sql
3381        );
3382        assert!(
3383            sql.contains("`deleted_at` = NOW()"),
3384            "应设置 deleted_at = NOW(): {}",
3385            sql
3386        );
3387        // 软删除条件应自动追加,防止更新已删除记录
3388        assert!(
3389            sql.contains("`deleted_at` IS NULL"),
3390            "软删除 UPDATE 应追加 deleted_at IS NULL 防止重复删除: {}",
3391            sql
3392        );
3393        Ok(())
3394    }
3395
3396    /// 行为级测试 L3-5:build_force_delete 物理删除,不追加软删除过滤
3397    ///
3398    /// 用户视角:管理员强制清除时使用 build_force_delete。
3399    #[test]
3400    fn test_p01_soft_delete_force_delete() -> Result<(), crate::DbError> {
3401        let dialect = get_dialect(DbType::MySQL)?;
3402        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
3403            .table("soft_users")
3404            .where_eq("id", Value::I64(99))
3405            .build_force_delete();
3406        // 应生成 DELETE FROM
3407        assert!(
3408            sql.starts_with("DELETE FROM"),
3409            "build_force_delete 应生成 DELETE FROM,实际: {}",
3410            sql
3411        );
3412        // 不应追加软删除过滤
3413        assert!(
3414            !sql.contains("`deleted_at` IS NULL"),
3415            "物理删除不应追加软删除过滤: {}",
3416            sql
3417        );
3418        Ok(())
3419    }
3420
3421    /// 行为级测试 L3-6:build_select_with_params 自动追加软删除条件(参数化版本)
3422    #[test]
3423    fn test_p01_soft_delete_select_with_params() -> Result<(), crate::DbError> {
3424        let dialect = get_dialect(DbType::MySQL)?;
3425        let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
3426            .table("soft_users")
3427            .where_eq("id", Value::I64(1))
3428            .build_select_with_params();
3429        assert!(
3430            sql.contains("`deleted_at` IS NULL"),
3431            "参数化版本也应自动追加软删除: {}",
3432            sql
3433        );
3434        assert_eq!(params.len(), 1, "参数应为 1 个(用户 where_eq 的值)");
3435        assert_eq!(params[0], Value::I64(1));
3436        Ok(())
3437    }
3438
3439    /// 行为级测试 L3-7:build_delete_with_params 自动转为 UPDATE
3440    #[test]
3441    fn test_p01_soft_delete_delete_with_params_becomes_update() -> Result<(), crate::DbError> {
3442        let dialect = get_dialect(DbType::MySQL)?;
3443        let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
3444            .table("soft_users")
3445            .where_eq("id", Value::I64(7))
3446            .build_delete_with_params();
3447        assert!(sql.starts_with("UPDATE"), "应生成 UPDATE: {}", sql);
3448        assert!(
3449            sql.contains("`deleted_at` = NOW()"),
3450            "应设置 NOW(): {}",
3451            sql
3452        );
3453        assert_eq!(params.len(), 1, "参数应为 1 个(WHERE 的值)");
3454        Ok(())
3455    }
3456
3457    /// 行为级测试 L3-8:build_force_delete_with_params 物理删除(参数化版本)
3458    #[test]
3459    fn test_p01_soft_delete_force_delete_with_params() -> Result<(), crate::DbError> {
3460        let dialect = get_dialect(DbType::MySQL)?;
3461        let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
3462            .table("soft_users")
3463            .where_eq("id", Value::I64(11))
3464            .build_force_delete_with_params();
3465        assert!(sql.starts_with("DELETE FROM"), "应生成 DELETE: {}", sql);
3466        assert!(
3467            !sql.contains("`deleted_at` IS NULL"),
3468            "不应追加软删除过滤: {}",
3469            sql
3470        );
3471        assert_eq!(params.len(), 1);
3472        Ok(())
3473    }
3474
3475    /// 行为级测试 L3-9:非软删除模型 TestModel 不追加软删除条件
3476    ///
3477    /// 用户视角:未启用软删除的模型行为不变。
3478    #[test]
3479    fn test_p01_non_soft_delete_model_unchanged() -> Result<(), crate::DbError> {
3480        let dialect = get_dialect(DbType::MySQL)?;
3481        let sql = QueryBuilder::<TestModel>::new(dialect)
3482            .table("users")
3483            .where_eq("id", Value::I64(1))
3484            .sql();
3485        assert!(
3486            !sql.contains("deleted_at"),
3487            "非软删除模型不应追加 deleted_at: {}",
3488            sql
3489        );
3490        // build_delete 仍生成 DELETE FROM
3491        let dialect = get_dialect(DbType::MySQL)?;
3492        let del_sql = QueryBuilder::<TestModel>::new(dialect)
3493            .table("users")
3494            .where_eq("id", Value::I64(1))
3495            .sql_delete();
3496        assert!(
3497            del_sql.starts_with("DELETE FROM"),
3498            "非软删除模型 build_delete 应生成 DELETE: {}",
3499            del_sql
3500        );
3501        Ok(())
3502    }
3503
3504    /// 行为级测试 L3-10:build_count 也应自动追加软删除条件
3505    #[test]
3506    fn test_p01_soft_delete_count_auto_filter() -> Result<(), crate::DbError> {
3507        let dialect = get_dialect(DbType::MySQL)?;
3508        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
3509            .table("soft_users")
3510            .build_count();
3511        assert!(
3512            sql.contains("`deleted_at` IS NULL"),
3513            "build_count 也应追加软删除过滤: {}",
3514            sql
3515        );
3516        Ok(())
3517    }
3518
3519    // ==================== P0-2 参数化查询注入防护测试 ====================
3520
3521    /// 行为级测试 L3-11:where_eq 使用 `?` 占位符,值收集到 params
3522    ///
3523    /// 用户视角:参数化查询杜绝 SQL 注入。
3524    #[test]
3525    fn test_p02_where_eq_uses_placeholder() -> Result<(), crate::DbError> {
3526        let dialect = get_dialect(DbType::MySQL)?;
3527        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3528            .table("users")
3529            .where_eq("name", Value::String("alice".into()))
3530            .build_select_with_params();
3531        // SQL 中应含 `?` 占位符,不应内嵌值
3532        assert!(sql.contains("`name` = ?"), "应使用 ? 占位符: {}", sql);
3533        assert!(!sql.contains("'alice'"), "不应内嵌值到 SQL: {}", sql);
3534        assert_eq!(params.len(), 1);
3535        assert_eq!(params[0], Value::String("alice".into()));
3536        Ok(())
3537    }
3538
3539    /// 行为级测试 L3-12:where_like 使用 `?` 占位符
3540    #[test]
3541    fn test_p02_where_like_uses_placeholder() -> Result<(), crate::DbError> {
3542        let dialect = get_dialect(DbType::MySQL)?;
3543        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3544            .table("users")
3545            .where_like("name", Value::String("%alice%".into()))
3546            .build_select_with_params();
3547        assert!(sql.contains("`name` LIKE ?"), "应使用 LIKE ?: {}", sql);
3548        assert!(!sql.contains("%alice%"), "不应内嵌 pattern: {}", sql);
3549        assert_eq!(params.len(), 1);
3550        Ok(())
3551    }
3552
3553    /// 行为级测试 L3-12a:where_ne 使用 `?` 占位符
3554    ///
3555    /// 验证 P0-2 参数化 API where_ne 生成 `field != ?` 且值不内嵌。
3556    #[test]
3557    fn test_p02_where_ne_uses_placeholder() -> Result<(), crate::DbError> {
3558        let dialect = get_dialect(DbType::MySQL)?;
3559        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3560            .table("users")
3561            .where_ne("status", Value::I64(0))
3562            .build_select_with_params();
3563        assert!(sql.contains("`status` != ?"), "应使用 != ?: {}", sql);
3564        assert!(!sql.contains("!= 0"), "不应内嵌值: {}", sql);
3565        assert_eq!(params.len(), 1);
3566        assert_eq!(params[0], Value::I64(0));
3567        Ok(())
3568    }
3569
3570    /// 行为级测试 L3-12b:where_ge 使用 `?` 占位符
3571    ///
3572    /// 验证 P0-2 参数化 API where_ge 生成 `field >= ?` 且值不内嵌。
3573    #[test]
3574    fn test_p02_where_ge_uses_placeholder() -> Result<(), crate::DbError> {
3575        let dialect = get_dialect(DbType::MySQL)?;
3576        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3577            .table("users")
3578            .where_ge("age", Value::I64(18))
3579            .build_select_with_params();
3580        assert!(sql.contains("`age` >= ?"), "应使用 >= ?: {}", sql);
3581        assert!(!sql.contains(">= 18"), "不应内嵌值: {}", sql);
3582        assert_eq!(params.len(), 1);
3583        assert_eq!(params[0], Value::I64(18));
3584        Ok(())
3585    }
3586
3587    /// 行为级测试 L3-12c:where_lt 使用 `?` 占位符
3588    ///
3589    /// 验证 P0-2 参数化 API where_lt 生成 `field < ?` 且值不内嵌。
3590    #[test]
3591    fn test_p02_where_lt_uses_placeholder() -> Result<(), crate::DbError> {
3592        let dialect = get_dialect(DbType::MySQL)?;
3593        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3594            .table("users")
3595            .where_lt("score", Value::F64(60.0))
3596            .build_select_with_params();
3597        assert!(sql.contains("`score` < ?"), "应使用 < ?: {}", sql);
3598        assert!(!sql.contains("< 60"), "不应内嵌值: {}", sql);
3599        assert_eq!(params.len(), 1);
3600        assert_eq!(params[0], Value::F64(60.0));
3601        Ok(())
3602    }
3603
3604    /// 行为级测试 L3-13:注入攻击防护 - 值含 SQL 关键字也不会被解释执行
3605    ///
3606    /// 用户视角:即使用户输入 `'; DROP TABLE users; --`,也不会造成注入。
3607    #[test]
3608    fn test_p02_injection_protection_drop_table() -> Result<(), crate::DbError> {
3609        let dialect = get_dialect(DbType::MySQL)?;
3610        let evil_input = "'; DROP TABLE users; --".to_string();
3611        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3612            .table("users")
3613            .where_eq("name", Value::String(evil_input.clone()))
3614            .build_select_with_params();
3615        // SQL 中不应出现 DROP TABLE
3616        assert!(!sql.contains("DROP TABLE"), "SQL 注入未防护: {}", sql);
3617        // 整个恶意字符串应作为单一参数传递
3618        assert_eq!(params.len(), 1);
3619        assert_eq!(params[0], Value::String(evil_input));
3620        // SQL 中只有 1 个 `?`
3621        assert_eq!(sql.matches('?').count(), 1);
3622        Ok(())
3623    }
3624
3625    /// 行为级测试 L3-14:注入攻击防护 - OR 1=1 经典攻击
3626    #[test]
3627    fn test_p02_injection_protection_or_one_equals_one() -> Result<(), crate::DbError> {
3628        let dialect = get_dialect(DbType::MySQL)?;
3629        let evil = "' OR '1'='1".to_string();
3630        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3631            .table("users")
3632            .where_eq("name", Value::String(evil.clone()))
3633            .build_select_with_params();
3634        assert!(!sql.contains("OR '1'='1'"), "OR 1=1 注入未防护: {}", sql);
3635        assert_eq!(params.len(), 1);
3636        assert_eq!(params[0], Value::String(evil));
3637        Ok(())
3638    }
3639
3640    /// 行为级测试 L3-15:多参数顺序正确(WHERE a = ? AND b = ?)
3641    #[test]
3642    fn test_p02_multiple_params_order() -> Result<(), crate::DbError> {
3643        let dialect = get_dialect(DbType::MySQL)?;
3644        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3645            .table("users")
3646            .where_eq("name", Value::String("alice".into()))
3647            .where_gt("age", Value::I64(18))
3648            .where_le("score", Value::F64(99.5))
3649            .build_select_with_params();
3650        assert_eq!(sql.matches('?').count(), 3, "应有 3 个占位符: {}", sql);
3651        assert_eq!(params.len(), 3);
3652        // 参数顺序应与 WHERE 子句出现顺序一致
3653        assert_eq!(params[0], Value::String("alice".into()));
3654        assert_eq!(params[1], Value::I64(18));
3655        assert_eq!(params[2], Value::F64(99.5));
3656        Ok(())
3657    }
3658
3659    /// 行为级测试 L3-16:where_in 参数化
3660    #[test]
3661    fn test_p02_where_in_uses_placeholders() -> Result<(), crate::DbError> {
3662        let dialect = get_dialect(DbType::MySQL)?;
3663        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3664            .table("users")
3665            .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
3666            .build_select_with_params();
3667        assert!(
3668            sql.contains("`id` IN (?, ?, ?)"),
3669            "应使用 3 个占位符: {}",
3670            sql
3671        );
3672        assert_eq!(params.len(), 3);
3673        Ok(())
3674    }
3675
3676    /// 行为级测试 L3-17:where_between 参数化
3677    #[test]
3678    fn test_p02_where_between_uses_placeholders() -> Result<(), crate::DbError> {
3679        let dialect = get_dialect(DbType::MySQL)?;
3680        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3681            .table("users")
3682            .where_between("age", Value::I64(18), Value::I64(65))
3683            .build_select_with_params();
3684        assert!(
3685            sql.contains("`age` BETWEEN ? AND ?"),
3686            "应使用 2 个占位符: {}",
3687            sql
3688        );
3689        assert_eq!(params.len(), 2);
3690        assert_eq!(params[0], Value::I64(18));
3691        assert_eq!(params[1], Value::I64(65));
3692        Ok(())
3693    }
3694
3695    /// 行为级测试 L3-18:UPDATE 参数化版本 - SET 参数在前,WHERE 参数在后
3696    #[test]
3697    fn test_p02_update_params_order_set_before_where() -> Result<(), crate::DbError> {
3698        let dialect = get_dialect(DbType::MySQL)?;
3699        let mut data = std::collections::HashMap::new();
3700        data.insert("name".to_string(), Value::String("bob".into()));
3701        data.insert("age".to_string(), Value::I64(30));
3702        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3703            .table("users")
3704            .where_eq("id", Value::I64(99))
3705            .build_update_with_params(&data);
3706        // SET 子句应有 2 个占位符,WHERE 子句 1 个,共 3 个
3707        assert_eq!(sql.matches('?').count(), 3, "应有 3 个 ?: {}", sql);
3708        assert_eq!(params.len(), 3);
3709        // 前 2 个为 SET 参数,最后 1 个为 WHERE 参数
3710        // 注意:HashMap 迭代顺序未指定,仅校验 WHERE 参数在最后
3711        assert_eq!(params[2], Value::I64(99));
3712        Ok(())
3713    }
3714
3715    /// 行为级测试 L3-19:build_where_clause(无参数版本)参数化条件内嵌值
3716    ///
3717    /// 验证无参数版本(build_select)对参数化条件的处理:直接内嵌转义值。
3718    #[test]
3719    fn test_p02_build_where_clause_inlines_value() -> Result<(), crate::DbError> {
3720        let dialect = get_dialect(DbType::MySQL)?;
3721        let sql = QueryBuilder::<TestModel>::new(dialect)
3722            .table("users")
3723            .where_eq("name", Value::String("alice".into()))
3724            .sql();
3725        // 无参数版本应内嵌值(依赖 to_param_with_dialect 转义)
3726        assert!(
3727            sql.contains("`name` = "),
3728            "无参数版本应含 WHERE 条件: {}",
3729            sql
3730        );
3731        // 不应含 `?`(无参数版本)
3732        assert!(
3733            !sql.contains("`name` = ?"),
3734            "无参数版本不应使用 ? 占位符: {}",
3735            sql
3736        );
3737        Ok(())
3738    }
3739
3740    /// 行为级测试 L3-20:is_soft_delete_disabled 反映状态
3741    #[test]
3742    fn test_p01_is_soft_delete_disabled_flag() -> Result<(), crate::DbError> {
3743        let dialect = get_dialect(DbType::MySQL)?;
3744        let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
3745        assert!(!builder.is_soft_delete_disabled(), "默认应启用软删除过滤");
3746        let builder =
3747            QueryBuilder::<SoftDeleteModel>::new(get_dialect(DbType::MySQL)?).without_soft_delete();
3748        assert!(
3749            builder.is_soft_delete_disabled(),
3750            "without_soft_delete 后应反映禁用状态"
3751        );
3752        Ok(())
3753    }
3754
3755    // ==================== P0-3 多租户自动过滤行为测试 ====================
3756
3757    /// 多租户测试模型:实现 tenant_field() 返回 "tenant_id"
3758    struct TenantModel;
3759    impl Model for TenantModel {
3760        type PrimaryKey = i64;
3761
3762        fn table_name() -> &'static str {
3763            "orders"
3764        }
3765
3766        fn pk(&self) -> Self::PrimaryKey {
3767            1
3768        }
3769
3770        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
3771
3772        fn tenant_field() -> Option<&'static str> {
3773            Some("tenant_id")
3774        }
3775    }
3776
3777    /// 同时实现软删除 + 多租户的模型
3778    struct SoftDeleteAndTenantModel;
3779    impl Model for SoftDeleteAndTenantModel {
3780        type PrimaryKey = i64;
3781
3782        fn table_name() -> &'static str {
3783            "documents"
3784        }
3785
3786        fn pk(&self) -> Self::PrimaryKey {
3787            1
3788        }
3789
3790        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
3791
3792        fn soft_delete_field() -> Option<&'static str> {
3793            Some("deleted_at")
3794        }
3795
3796        fn tenant_field() -> Option<&'static str> {
3797            Some("tenant_id")
3798        }
3799    }
3800
3801    /// 行为级测试 L3-21:多租户模型 + with_tenant_id 自动追加 WHERE tenant_id = ?
3802    ///
3803    /// 用户视角:设置租户 ID 后,查询自动过滤当前租户数据。
3804    #[test]
3805    fn test_p03_tenant_select_auto_filter() -> Result<(), crate::DbError> {
3806        let dialect = get_dialect(DbType::MySQL)?;
3807        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3808            .table("orders")
3809            .with_tenant_id(42)
3810            .build_select_with_params();
3811        assert!(
3812            sql.contains("`tenant_id` = ?"),
3813            "多租户模型应自动追加 tenant_id = ?: {}",
3814            sql
3815        );
3816        assert_eq!(params.len(), 1, "应有 1 个参数(tenant_id 值)");
3817        assert_eq!(params[0], Value::I64(42));
3818        Ok(())
3819    }
3820
3821    /// 行为级测试 L3-22:多租户模型 + 用户 WHERE 条件 + 租户条件
3822    #[test]
3823    fn test_p03_tenant_select_with_user_where() -> Result<(), crate::DbError> {
3824        let dialect = get_dialect(DbType::MySQL)?;
3825        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3826            .table("orders")
3827            .with_tenant_id(7)
3828            .where_eq("status", Value::String("active".into()))
3829            .build_select_with_params();
3830        assert!(sql.contains("`status` = ?"), "用户条件应保留: {}", sql);
3831        assert!(
3832            sql.contains("`tenant_id` = ?"),
3833            "租户条件应自动追加: {}",
3834            sql
3835        );
3836        assert_eq!(params.len(), 2, "应有 2 个参数");
3837        // 第 1 个为用户 where_eq 的值,第 2 个为 tenant_id
3838        assert_eq!(params[0], Value::String("active".into()));
3839        assert_eq!(params[1], Value::I64(7));
3840        Ok(())
3841    }
3842
3843    /// 行为级测试 L3-23:without_tenant() 临时禁用租户过滤
3844    ///
3845    /// 用户视角:管理员跨租户查询时禁用自动过滤。
3846    #[test]
3847    fn test_p03_tenant_without_tenant() -> Result<(), crate::DbError> {
3848        let dialect = get_dialect(DbType::MySQL)?;
3849        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3850            .table("orders")
3851            .with_tenant_id(42)
3852            .without_tenant()
3853            .build_select_with_params();
3854        assert!(
3855            !sql.contains("`tenant_id` = ?"),
3856            "without_tenant 应禁用过滤: {}",
3857            sql
3858        );
3859        assert_eq!(params.len(), 0, "不应有租户参数");
3860        Ok(())
3861    }
3862
3863    /// 行为级测试 L3-24:多租户模型 build_delete 自动追加租户条件
3864    ///
3865    /// 用户视角:删除操作自动限定在当前租户,防止跨租户删除。
3866    #[test]
3867    fn test_p03_tenant_delete_auto_filter() -> Result<(), crate::DbError> {
3868        let dialect = get_dialect(DbType::MySQL)?;
3869        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3870            .table("orders")
3871            .with_tenant_id(99)
3872            .where_eq("id", Value::I64(1))
3873            .build_delete_with_params();
3874        assert!(
3875            sql.contains("`tenant_id` = ?"),
3876            "删除应自动追加租户条件: {}",
3877            sql
3878        );
3879        // 2 个参数:where_eq(id=1) + tenant_id=99
3880        assert_eq!(params.len(), 2);
3881        assert_eq!(params[0], Value::I64(1));
3882        assert_eq!(params[1], Value::I64(99));
3883        Ok(())
3884    }
3885
3886    /// 行为级测试 L3-25:多租户模型 build_update 自动追加租户条件
3887    #[test]
3888    fn test_p03_tenant_update_auto_filter() -> Result<(), crate::DbError> {
3889        let dialect = get_dialect(DbType::MySQL)?;
3890        let mut data = std::collections::HashMap::new();
3891        data.insert("status".to_string(), Value::String("shipped".into()));
3892        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3893            .table("orders")
3894            .with_tenant_id(5)
3895            .where_eq("id", Value::I64(10))
3896            .build_update_with_params(&data);
3897        assert!(
3898            sql.contains("`tenant_id` = ?"),
3899            "更新应自动追加租户条件: {}",
3900            sql
3901        );
3902        // 3 个参数:SET status + WHERE id + tenant_id
3903        assert_eq!(params.len(), 3);
3904        // 最后一个应为 tenant_id
3905        assert_eq!(params[2], Value::I64(5));
3906        Ok(())
3907    }
3908
3909    /// 行为级测试 L3-26:多租户模型 build_count 自动追加租户条件
3910    #[test]
3911    fn test_p03_tenant_count_auto_filter() -> Result<(), crate::DbError> {
3912        let dialect = get_dialect(DbType::MySQL)?;
3913        let sql = QueryBuilder::<TenantModel>::new(dialect)
3914            .table("orders")
3915            .with_tenant_id(42)
3916            .build_count();
3917        assert!(
3918            sql.contains("`tenant_id` = 42"),
3919            "build_count 应追加租户条件(无参数版本内嵌值): {}",
3920            sql
3921        );
3922        Ok(())
3923    }
3924
3925    /// 行为级测试 L3-27:非多租户模型 TestModel 不追加租户条件
3926    ///
3927    /// 用户视角:未启用多租户的模型行为不变。
3928    #[test]
3929    fn test_p03_non_tenant_model_unchanged() -> Result<(), crate::DbError> {
3930        let dialect = get_dialect(DbType::MySQL)?;
3931        // 即使设置了 with_tenant_id,非多租户模型也不应追加
3932        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3933            .table("users")
3934            .with_tenant_id(42)
3935            .build_select_with_params();
3936        assert!(
3937            !sql.contains("tenant_id"),
3938            "非多租户模型不应追加 tenant_id: {}",
3939            sql
3940        );
3941        assert_eq!(params.len(), 0);
3942        Ok(())
3943    }
3944
3945    /// 行为级测试 L3-28:多租户模型未设置 tenant_id 时不追加条件
3946    ///
3947    /// 用户视角:未设置租户 ID 时,查询不追加租户过滤(允许跨租户,需调用方保证安全)。
3948    #[test]
3949    fn test_p03_tenant_no_id_no_filter() -> Result<(), crate::DbError> {
3950        let dialect = get_dialect(DbType::MySQL)?;
3951        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3952            .table("orders")
3953            .build_select_with_params();
3954        assert!(
3955            !sql.contains("tenant_id"),
3956            "未设置 tenant_id 时不应追加过滤: {}",
3957            sql
3958        );
3959        assert_eq!(params.len(), 0);
3960        Ok(())
3961    }
3962
3963    /// 行为级测试 L3-29:软删除 + 多租户组合,两个条件同时追加
3964    ///
3965    /// 用户视角:同时启用软删除和多租户时,查询自动追加两个条件。
3966    #[test]
3967    fn test_p03_soft_delete_and_tenant_combined() -> Result<(), crate::DbError> {
3968        let dialect = get_dialect(DbType::MySQL)?;
3969        let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3970            .table("documents")
3971            .with_tenant_id(100)
3972            .where_eq("title", Value::String("report".into()))
3973            .build_select_with_params();
3974        // 软删除条件
3975        assert!(
3976            sql.contains("`deleted_at` IS NULL"),
3977            "应追加软删除条件: {}",
3978            sql
3979        );
3980        // 租户条件
3981        assert!(sql.contains("`tenant_id` = ?"), "应追加租户条件: {}", sql);
3982        // 用户条件
3983        assert!(sql.contains("`title` = ?"), "用户条件应保留: {}", sql);
3984        // 2 个参数:where_eq(title) + tenant_id(软删除 IS NULL 无参数)
3985        assert_eq!(params.len(), 2);
3986        assert_eq!(params[0], Value::String("report".into()));
3987        assert_eq!(params[1], Value::I64(100));
3988        Ok(())
3989    }
3990
3991    /// 行为级测试 L3-30:without_tenant + without_soft_delete 同时禁用
3992    #[test]
3993    fn test_p03_without_tenant_and_soft_delete() -> Result<(), crate::DbError> {
3994        let dialect = get_dialect(DbType::MySQL)?;
3995        let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3996            .table("documents")
3997            .with_tenant_id(100)
3998            .without_tenant()
3999            .without_soft_delete()
4000            .build_select_with_params();
4001        assert!(
4002            !sql.contains("`deleted_at` IS NULL"),
4003            "应禁用软删除: {}",
4004            sql
4005        );
4006        assert!(!sql.contains("`tenant_id` = ?"), "应禁用租户: {}", sql);
4007        assert_eq!(params.len(), 0);
4008        Ok(())
4009    }
4010
4011    /// 行为级测试 L3-31:is_tenant_disabled 反映状态
4012    #[test]
4013    fn test_p03_is_tenant_disabled_flag() -> Result<(), crate::DbError> {
4014        let dialect = get_dialect(DbType::MySQL)?;
4015        let builder = QueryBuilder::<TenantModel>::new(dialect);
4016        assert!(!builder.is_tenant_disabled(), "默认应启用租户过滤");
4017        let builder = QueryBuilder::<TenantModel>::new(get_dialect(DbType::MySQL)?)
4018            .with_tenant_id(1)
4019            .without_tenant();
4020        assert!(
4021            builder.is_tenant_disabled(),
4022            "without_tenant 后应反映禁用状态"
4023        );
4024        Ok(())
4025    }
4026
4027    /// 行为级测试 L3-32:build_force_delete 保留租户条件(防止跨租户物理删除)
4028    ///
4029    /// 用户视角:物理删除也应受租户隔离约束,跨租户操作需显式 without_tenant()。
4030    #[test]
4031    fn test_p03_tenant_force_delete_keeps_tenant_filter() -> Result<(), crate::DbError> {
4032        let dialect = get_dialect(DbType::MySQL)?;
4033        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
4034            .table("orders")
4035            .with_tenant_id(42)
4036            .where_eq("id", Value::I64(999))
4037            .build_force_delete_with_params();
4038        // 物理删除不应追加软删除(TenantModel 未实现软删除,无影响)
4039        // 但应保留租户条件
4040        assert!(
4041            sql.contains("`tenant_id` = ?"),
4042            "物理删除应保留租户条件: {}",
4043            sql
4044        );
4045        assert_eq!(params.len(), 2);
4046        assert_eq!(params[0], Value::I64(999));
4047        assert_eq!(params[1], Value::I64(42));
4048        Ok(())
4049    }
4050
4051    // ─── v3.3.0 multi-tenant-enhanced:上下文自动注入测试 ──────────────
4052
4053    /// 既有 with_tenant_id 行为不变(显式优先于上下文自动注入)
4054    #[cfg(feature = "multi-tenant-enhanced")]
4055    #[tokio::test]
4056    async fn test_mt_explicit_tenant_id_takes_priority() -> Result<(), crate::DbError> {
4057        let ctx = crate::tenant_context::TenantContext::new(
4058            99,
4059            crate::tenant_context::IsolationStrategy::RowLevel,
4060        );
4061        ctx.scope(async {
4062            // 显式 with_tenant_id(42) 应优先于上下文的 tenant_id=99
4063            let (sql, params) =
4064                QueryBuilder::<TenantModel>::new(get_dialect(DbType::MySQL).unwrap())
4065                    .table("orders")
4066                    .with_tenant_id(42)
4067                    .build_select_with_params();
4068            assert!(sql.contains("`tenant_id` = ?"), "应追加租户条件: {}", sql);
4069            assert_eq!(params.len(), 1);
4070            assert_eq!(params[0], Value::I64(42), "显式 tenant_id 应优先");
4071        })
4072        .await;
4073        Ok(())
4074    }
4075
4076    /// 上下文自动注入:未显式 with_tenant_id 时从 TenantContext 读取
4077    #[cfg(feature = "multi-tenant-enhanced")]
4078    #[tokio::test]
4079    async fn test_mt_context_auto_inject() -> Result<(), crate::DbError> {
4080        let ctx = crate::tenant_context::TenantContext::new(
4081            77,
4082            crate::tenant_context::IsolationStrategy::RowLevel,
4083        );
4084        ctx.scope(async {
4085            let (sql, params) =
4086                QueryBuilder::<TenantModel>::new(get_dialect(DbType::MySQL).unwrap())
4087                    .table("orders")
4088                    .build_select_with_params();
4089            assert!(
4090                sql.contains("`tenant_id` = ?"),
4091                "应从上下文自动追加租户条件: {}",
4092                sql
4093            );
4094            assert_eq!(params.len(), 1);
4095            assert_eq!(params[0], Value::I64(77), "应从上下文注入 tenant_id");
4096        })
4097        .await;
4098        Ok(())
4099    }
4100
4101    /// Schema 隔离策略:表名重写为 tenant_{id}_{table}
4102    #[cfg(feature = "multi-tenant-enhanced")]
4103    #[tokio::test]
4104    async fn test_mt_schema_isolation_table_rewrite() -> Result<(), crate::DbError> {
4105        let ctx = crate::tenant_context::TenantContext::new(
4106            42,
4107            crate::tenant_context::IsolationStrategy::SchemaIsolation,
4108        );
4109        ctx.scope(async {
4110            let (sql, _params) =
4111                QueryBuilder::<TenantModel>::new(get_dialect(DbType::MySQL).unwrap())
4112                    .table("orders")
4113                    .build_select_with_params();
4114            assert!(
4115                sql.contains("tenant_42_orders"),
4116                "Schema 隔离应重写表名: {}",
4117                sql
4118            );
4119        })
4120        .await;
4121        Ok(())
4122    }
4123
4124    /// 既有 API 兼容:feature 启用但未设置上下文时行为不变
4125    #[cfg(feature = "multi-tenant-enhanced")]
4126    #[test]
4127    fn test_mt_no_context_no_change() -> Result<(), crate::DbError> {
4128        let (sql, params) = QueryBuilder::<TenantModel>::new(get_dialect(DbType::MySQL)?)
4129            .table("orders")
4130            .build_select_with_params();
4131        // 未设置上下文且未显式 with_tenant_id:不追加租户条件(既有行为不变)
4132        assert!(
4133            !sql.contains("`tenant_id` = ?"),
4134            "未设置上下文不应追加租户条件: {}",
4135            sql
4136        );
4137        assert_eq!(params.len(), 0);
4138        Ok(())
4139    }
4140
4141    // ---- TypedColumn 类型安全方法测试 ----
4142
4143    struct TcUsersTable;
4144    impl crate::typed::TypedTable for TcUsersTable {
4145        const NAME: &'static str = "users";
4146    }
4147    struct TcColId;
4148    impl crate::typed::TypedColumn for TcColId {
4149        const NAME: &'static str = "id";
4150        type Table = TcUsersTable;
4151        type RustType = i64;
4152        type SqlType = crate::typed_ast::Untyped;
4153    }
4154    struct TcColName;
4155    impl crate::typed::TypedColumn for TcColName {
4156        const NAME: &'static str = "name";
4157        type Table = TcUsersTable;
4158        type RustType = String;
4159        type SqlType = crate::typed_ast::Untyped;
4160    }
4161
4162    #[test]
4163    fn test_where_eq_typed() -> Result<(), crate::DbError> {
4164        let dialect = get_dialect(DbType::MySQL)?;
4165        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
4166            .where_eq_typed::<TcColId>(Value::I64(42))
4167            .build_select_with_params();
4168        assert!(sql.contains("`id` = ?"));
4169        assert_eq!(params[0], Value::I64(42));
4170        Ok(())
4171    }
4172
4173    #[test]
4174    fn test_order_by_typed() -> Result<(), crate::DbError> {
4175        let dialect = get_dialect(DbType::MySQL)?;
4176        let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
4177            .order_by_typed::<TcColName>()
4178            .build_select_with_params();
4179        assert!(sql.contains("ORDER BY"));
4180        assert!(sql.contains("`name`"));
4181        Ok(())
4182    }
4183
4184    #[test]
4185    fn test_select_typed() -> Result<(), crate::DbError> {
4186        let dialect = get_dialect(DbType::MySQL)?;
4187        let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
4188            .select_typed::<TcColId>()
4189            .select_typed::<TcColName>()
4190            .build_select_with_params();
4191        assert!(sql.contains("SELECT"));
4192        // select_columns are joined as-is (not quoted by build_select_with_params)
4193        assert!(sql.contains("id"));
4194        assert!(sql.contains("name"));
4195        Ok(())
4196    }
4197
4198    #[test]
4199    fn test_where_null_typed() -> Result<(), crate::DbError> {
4200        let dialect = get_dialect(DbType::MySQL)?;
4201        let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
4202            .where_null_typed::<TcColName>()
4203            .build_select_with_params();
4204        assert!(sql.contains("`name` IS NULL"));
4205        Ok(())
4206    }
4207
4208    #[test]
4209    fn test_where_not_null_typed() -> Result<(), crate::DbError> {
4210        let dialect = get_dialect(DbType::MySQL)?;
4211        let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
4212            .where_not_null_typed::<TcColName>()
4213            .build_select_with_params();
4214        assert!(sql.contains("`name` IS NOT NULL"));
4215        Ok(())
4216    }
4217
4218    #[test]
4219    fn test_group_by_typed() -> Result<(), crate::DbError> {
4220        let dialect = get_dialect(DbType::MySQL)?;
4221        let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
4222            .group_by_typed::<TcColName>()
4223            .build_select_with_params();
4224        assert!(sql.contains("GROUP BY"));
4225        assert!(sql.contains("`name`"));
4226        Ok(())
4227    }
4228
4229    #[test]
4230    fn test_where_gt_typed() -> Result<(), crate::DbError> {
4231        let dialect = get_dialect(DbType::MySQL)?;
4232        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
4233            .where_gt_typed::<TcColId>(Value::I64(10))
4234            .build_select_with_params();
4235        assert!(sql.contains("`id` > ?"));
4236        assert_eq!(params[0], Value::I64(10));
4237        Ok(())
4238    }
4239
4240    // ---- P2-3:行锁查询测试(TASK-025/026) ----
4241
4242    #[test]
4243    fn test_lock_for_update_mysql() -> Result<(), crate::DbError> {
4244        let dialect = get_dialect(DbType::MySQL)?;
4245        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
4246            .table("users")
4247            .where_eq("id", Value::I64(1))
4248            .lock_for_update()?
4249            .build_select_with_params();
4250        assert!(sql.contains("SELECT * FROM `users`"));
4251        assert!(sql.contains("WHERE `id` = ?"));
4252        assert!(sql.contains("FOR UPDATE"));
4253        assert_eq!(params.len(), 1);
4254        assert_eq!(params[0], Value::I64(1));
4255        Ok(())
4256    }
4257
4258    #[test]
4259    fn test_lock_shared_mysql() -> Result<(), crate::DbError> {
4260        let dialect = get_dialect(DbType::MySQL)?;
4261        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
4262            .table("users")
4263            .where_eq("id", Value::I64(1))
4264            .lock_shared()?
4265            .build_select_with_params();
4266        assert!(sql.contains("SELECT * FROM `users`"));
4267        assert!(sql.contains("WHERE `id` = ?"));
4268        assert!(sql.contains("LOCK IN SHARE MODE"));
4269        assert_eq!(params.len(), 1);
4270        assert_eq!(params[0], Value::I64(1));
4271        Ok(())
4272    }
4273
4274    #[test]
4275    fn test_lock_for_update_postgresql() -> Result<(), crate::DbError> {
4276        let dialect = get_dialect(DbType::PostgreSQL)?;
4277        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
4278            .table("users")
4279            .where_eq("id", Value::I64(1))
4280            .lock_for_update()?
4281            .build_select_with_params();
4282        assert!(sql.contains("SELECT * FROM \"users\""));
4283        assert!(sql.contains("WHERE \"id\" = ?"));
4284        assert!(sql.contains("FOR UPDATE"));
4285        assert_eq!(params.len(), 1);
4286        assert_eq!(params[0], Value::I64(1));
4287        Ok(())
4288    }
4289
4290    #[test]
4291    fn test_lock_shared_postgresql() -> Result<(), crate::DbError> {
4292        let dialect = get_dialect(DbType::PostgreSQL)?;
4293        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
4294            .table("users")
4295            .where_eq("id", Value::I64(1))
4296            .lock_shared()?
4297            .build_select_with_params();
4298        assert!(sql.contains("SELECT * FROM \"users\""));
4299        assert!(sql.contains("WHERE \"id\" = ?"));
4300        assert!(sql.contains("FOR SHARE"));
4301        assert_eq!(params.len(), 1);
4302        assert_eq!(params[0], Value::I64(1));
4303        Ok(())
4304    }
4305
4306    #[test]
4307    fn test_lock_for_update_sqlite_should_fail() {
4308        let dialect = get_dialect(DbType::Sqlite).unwrap();
4309        let result = QueryBuilder::<TestModel>::new(dialect)
4310            .table("users")
4311            .where_eq("id", Value::I64(1))
4312            .lock_for_update();
4313        assert!(result.is_err(), "SQLite 不应支持 FOR UPDATE 锁");
4314        let err = result.err().unwrap();
4315        assert!(
4316            format!("{:?}", err).contains("FOR UPDATE lock is not supported"),
4317            "错误信息应说明不支持行锁"
4318        );
4319    }
4320
4321    #[test]
4322    fn test_lock_shared_sqlite_should_fail() {
4323        let dialect = get_dialect(DbType::Sqlite).unwrap();
4324        let result = QueryBuilder::<TestModel>::new(dialect)
4325            .table("users")
4326            .where_eq("id", Value::I64(1))
4327            .lock_shared();
4328        assert!(result.is_err(), "SQLite 不应支持共享锁");
4329        let err = result.err().unwrap();
4330        assert!(
4331            format!("{:?}", err).contains("Shared lock is not supported"),
4332            "错误信息应说明不支持共享锁"
4333        );
4334    }
4335
4336    #[test]
4337    fn test_lock_with_limit_and_offset() -> Result<(), crate::DbError> {
4338        let dialect = get_dialect(DbType::MySQL)?;
4339        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
4340            .table("users")
4341            .where_eq("status", Value::String("active".into()))
4342            .limit(10)
4343            .offset(20)
4344            .lock_for_update()?
4345            .build_select_with_params();
4346        assert!(sql.contains("WHERE `status` = ?"));
4347        assert!(sql.contains("LIMIT 10"));
4348        assert!(sql.contains("OFFSET 20"));
4349        assert!(sql.contains("FOR UPDATE"));
4350        assert_eq!(params.len(), 1);
4351        Ok(())
4352    }
4353
4354    // ---- P2-4:INSERT OR IGNORE 测试(TASK-027/028) ----
4355
4356    #[test]
4357    fn test_insert_or_ignore_mysql() -> Result<(), crate::DbError> {
4358        let dialect = get_dialect(DbType::MySQL)?;
4359        let mut data = std::collections::HashMap::new();
4360        data.insert("name".to_string(), Value::String("Alice".into()));
4361        data.insert("age".to_string(), Value::I64(30));
4362
4363        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
4364            .table("users")
4365            .insert_or_ignore()
4366            .build_insert_with_params(&data);
4367        assert!(sql.contains("INSERT IGNORE INTO `users`"));
4368        // HashMap 迭代顺序不确定,检查列名都存在即可
4369        assert!(sql.contains("`name`"), "SQL 应包含 name 列: {}", sql);
4370        assert!(sql.contains("`age`"), "SQL 应包含 age 列: {}", sql);
4371        assert!(sql.contains("VALUES (?, ?)"));
4372        assert_eq!(params.len(), 2);
4373        Ok(())
4374    }
4375
4376    #[test]
4377    fn test_insert_or_ignore_postgresql() -> Result<(), crate::DbError> {
4378        let dialect = get_dialect(DbType::PostgreSQL)?;
4379        let mut data = std::collections::HashMap::new();
4380        data.insert("name".to_string(), Value::String("Bob".into()));
4381
4382        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
4383            .table("users")
4384            .insert_or_ignore()
4385            .build_insert_with_params(&data);
4386        assert!(sql.contains("INSERT OR IGNORE INTO \"users\""));
4387        assert!(sql.contains("(\"name\")"));
4388        assert!(sql.contains("VALUES (?)"));
4389        assert_eq!(params.len(), 1);
4390        assert_eq!(params[0], Value::String("Bob".into()));
4391        Ok(())
4392    }
4393
4394    #[test]
4395    fn test_insert_or_ignore_sqlite() -> Result<(), crate::DbError> {
4396        let dialect = get_dialect(DbType::Sqlite)?;
4397        let mut data = std::collections::HashMap::new();
4398        data.insert("name".to_string(), Value::String("Charlie".into()));
4399
4400        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
4401            .table("users")
4402            .insert_or_ignore()
4403            .build_insert_with_params(&data);
4404        assert!(sql.contains("INSERT OR IGNORE INTO \"users\""));
4405        assert!(sql.contains("(\"name\")"));
4406        assert!(sql.contains("VALUES (?)"));
4407        assert_eq!(params.len(), 1);
4408        assert_eq!(params[0], Value::String("Charlie".into()));
4409        Ok(())
4410    }
4411
4412    #[test]
4413    fn test_insert_normal_without_ignore() -> Result<(), crate::DbError> {
4414        let dialect = get_dialect(DbType::MySQL)?;
4415        let mut data = std::collections::HashMap::new();
4416        data.insert("name".to_string(), Value::String("Dave".into()));
4417
4418        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
4419            .table("users")
4420            .build_insert_with_params(&data);
4421        assert!(sql.contains("INSERT INTO `users`"));
4422        assert!(!sql.contains("IGNORE"), "普通插入不应包含 IGNORE");
4423        assert_eq!(params.len(), 1);
4424        assert_eq!(params[0], Value::String("Dave".into()));
4425        Ok(())
4426    }
4427
4428    #[test]
4429    fn test_insert_or_ignore_empty_data() -> Result<(), crate::DbError> {
4430        let dialect = get_dialect(DbType::MySQL)?;
4431        let data = std::collections::HashMap::new();
4432
4433        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
4434            .table("users")
4435            .insert_or_ignore()
4436            .build_insert_with_params(&data);
4437        assert!(sql.is_empty(), "空数据应返回空 SQL");
4438        assert!(params.is_empty());
4439        Ok(())
4440    }
4441
4442    // ---- P2-3:方言支持性测试(TASK-029) ----
4443
4444    #[test]
4445    fn test_dialect_supports_lock_for_update() -> Result<(), crate::DbError> {
4446        let mysql = get_dialect(DbType::MySQL)?;
4447        let pg = get_dialect(DbType::PostgreSQL)?;
4448        let sqlite = get_dialect(DbType::Sqlite)?;
4449        let clickhouse = get_dialect(DbType::ClickHouse)?;
4450        let duckdb = get_dialect(DbType::DuckDB)?;
4451
4452        assert!(mysql.supports_lock_for_update(), "MySQL 应支持 FOR UPDATE");
4453        assert!(
4454            pg.supports_lock_for_update(),
4455            "PostgreSQL 应支持 FOR UPDATE"
4456        );
4457        assert!(
4458            !sqlite.supports_lock_for_update(),
4459            "SQLite 不应支持 FOR UPDATE"
4460        );
4461        assert!(
4462            !clickhouse.supports_lock_for_update(),
4463            "ClickHouse 是列式 OLAP,不应支持 FOR UPDATE"
4464        );
4465        assert!(
4466            !duckdb.supports_lock_for_update(),
4467            "DuckDB 不应支持 FOR UPDATE"
4468        );
4469        Ok(())
4470    }
4471
4472    #[test]
4473    fn test_dialect_supports_lock_shared() -> Result<(), crate::DbError> {
4474        let mysql = get_dialect(DbType::MySQL)?;
4475        let pg = get_dialect(DbType::PostgreSQL)?;
4476        let sqlite = get_dialect(DbType::Sqlite)?;
4477        let clickhouse = get_dialect(DbType::ClickHouse)?;
4478        let duckdb = get_dialect(DbType::DuckDB)?;
4479
4480        assert!(mysql.supports_lock_shared(), "MySQL 应支持共享锁");
4481        assert!(pg.supports_lock_shared(), "PostgreSQL 应支持共享锁");
4482        assert!(!sqlite.supports_lock_shared(), "SQLite 不应支持共享锁");
4483        assert!(
4484            !clickhouse.supports_lock_shared(),
4485            "ClickHouse 是列式 OLAP,不应支持共享锁"
4486        );
4487        assert!(!duckdb.supports_lock_shared(), "DuckDB 不应支持共享锁");
4488        Ok(())
4489    }
4490
4491    #[test]
4492    fn test_get_lock_type_and_is_insert_or_ignore() -> Result<(), crate::DbError> {
4493        let dialect = get_dialect(DbType::MySQL)?;
4494
4495        // 默认值
4496        let builder = QueryBuilder::<TestModel>::new(dialect);
4497        assert!(builder.get_lock_type().is_none(), "默认无锁");
4498        assert!(!builder.is_insert_or_ignore(), "默认不忽略插入");
4499
4500        // 设置锁后
4501        let builder = QueryBuilder::<TestModel>::new(get_dialect(DbType::MySQL)?)
4502            .table("users")
4503            .lock_for_update()?;
4504        assert_eq!(builder.get_lock_type(), Some(LockType::ForUpdate));
4505
4506        // 设置忽略插入后
4507        let builder = QueryBuilder::<TestModel>::new(get_dialect(DbType::MySQL)?)
4508            .table("users")
4509            .insert_or_ignore();
4510        assert!(builder.is_insert_or_ignore());
4511
4512        Ok(())
4513    }
4514
4515    struct TestModelWithColumns;
4516    impl Model for TestModelWithColumns {
4517        type PrimaryKey = i64;
4518        fn table_name() -> &'static str {
4519            "test_with_cols"
4520        }
4521        fn pk(&self) -> Self::PrimaryKey {
4522            0
4523        }
4524        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
4525    }
4526    impl crate::model::ModelExt for TestModelWithColumns {
4527        fn columns() -> Vec<&'static str> {
4528            vec!["id", "name", "email", "avatar", "blob_data"]
4529        }
4530        fn fillable() -> Vec<&'static str> {
4531            vec!["name", "email", "avatar", "blob_data"]
4532        }
4533        fn guarded() -> Vec<&'static str> {
4534            vec!["id"]
4535        }
4536        fn hidden() -> Vec<&'static str> {
4537            vec!["blob_data"]
4538        }
4539        fn relations() -> std::collections::HashMap<&'static str, crate::model::Relation> {
4540            std::collections::HashMap::new()
4541        }
4542        fn fill(&mut self, _data: std::collections::HashMap<String, crate::value::Value>) {}
4543        fn to_json(&self) -> serde_json::Value {
4544            serde_json::Value::Null
4545        }
4546    }
4547
4548    #[test]
4549    fn test_select_exclude_basic() -> Result<(), crate::DbError> {
4550        let dialect = get_dialect(DbType::MySQL)?;
4551        let builder = QueryBuilder::<TestModelWithColumns>::new(dialect)
4552            .table("users")
4553            .select_exclude(&["avatar", "blob_data"])?;
4554        let sql = builder.sql();
4555        assert!(sql.contains("id"));
4556        assert!(sql.contains("name"));
4557        assert!(sql.contains("email"));
4558        assert!(!sql.contains("avatar"));
4559        assert!(!sql.contains("blob_data"));
4560        Ok(())
4561    }
4562
4563    #[test]
4564    fn test_select_exclude_nonexistent_field() {
4565        let dialect = get_dialect(DbType::MySQL).unwrap();
4566        let result = QueryBuilder::<TestModelWithColumns>::new(dialect)
4567            .table("users")
4568            .select_exclude(&["nonexistent"]);
4569        assert!(result.is_err());
4570    }
4571
4572    #[test]
4573    fn test_select_exclude_all_fields() {
4574        let dialect = get_dialect(DbType::MySQL).unwrap();
4575        let result = QueryBuilder::<TestModelWithColumns>::new(dialect)
4576            .table("users")
4577            .select_exclude(&["id", "name", "email", "avatar", "blob_data"]);
4578        assert!(result.is_err());
4579        let err = result.unwrap_err();
4580        assert!(matches!(err, crate::DbError::InvalidInput(_)));
4581    }
4582
4583    // ===== cache_ttl → L2Cache 集成测试(v4.7.0 幻影交付修复)=====
4584
4585    #[tokio::test]
4586    async fn test_execute_with_cache_ttl_hit() {
4587        use crate::l2_cache::L2Cache;
4588        use std::sync::atomic::{AtomicU64, Ordering};
4589        use std::sync::Arc;
4590        use std::time::Duration;
4591
4592        let dialect = get_dialect(DbType::MySQL).unwrap();
4593        let cache = L2Cache::new();
4594        let qb = QueryBuilder::<TestModel>::new(dialect)
4595            .table("users")
4596            .where_eq("status", Value::I64(1))
4597            .cache_ttl(Duration::from_secs(300));
4598
4599        let call_count = Arc::new(AtomicU64::new(0));
4600        let cc1 = Arc::clone(&call_count);
4601        let rows1 = qb
4602            .execute_with_cache(&cache, "users", || async move {
4603                cc1.fetch_add(1, Ordering::SeqCst);
4604                let mut row = std::collections::HashMap::new();
4605                row.insert("id".to_string(), Value::I64(1));
4606                Ok(vec![row])
4607            })
4608            .await
4609            .unwrap();
4610        assert_eq!(rows1.len(), 1);
4611        assert_eq!(call_count.load(Ordering::SeqCst), 1);
4612
4613        let cc2 = Arc::clone(&call_count);
4614        let rows2 = qb
4615            .execute_with_cache(&cache, "users", || async move {
4616                cc2.fetch_add(1, Ordering::SeqCst);
4617                let mut row = std::collections::HashMap::new();
4618                row.insert("id".to_string(), Value::I64(2));
4619                Ok(vec![row])
4620            })
4621            .await
4622            .unwrap();
4623        assert_eq!(rows2.len(), 1);
4624        assert_eq!(rows2[0].get("id"), Some(&Value::I64(1)));
4625        assert_eq!(
4626            call_count.load(Ordering::SeqCst),
4627            1,
4628            "second call should hit cache, not loader"
4629        );
4630    }
4631
4632    #[tokio::test]
4633    async fn test_execute_with_cache_no_ttl_bypasses_cache() {
4634        use crate::l2_cache::L2Cache;
4635        use std::sync::atomic::{AtomicU64, Ordering};
4636        use std::sync::Arc;
4637
4638        let dialect = get_dialect(DbType::MySQL).unwrap();
4639        let cache = L2Cache::new();
4640        let qb = QueryBuilder::<TestModel>::new(dialect).table("users");
4641
4642        let call_count = Arc::new(AtomicU64::new(0));
4643        let cc1 = Arc::clone(&call_count);
4644        let rows1 = qb
4645            .execute_with_cache(&cache, "users", || async move {
4646                cc1.fetch_add(1, Ordering::SeqCst);
4647                Ok(vec![])
4648            })
4649            .await
4650            .unwrap();
4651        assert!(rows1.is_empty());
4652
4653        let cc2 = Arc::clone(&call_count);
4654        let rows2 = qb
4655            .execute_with_cache(&cache, "users", || async move {
4656                cc2.fetch_add(1, Ordering::SeqCst);
4657                Ok(vec![])
4658            })
4659            .await
4660            .unwrap();
4661        assert!(rows2.is_empty());
4662        assert_eq!(
4663            call_count.load(Ordering::SeqCst),
4664            2,
4665            "without ttl, loader should be called each time"
4666        );
4667    }
4668}