Skip to main content

sz_orm_core/
query.rs

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