Skip to main content

sz_orm_core/
query.rs

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