Skip to main content

sz_orm_core/
query.rs

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