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